三角形是由三个点定义的,也叫顶点vertices。为了让GPU去渲染出一个三角形,我们必须告诉它三角形三个定点的位置。那我们如何将这些信息传给GPU呢?在Direct3D 11中,顶点信息例如位置是被存储在缓冲资源中,也叫顶点缓冲区。我们必须创建一个足够大的顶点缓冲区,再将顶点位置填进去。在Direct3D 11中,当创建一个缓冲资源的时候应用必须知道缓冲区的字节大小。我们知道缓冲区必须能够容纳三个顶点,但是每个顶点占用多少字节呢?为了理解这个,我们需要知道顶点布局(vertex layout)。
顶点有位置,通常也有其他属性,例如各种状态下的颜色,纹理贴图等等。顶点布局就是说明这些属性在内存中是怎样存在的:每种属性使用什么数据类型,占多少字节,在内存中的顺序。因为属性一般有不同的类型,就像C语言中的结构体中的字段,顶点通常也是由结构体来描述,顶点的大小也就可以用结构体的大小来表示。
下面我们用XMFLOAT3来描述顶点结构,这个类型是由三个浮点数组成的,通常在3D中来表示位置:
struct SimpleVertex
{
XMFLOAT3 Pos; // Position
};
我们现在有了可以表示顶点的结构,这个结构维护了系统内存中存储的顶点信息。然而,当我们将包含顶点的顶点缓冲区传给GPU的时候,我们只是传给它一块内存区域,GPU一定也要知道顶点布局才能从缓冲区中抽取出正确的属性信息,为了达到这个目的,就需要使用输入布局(input layout)。
在Direct3D 11中,输入布局是一个Direct3D对象,它以一种方式使得顶点集合被GPU所理解。每个顶点的属性都能够被D3D11_INPUT_ELEMENT_DESC 结构描述。每个应用可以定义一个或多个D3D11_INPUT_ELEMENT_DESC ,使用这些结构就能创建输入布局,将顶点集合表示成一个整体,现在就来详细的看一下这个结构中的字段含义:
字段 | 含义 |
---|---|
SemanticName | 字符串,语义名字,我们可以随便命名,没有大小写之分,举个例子,对于顶点位置好的语义名字就是POSITION |
SemanticIndex | 对语义名字的补充,一个顶点可能有多个相同性质的属性,比如两套纹理坐标,两套颜色,使用语义索引,我们就可以共用一个语义名字“Color”,带上不同的语义索引0和1,而不必使用“COLOR0”和“COLOR1”. |
Format | 定义了这个元素的数据类型,例如DXGI_FORMAT_R32G32B32_FLOAT 有12字节长,DXGI_FORMAT_R16G16B16A16_UINT 有8个字节长. |
InputSlot | 正如之前所说,Direct3D 11应用中通过顶点缓冲区将顶点数据传给GPU,而且多个顶点缓冲区可以被GPU同时抓取数据,准确的说是16个,每个顶点缓冲区都是0~15的输入信号字,这个字段就是告诉GPU,它应该抓取哪个顶点缓冲区。 |
AlignedByteOffset | GPU在内存中开始抓取数据的位置。 |
InputSlotClass | 通常使用 D3D11_INPUT_PER_VERTEX_DATA,详细说明以后再说。 |
InstanceDataStepRate | 这个字段用于 instancing. 因为我们没有使用 instancing, 先不用理解这个字段的含义,必须初始化为0. |
现在就能定义D3D11_INPUT_ELEMENT_DESC 数组,并创建输入布局:
// Define the input layout
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
UINT numElements = ARRAYSIZE(layout);
我们使用从方法D3DX11CompileFromFile 返回的ID3DBlob 对象去检索表示顶点着色器输入特征的二进制数据。一旦我们有了这个数据,就能调用ID3D11Device::CreateInputLayout()方法创建顶点布局对象,然后ID3D11DeviceContext::IASetInputLayout()方法将其设置为激活的顶点布局,代码如下:
// Create the input layout
if( FAILED( g_pd3dDevice->CreateInputLayout( layout, numElements, pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), &g_pVertexLayout ) ) )
return FALSE;
// Set the input layout
g_pImmediateContext->IASetInputLayout( g_pVertexLayout );
在初始化过程中我们也将需要做的一件事就是创建包含顶点数据的缓冲区。我们将使用D3D11_BUFFER_DESC 和D3D11_SUBRESOURCE_DATA,然后调用ID3D11Device::CreateBuffer()方法,D3D11_BUFFER_DESC 描述了创建的顶点缓冲对象,D3D11_SUBRESOURCE_DATA 表示创建中实际的数据。然后我们就可以通过ID3D11DeviceContext::IASetVertexBuffers()方法将它和设备绑定,代码如下:
// Create vertex buffer
SimpleVertex vertices[] =
{
XMFLOAT3( 0.0f, 0.5f, 0.5f ),
XMFLOAT3( 0.5f, -0.5f, 0.5f ),
XMFLOAT3( -0.5f, -0.5f, 0.5f ),
};
D3D11_BUFFER_DESC bd;
ZeroMemory( &bd, sizeof(bd) );
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = sizeof( SimpleVertex ) * 3;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;
bd.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory( &InitData, sizeof(InitData) );
InitData.pSysMem = vertices;
if( FAILED( g_pd3dDevice->CreateBuffer( &bd, &InitData, &g_pVertexBuffer ) ) )
return FALSE;
// Set vertex buffer
UINT stride = sizeof( SimpleVertex );
UINT offset = 0;
g_pImmediateContext->IASetVertexBuffers( 0, 1, &g_pVertexBuffer, &stride, &offset );
最后一件事需要做的就是调用ID3D11DeviceContext::Draw(),命令GPU使用当前的顶点缓冲区、当前的顶点布局,当前的拓扑结构(渲染图形顶点顺序)区渲染,第一个参数表示顶点数量,第二个参数表示要绘制的第一个顶点的索引。
// Render a triangle
g_pImmediateContext->VSSetShader( g_pVertexShader, NULL, 0 );
g_pImmediateContext->PSSetShader( g_pPixelShader, NULL, 0 );
g_pImmediateContext->Draw( 3, 0 );