DirectX教程(7):渲染框架

  Direct3D初始化后,就该开始进行一些实际的渲染。渲染本身非常容易,但涉及到一些准备工作。在本课程中,我们将一遍又一遍的设置所有内容以渲染一个空白帧。涉及到的设置非常简单,主要有两件事要做:首先,我们需要告诉GPU在内存中的哪个位置创建最终图像(对我们来说,是在后台缓冲区);其次,我们需要告诉GPU应该在后台缓冲区上的哪个位置进行绘制。一旦完成这两个步骤,我们就可以渲染了,而渲染很简单。

设定渲染目标

  首先我们需要确定渲染到哪里,你可能会想当然说“后缓冲区”。然而,Direct3D实际上并不知道这一点,你可能也不想立即渲染到后缓冲区。例如,许多游戏渲染到模型的表面,然后将该模型渲染到后台缓冲区,这项技术可以产生多种效果。如果你玩过《传送门》这个游戏,你将看到下面的场景。在《传送门》中,游戏引擎首先渲染到传送门上,然后渲染包含传送门图像在内的整个场景。
渲染到传送门,然后渲染到后缓冲区
  在Direct3D中进行渲染时,必须建立渲染目标,这是一个简单的COM对象,它在显存中维护一个位置,供你渲染。在大多数情况下,这个位置就是后缓冲区。下面就是我们的方法:

ID3D11RenderTargetView *backbuffer;    // 全局声明

// 此函数初始化并准备Direct3D以供使用
void InitD3D(HWND hWnd)
{
    // Direct3D初始化
    // ...

    // 获取后缓冲区的地址
    ID3D11Texture2D *pBackBuffer;
    swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);

    // 使用后缓冲区地址来创建渲染目标
    dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer);
    pBackBuffer->Release();

    // 将渲染目标设置为后缓冲区
    devcon->OMSetRenderTargets(1, &backbuffer, NULL);
}

  这里要做三件事:首先,我们确定后缓冲区的地址;其次,我们使用该地址创建一个COM对象来表示渲染目标;最后,我们将该对象设置为活动渲染目标。下面,我们逐一介绍这些新代码:

代码 描述
ID3D11RenderTargetView *backbuffer; 该变量是指向一个COM对象的指针,该对象保存了有关渲染目标的所有信息。我们将渲染到后缓冲区,因此我们将此变量称为“backbuffer”
ID3D11Texture2D *pBackBuffer; 在3D渲染中,纹理是图像的一个别称。ID3D11Texture2D是存储平面图像的对象。就像任何COM对象一样,我们首先定义指针,然后再由一个函数为我们创建该对象。
swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer); GetBuffer()函数的作用是在交换链上找到后缓冲区,并使用它创建pBackBuffer纹理对象。第一个参数是要获取后缓冲区的编号,我们仅在该链上使用一个后缓冲区,它就是#0后缓冲区,因此,第一个参数设置为0;第二个参数是标识ID3D11Texture2DCOM对象的编号,每种类型的COM对象都有其自己的唯一ID,该ID用于获取有关它的信息。要获得此ID,我们必须使用_uuidof()运算符。这个运算符确切的细节并不重要,我们只需要知道这样做可以让GetBuffer()函数知道应该创建哪种类型的对象;第三个参数是一个void指针,这个void填充了ID3D11Texture2D对象的位置。这个指针的类型必须是void,因为我们可能需要放置其他类型的对象
dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer); 这个函数创建渲染目标对象,我们在程序顶部为该对象创建了指针。第一个参数是指向纹理的指针,对于我们的程序而言,它应该是“pBackBuffer”;第二个参数是描述渲染目标的结构,我们不需要为后缓冲区填写此内容,因此设置为NULL;第三个参数是对象指针的地址,因此我们将使用“&backbuffer”。
pBackBuffer->Release(); Release()函数释放所有内存并关闭COM对象使用的所有线程。我们已经使用完了pBackBuffer对象,因此对其进行Release()。注意,这不会破坏后缓冲区,只会关闭我们曾经访问的纹理对象。
devcon->OMSetRenderTargets(1, &backbuffer, NULL); 最后一个函数实际上是设置渲染目标,更确切的说,它设置了多个渲染目标。第一个参数是要设置的渲染目标的数量,通常为1,但某些情况下会更大;第二个参数是指向渲染目标视图对象列表的指针,我们只有一个渲染目标视图对象,所以只需要将渲染目标视图对象的地址传入即可;第三个参数是高级的,后面的课程将会讲到,这里设为NULL就行。

  这部分代码非常重要,确保你理解了所有的内容,因为在Direct3D编程中将多次用到并进行修改。

设置ViewPort

  viewport是一种将像素坐标转换为归一化坐标的方法,下图显示了两者之间的区别:
像素坐标和归一化坐标述
  在左图中,像素坐标从0开始,0坐标位于左上角,并依次增加一个像素。在右图中,无论后缓冲区的大小如何,归一化坐标都是从(-1,-1)增加到(1,1),归一化是指将值调整为等于1。(-1,-1)和(1,1)等于什么由viewport决定。viewport是一种结构体,可让我们设置(-1,-1)和(1,1)在像素坐标中的位置。设置viewport的代码如下所示:

// this function initializes and prepares Direct3D for use
void InitD3D(HWND hWnd)
{
    // Direct3D初始化
    // ...

    // 设置渲染目标
    // ...

    // 设置viewport
    D3D11_VIEWPORT viewport;
    ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));

    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.Width = 800;
    viewport.Height = 600;

    devcon->RSSetViewports(1, &viewport);
}

  这里唯一需要说明的就是最后一行代码。RSSetViewports()是一个激活viewport结构的函数,第一个参数是使用的viewport的数量,第二个参数是指向viewport结构的指针列表的地址。在某些高级情况下,使用多个viewport非常方便,但在这里我们不进行讲述。目前使用1和"&viewport"即可。

渲染框架

  接下来,我们将创建一个呈现单个帧的函数。目前该框架非常简单,仅包含蓝色背景,当然,你可以根据需要更改颜色。这是此函数的代码:

// 这是用于渲染单个帧的函数
void RenderFrame(void)
{
    // 将后缓冲区清除为深蓝色
    devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));

    // 在这里进行后缓冲区上的3D渲染

    // 切换后缓冲区和前缓冲区
    swapchain->Present(0, 0);
}

devcon->ClearRenderTargetView()

  这将使用特定颜色填充渲染目标缓冲区。在我们的场景下,将填充后缓冲区。函数共两个参数,第一个参数是渲染目标对象的地址,我们在这里传入“backbuffer”。第二个参数是用来填充后缓冲区的颜色,为此,我们使用一个称为D3DXCOLOR的简单结构,四个构造函数参数用于构建颜色,前三个时红色、绿色和蓝色,第四个是alpha值(在后缓冲区中没有意义,但却有必要写上)。

swapchain->Present()

  接下来,我们调用Present()函数,此函数实际上是显示后缓冲区上的所有内容,它的工作本质上就是在交换链中执行“交换”,以使后缓冲区成为前缓冲区,它的两个参数都设为0。

强制清理

  在上一节中,我们通过释放创建的每个COM对象来关闭Direct3D。大多是COM对象都有这个Release()函数,并且这些COM对象都必须在完成之后调用Release()函数,渲染目标对象也不例外。

// 这个函数时为了清理Ditrect3D和COM
void CleanD3D()
{
    // 关闭并释放所有现有的COM对象
    swapchain->Release();
    backbuffer->Release();
    dev->Release();
    devcon->Release();
}

最终的程序

  我们已经运行了Direct3D并且在窗口中进行了简单的空白帧渲染。

// include the basic windows header files and the Direct3D header files
#include <windows.h>
#include <windowsx.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>

// include the Direct3D Library file
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "d3dx11.lib")
#pragma comment (lib, "d3dx10.lib")

// global declarations
IDXGISwapChain *swapchain;             // the pointer to the swap chain interface
ID3D11Device *dev;                     // the pointer to our Direct3D device interface
ID3D11DeviceContext *devcon;           // the pointer to our Direct3D device context
ID3D11RenderTargetView *backbuffer;    // the pointer to our back buffer

// function prototypes
void InitD3D(HWND hWnd);    // sets up and initializes Direct3D
void RenderFrame(void);     // renders a single frame
void CleanD3D(void);        // closes Direct3D and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    RECT wr = {0, 0, 800, 600};
    AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass",
                          L"Our First Direct3D Program",
                          WS_OVERLAPPEDWINDOW,
                          300,
                          300,
                          wr.right - wr.left,
                          wr.bottom - wr.top,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    InitD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);

            if(msg.message == WM_QUIT)
                break;
        }

        RenderFrame();
    }

    // clean up DirectX and COM
    CleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}


// this function initializes and prepares Direct3D for use
void InitD3D(HWND hWnd)
{
    // create a struct to hold information about the swap chain
    DXGI_SWAP_CHAIN_DESC scd;

    // clear out the struct for use
    ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

    // fill the swap chain description struct
    scd.BufferCount = 1;                                    // one back buffer
    scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;     // use 32-bit color
    scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;      // how swap chain is to be used
    scd.OutputWindow = hWnd;                                // the window to be used
    scd.SampleDesc.Count = 1;                               // how many multisamples
    scd.SampleDesc.Quality = 0;                             // multisample quality level
    scd.Windowed = TRUE;                                    // windowed/full-screen mode

    // create a device, device context and swap chain using the information in the scd struct
    D3D11CreateDeviceAndSwapChain(NULL,
                                  D3D_DRIVER_TYPE_HARDWARE,
                                  NULL,
                                  NULL,
                                  NULL,
                                  NULL,
                                  D3D11_SDK_VERSION,
                                  &scd,
                                  &swapchain,
                                  &dev,
                                  NULL,
                                  &devcon);


    // get the address of the back buffer
    ID3D11Texture2D *pBackBuffer;
    swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);

    // use the back buffer address to create the render target
    dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer);
    pBackBuffer->Release();

    // set the render target as the back buffer
    devcon->OMSetRenderTargets(1, &backbuffer, NULL);


    // Set the viewport
    D3D11_VIEWPORT viewport;
    ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));

    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.Width = 800;
    viewport.Height = 600;

    devcon->RSSetViewports(1, &viewport);
}


// this is the function used to render a single frame
void RenderFrame(void)
{
    // clear the back buffer to a deep blue
    devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));

    // do 3D rendering on the back buffer here

    // switch the back buffer and the front buffer
    swapchain->Present(0, 0);
}


// this is the function that cleans up Direct3D and COM
void CleanD3D(void)
{
    // close and release all existing COM objects
    swapchain->Release();
    backbuffer->Release();
    dev->Release();
    devcon->Release();
}

  现在我们有了帧渲染,会得到一个蓝色的窗口。
我们的Direct3D程序已经上色

发布了19 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/hjc132/article/details/104963870