在第一个教程中,我们学习了创建一个最小Direct3D 11应用程序所需的元素,每个Direct3D 11应用程序必须包含这些元素才能正常工作,这些元素包括创建一个窗口和设备对象,然后才能在窗口中显示颜色。
(SDK root)\Samples\C++\Direct3D11\Tutorials\Tutorial01。
第一个步骤中的创建窗口和消息循环在Direct3D 9、Direct3D 10、Direct3D 11都是相同的,可参见Direct3D 10教程0:Win32编程基础理解这个过程。当显示了一个窗口后,下面继续创建一个Direct3D 11设备,这个设备用于绘制3D场景。首先必须创建三个对象:一个设备、一个立即执行上下文(immediate context)和一个交换链(Swap Chain),立即执行上下文对象是Direct3D 11中新添加的。
在Direct3D 10中,设备同时用来绘制和资源的创建。在Direct3D 11中,立即执行上下文用于将内容绘制到缓存,而设备用于创建资源。
当显示了一个窗口后,下面继续创建一个Direct3D 11设备,这个设备用于绘制3D场景。首先必须创建两个对象:一个设备和一个交换链(Swap Chain)。
设备对象用于将内容绘制在一个缓冲中,设备还包含创建资源的方法。
交换链即表示对缓冲的操作,这些缓冲就是设备绘制的和显示在屏幕上的内容。交换链包含两个或两个以上的缓冲,主要是前缓冲和后备缓冲,它们就是设备绘制形成的纹理,用于显示在屏幕上。前缓冲(front buffer)就是当前显示在屏幕上的内容,这个缓冲是只读的,无法修改。后备缓冲(back buffer)是设备将要绘制的渲染目标,一旦它完成了绘制操作,交换链就会通过交换前缓冲和后备缓冲,将后备缓冲的内容显示在屏幕上,此时后备缓冲就变成了前缓冲。
要创建交换链,我们需要设置一个DXGI_SWAPCHAIN_DESC结构体说明将要创建的交换链。此结构体的几个字段需要说明一下:BackBufferUsage标志告诉程序我们使用后备缓冲的方式。本例中我们想绘制到后备缓冲,所以将BackBufferUsage设置为DXGI_USAGE_RENDER_TARGET_OUTPUT。OutputWindow字段表示交换链将使用的窗口,在这个窗口上我们显示图像。SampleDesc用来开启多重采样(multi-sampling),因为这个教程不使用多重采样,SampleDesc的Count设置为1,Quality设置为0,这样就禁用了多重采样。
设置好这个描述结构体后,我们就可以调用D3D11CreateDeviceAndSwapChaing方法创建设备和交换链。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory( &sd,
sizeof
( sd ) );
sd.BufferCount = 1;
sd.BufferDesc.Width = width;
sd.BufferDesc.Height = height;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
for
(
UINT
driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
{
g_driverType = driverTypes[driverTypeIndex];
hr = D3D11CreateDeviceAndSwapChain( NULL, g_driverType, NULL, createDeviceFlags,
D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice,
NULL, &g_pImmediateContext);
if
( SUCCEEDED( hr ) )
break
;
}
if
( FAILED( hr ) )
return
hr;
|
下一步需要创建一个渲染目标视图(render target view)。渲染目标视图是Direct3D 11中一种资源视图(resource view)。资源视图可以让一个资源绑定到图形管线的某个阶段。可以将资源视图看成C中的类型转换,C中的一块原始内存(raw memory)可以被转换为任意数据结构,我们可以将一块内存转换为整数数组,浮点数数组,结构,结构数组等。如果我们不知道原始内存的类型,那么它对我们来说用处不大。Direct3D 11的资源视图工作原理类似,例如一张2D纹理就类似于一块原始内存,就是一种原始基础资源,有了这个原始资源,我们就可以创建不同的资源视图将这个纹理以不同的格式绑定到图形管线的不同阶段,而不同的格式可以是要绘制的渲染目标,接收深度信息的深度模板缓冲,或者也可以是一个纹理资源。C中的类型转换可以以不同方式使用一块内存,而在Direct3D 11中是资源视图进行类似的操作。
因为我们需要将交换链中的后备缓冲绑定为一个渲染目标,所以需要创建一个渲染目标视图,这样Direct3D 11就可以在其上进行绘制了。我们首先调用GetBuffer() 方法获取后备缓冲对象。我们可以使用一个D3D11_RENDERTARGETVIEW_DESC结构体表示要创建的渲染目标视图,这个结构体通常是CreateRenderTargetView方法的第二个参数。但是,在本教程中,默认的渲染目标视图就能满足需要,所以第二个参数为NULL表示使用默认的渲染目标视图。创建了渲染目标视图后,我们就可以调用OMSetRenderTargets()方法将它绑定到图形管线,这样管线的绘制输出被写到了后备缓冲中。创建并设置渲染目标视图的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
// Create a render target view
ID3D11Texture2D* pBackBuffer;
hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (
LPVOID
* )&pBackBuffer );
if
( FAILED( hr ) )
return
hr;
hr = g_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &g_pRenderTargetView );
pBackBuffer->Release();
if
( FAILED( hr ) )
return
hr;
g_pd3dDevice->OMSetRenderTargets( 1, &g_pRenderTargetView, NULL );
|
最后,我们需要初始化视口(viewport)。视口剪裁空间坐标(X和Y的范围从-1到1,Z的范围从0到1)映射到渲染空间(有时又称为像素空间)。在Direct3D 9中,如果程序没有设置视口,会创建一个与渲染目标相同大小的默认视口。在Direct3D 11中,没有默认视口,我们必须事先设定。因为我们想将整个渲染目标输出,所以设置左上点为(0, 0),宽度和高度与渲染目标相同。代码如下:
1
2
3
4
5
6
7
8
9
|
// 设置视口
D3D11_VIEWPORT vp;
vp.Width = (
FLOAT
)width;
vp.Height = (
FLOAT
)height;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
g_pImmediateContext->RSSetViewports( 1, &vp );
|
创建了窗口和Direct3D 11设备后,就做好了绘制的准备。但是在处理消息循环环节还有一个问题:我们使用的是GetMessage()获取消息,使用GetMessage()带来的问题是当没有消息可返回给应用程序时,GetMessage()会将应用程序置于“睡眠”状态。这样会导致在程序进行绘制时,当消息队列为空时,应用程序会处于等待状态。我们可以使用PeekMessage()代替GetMessage()来解决这个问题。PeekMessage()像GetMessage()一样可以接收消息,当没有消息处于等待时,PeekMessage()会立即返回给应用程序一个FALSE值而不是处于休眠状态。这样我们就可以利用这段时间进行绘制的工作。修改过的消息循环的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// Main message loop
MSG msg = {0};
while
( WM_QUIT != msg.message )
{
if
( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
Render();
}
}
|
绘制过程是在Render()方法中进行的。在本教程中,我们想让过程尽量简单,所以就用单色填充屏幕。在Direct3D 10中,要将一种颜色填充渲染目标的简单方法就是使用设备的ClearRenderTargetView()方法。我们首先定义一个包含四个浮点数的数组设置要显示的颜色,然后将这个数组传递到ClearRenderTargetView()方法中。本例中使用的是蓝色。填充了后备缓冲后,就可以调用交换链的Present()方法完成绘制。Present()负责将后备缓冲的内容显示在屏幕上。Render()方法的代码如下:
1
2
3
4
5
6
7
|
void
Render()
{
// Just clear the backbuffer
float
ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f };
//red,green,blue,alpha
g_pImmediateContext->ClearRenderTargetView( g_pRenderTargetView, ClearColor );
g_pSwapChain->Present( 0, 0 );
}
|