Lesson 8:渲染帧
课程一览
Direct3D一旦初始化就该做一些实际的渲染了。渲染本身很简单,但是需要一点准备工作。这节课我么会配置好每件事情以备一遍又一遍地渲染一个空白帧。
相关设置很简单。有两个东西要做。首先,我们需要告诉GPU在内存的哪里创建最终图像(对与我们,它是个空白缓冲)。第二,我们需要告诉GPU我们需要在后台缓冲的哪里绘图。
设置渲染目标
让我们从渲染到哪里开始。逻辑上你会说,“废话,当然是后台缓冲!”。但是事实上Direct3D此时并不知道。可能你不希望立刻渲染到后台缓冲。例如很多游戏是渲染到模型表面再渲染模型到后台缓冲。这个技术可以产生很多特效。如果你玩过Portal(传送门)这个游戏,你会看到一个这方面的例子。在Portal中,游戏引擎首先渲染传送门,然后以传送门图像渲染完整场景。
当在Direct3D中渲染的时候,你必须建立渲染目标。这是个简单的COM对象,它包含了你要渲染到显存的位置。大多数情况下这个位置就是后台缓冲。
下面是我们的做法:
ID3D11RenderTargetView *backbuffer; // global declaration
// this function initializes and prepares Direct3D for use
void InitD3D(HWND hWnd)
{
// Direct3D initialization
// ...
// 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);
}
这里我们做了3件事情。第一,确定后台缓冲的地址。第二,创建COM对象代表渲染目标。第三,设置该对象为活动渲染目标。
ID3D11RenderTargetView *backbuffer;
这个变量是个指向包含有渲染目标信息的对象的指针。
ID3D11Texture2D *pBackBuffer;
在3D渲染中,纹理是图片的另一个名字。一个ID3D11Texture2D是一个存储了平面图像的对象。像任何COM对象一样,我们首先定义指针,然后用函数创建对象。
swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
你肯定不相信我,但是这个命令真的好简单。GetBuffer()函数做的就是找到交换链中的后台缓冲并用它创建pBackBuffer纹理对象。
第一个参数是获得的后台缓冲个数。我们仅用一个后台缓冲#0。所以第一个参数值是0.
第二个参数是一个标识ID3D11Texture2D COM对象的数字。每种类型的COM对象都有它自己的ID,用来获得关于对象的信息。为了得到这个ID,我们必须使用_uuidof运算符。我们这样做的原因是GetBuffer()函数知道它应该创建什么类型的对象。
第三个参数,void*是个指向没有特定类型的指针,可以指向任何东西。它可以转换成任意其它的指针类型。
dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer);
这个函数创建渲染目标对象。
第一个参数是个指向纹理的指针。在我们的程序中它是‘pBacjBuffer’。
第二个参数是一个描述渲染目标的结构。我们不需要填充它,给个NULL就好。
第三个参数是对象指针的地址。指针名是‘backbuffer’,所以我么用‘&backbuffer’。
pBackBuffer->Release();
这个Release()函数释放所有的内存并关闭使用COM对象的线程。注意它并不销毁后台缓冲,它仅关闭我们曾访问的纹理对象。
devcon->OMSetRenderTargets(1, &backbuffer, NULL);
上面的函数实际上用来设置渲染目标。更确切的说它设置多渲染目标。
第一个参数是要设置的渲染目标数量。通常是1。
第二个参数是个指针指向渲染目标视图对象。我们只用一个所以仅是渲染目标对象(&backbuffer)的地址。
第三个参数很高级,我们稍后再讨论,现在先设置为NULL。
这部分代码复杂而重要,确定你已经弄清楚了,以便后边的Direct3D编程中多次对它进行使用和修改。
设置视口
一个视口是把像素坐标转换为标准化坐标。下面的图显示了两者的不同。
单词normalized含义是适配一个值直到它等价1.
-1,-1和1,1实际等价于什么由视口决定。视口是使我们在像素坐标中在哪里设置-1,-1和1,1的结构。
设置视口的代码如下:
// this function initializes and prepares Direct3D for use
void InitD3D(HWND hWnd)
{
// Direct3D initialization
// ...
// Set the render target
// ...
// 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);
}
需要解释的可能只有最后一行。
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);
}
Nice!让我们讨论一下它们做了什么。
devcon->ClearRenderTargetView()
这个指令用一个特定的颜色填充渲染目标缓冲。在我们的程序中目标是后台缓冲。它很简单,有两个参数。
第一个是渲染目标对象地址,这里是‘backbuffer’
第二个参数是你想用来填充后台缓冲的颜色。为了做到这点,我们用一个简单的叫做D3DXCOLOR结构。四个构造函数参数用于构建的颜色(红色、绿色、蓝色值和透明值)。
swapchain->Present()
这个函数出现在所有的东西都绘制到后台缓冲后。它的工作主要是在交换链中执行‘交换’,这样后台缓冲就变成了前台缓冲。
这两个参数都设置为0,因为在课程中并不会用到。
Obligatory Cleanup
上节课我们通过释放每个被创建的COM对象关闭Direct3D。大多数COM对象都有Release()函数,在我们完成后就要调用它们。渲染目标对象也不例外。
// this is the function that cleans up Direct3D and COM
void CleanD3D()
{
// close and release all existing COM objects
swapchain->Release();
backbuffer->Release();
dev->Release();
devcon->Release();
}
完成项目
极好的!我们已经使Direct3D运行了并得到一个简单的渲染到窗口的空白帧。我们进步很大。
下面是Main.cpp代码,我们会得到一个蓝色窗口!
Ready...Set...
问题
1.渲染目标是啥?什么函数可以创建渲染目标?
2.哪两个函数渲染了空白的蓝色帧?这些函数具体做了些什么?
3.什么是标准化坐标(normalized coordinates)?
练习
1.看你能在程序运行的时候改变颜色。使它从蓝色淡出为黑色。
2.改变视口看发生了什么。