DX11顶点
在dx11中,顶点由空间位置和各种附加属性组成,D3D允许我们灵活地建立属于自己的顶点格式。要创建一个自定义的顶点格式,必须创建一个包含顶点数据的结构体。例如下面两种不同类型的顶点格式,第一个由位置和颜色组成,另一个由位置、发现和纹理坐标组成。
struct Vertex1
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
struct Vertex2
{
XMFLOAT3 Pos;
XMFLOAT3 Normal;
XMFLOAT2 Tex0;
XMFLOAT2 Tex1;
};
在定义了顶点结构体之后,我们要用输入布局(ID3D11InputLayout)来描述该顶点结构体的分量结构,使D3D知道如何使用每个分量。输入布局是一个D3D11_INPUT_ELEMENT_DESC(输入布局描述:input layout descrption)数组。数组中的每个元素描述了顶点结构体的一个分量。D3D11_INPUT_ELEMENT_DESC结构体定义如下:
typedef struct D3D11_INPUT_ELEMENT_DESC {
LPCSTR SemanticName;
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset
D3D11_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D11_INPUT_ELEMENT_DESC;
1.SemanticName:一个与元素相关的字符串。用于将顶点结构体中的元素映射为顶点着色器参数,如下图。
2.SemanticIndex:附加在语义上的索引值。如上图所示,顶点包含多组纹理信息,但是我们不会分别定义一个SemanticName为"TEXCOORD1"和一个为"TEXCOORD2"的元素。而是定义两个SemanticName都为"TEXCOORD"的元素,但第一个的SemanticIndex为0,第二个为1。
3.Format:一个用于指定元素格式的DXGI_FORMAT枚举类型成员;下面是一些常用的格式:
DXGI_FORMAT_R32_FLOAT // 1D 32-bit float scalar
DXGI_FORMAT_R32G32_FLOAT // 2D 32-bit float vector
DXGI_FORMAT_R32G32B32_FLOAT // 3D 32-bit float vector
DXGI_FORMAT_R32G32B32A32_FLOAT // 4D 32-bit float vector
DXGI_FORMAT_R8_UINT // 1D 8-bit unsigned integer scalar
DXGI_FORMAT_R16G16_SINT // 2D 16-bit signed integer vector
DXGI_FORMAT_R32G32B32_UINT // 3D 32-bit unsigned integer vector
DXGI_FORMAT_R8G8B8A8_SINT // 4D 8-bit signed integer vector
DXGI_FORMAT_R8G8B8A8_UINT // 4D 8-bit unsigned integer vector
4.InputSlot:指定当前元素来自于哪个输入槽(input slot)。Direct3D支持16个输入槽(索引依次为0到15),通过这些输入槽可以向着色器传入顶点数据。例如,当一个顶点由位置元素和颜色元素组成时,我们既可以使用第一个输入槽传送位置元素,使用第二个输入槽传送颜色元素。D3D可以将来自于不同输入槽的元素重新组合为顶点。
5.AlignedByteOffset:对单个输入槽来说,该参数表示从顶点结构体的起始位置到顶点元素的起始位置之间的字节偏移量。例如,在下面的顶点结构体中,元素Pos的字节偏移量为0,因为它的起始位置与顶点结构体的起始位置相同;元素Normal的字节偏移量为......
struct Vertex2
{
XMFLOAT3 Pos; // 0-byte offset
XMFLOAT3 Normal; // 12-byte offset
XMFLOAT2 Tex0; // 24-byte offset
XMFLOAT2 Tex1; // 32-byte offset
};
6.InputSlotClass:目前指定为D3D11_INPUT_PER_VERTEX_DATA;其他选项用于高级实例技术。
7.InstanceDataStepRate:目前指定为0;其他值只用于高级实例技术。
对于前面两个示例顶点结构体Vertex1和Vertex2来说,对应输入布局描述为:
D3D11_INPUT_ELEMENT_DESC desc1[]=
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
D3D11_INPUT_PER_VERTEX_DATA, 0}
};
D3D11_INPUT_ELEMENT_DESC desc2[]=
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D11_INPUT_PER_VERTEX_DATA, 0},
{"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT,0, 12,
D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24,
D3D11_INPUT_PER_VERTEX_DATA, 0}
{"TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 32,
D3D11_INPUT_PER_VERTEX_DATA, 0}
};
指定了输入布局描述之后,我们就可以使用ID3D11Device::CreateInputLayout方法获取一个表示输入布局的ID3D11InputLayou接口的指针:
HRESULT ID3D11Device::CreateInputLayout(
const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
UINT NumElements,
const void *pShaderBytecodeWithInputSignature,
SIZE_T BytecodeLength,
ID3D11InputLayout **ppInputLayout);
1.pInputElemetnDescs:一个用于描述顶点结构体的D3D11_INPUT_ELEMENT_DESC数组。
2.NumElements:D3D11_INPUT_ELEMENT_DESC数组的元素数量。
3.pShaderBytecodeWithInputSignature:指向顶点着色器参数的字节码的指针。
4.BytecodeLength:顶点着色器参数的字节码长度,单位为字节。
5.ppInputLayout:返回创建后的ID3D11InputLayout指针。
进一步解释一下第3个参数的含义。本质上,顶点着色器以一组顶点元素作为它的输入参数——也就是所谓的输入签名(input signature)。自定义顶点结构体中的元素必须被映射为与它们对应的顶点着色参数。通过在创建输入布局时传入顶点着色器签名,Direct3D在创建时就可以验证输入布局是否与输入签名匹配,并建立从顶点结构体到着色器参数之间的映射关系。一个ID3D11InputLayout对象可以在多个参数完全相同的着色器中重复使用。
假设有下列输入参数和顶点结构:
VertexOut VS(float3 Pos:POSITION, float4 Color:COLOR,
float3 Normal: NORMAL){ }
struct Vertex
{
XMFLOAT3 Pos ;
XMFLOAT4 Color;
};
这样就会报错,VC++的调试窗口会显示以下信息:
D3D11:ERROR:ID3D11Device::CreateInputLayout:The provided input
signature expects to read an element with SemanticName/Index:
'NORMAL'/0, but the declaration doesn't provide a matching name.
意思就是说,着色器函数VS有三个输入参数Pos:POSTITION,Color:COLOR,Normal:NORMAL,而自定义顶点结构体中只有两个元素Pos和Color,显然是无法对应上的,就会报错。
考虑另一种情况,假如顶点结构和输入参数与输入元素匹配,但类型不同:
VertexOut VS(int3 Pos:POSITION, float4 Color:COLOR) { }
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
} ;
这样做是可行的D3D允许输入寄存器中的字节被重新编译解释。但是会报warning。具体就不多说了。
下面的代码说明了该如何调用ID3D11Device::CreateInputLayou方法。
ID3D11Effect* mFX;
ID3D11EffectTechnique* mTech;
ID3D11InputLayout* mVertexLayout;
/* ...create the effect... */
mTech = mFX->GetTechniqueByName("Tech");
D3D11_PASS_DESC PassDesc;
mTech->GetPassByIndex(0)->GetDesc(&PassDesc);
HR(md3dDevice->CreateInputLayout(vertexDesc, 4,
PassDesc.pIAInputSignature, PassDesc.IAInputSignatureSize,
&mVertexLayout));
创建了输入布局对象之后,它不会自动绑定到设备上。我们必须调用下面的语句来实现绑定:
ID3D11InputLayout* mVertexLayout;
/* ...create the input layout... */
md3dImmediateContext->IASetInputLayout(mVertexLayout);
如果你打算用一个输入布局来绘制一些物体,然后再使用另一个的布局来绘制另一些物体,那你必须按照下面的形式来组织代码:
md3dImmediateContext->IASetInputLayout(mVertexLayout1);
/* ...draw objects using input layout 1... */
md3dImmediateContext->IASetInputLayout(mVertexLayout2);
/* ...draw objects using input layout 2... */
换句话说,当一个ID3D11InputLayout对象被绑定到设备上时,如果不去改变它,那么它会始终驻留在那里。