第六章
在之前的章节中,我们主要关注了渲染管线的理论和数学方面。在这一章,我们开始关注渲染管线会用到的Direct3D的API接口和方法、定义顶点和像素着色器、提交供渲染管线使用的几何图形。在这章结束,你应该可以画画多种多样的几何图形,带颜色或者网格之类的模式。
学习目标:
1. 发现Direct3D的接口方法,用以定义、存储、绘制几何数据。
2. 学习如何写基本的顶点和像素着色器
3. 学会如何用渲染阶段组织一个渲染管线
4. 学习效果框架可以如何有逻辑的组织起着色器和渲染阶段到渲染管线中,如何利用效果框架作为一个“着色器生成器”。
6.1 顶点和输入安排
回忆一下第五章讲的,一个顶点不仅可以由空间位置信息组成,还可以有其他附带的信息。为了创建一个定制的定点类型,我们首先创建一个用来保存顶点数据的结构体。比如,下边两个就表示了两种不同的顶点格式:
struct Vertex1
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
struct Vertex2
{
XMFLOAT3 Pos;
XMFLOAT3 Normal;
XMFLOAT2 Tex0;
XMFLOAT2 Tex1;
};
一旦定义了一个顶点结构,我们需要给Direct3D提供一个我们的顶点结构的描述,以便让它知道每个分量是做什么的。这个描述是用“输入布局”(ID3D11InputLayout)的形式来提供的。一个“输入布局”是用一个 D3D11_INPUT_ELEMENT_DESC 元素的矩阵来表示的。这个矩阵里边的每一个 D3D11_INPUT_ELEMENT_DESC 元素都关联到顶点结构体的其中一个分量。所以如果如果一个顶点的结构体有2个分量,那么这个 D3D11_INPUT_ELEMENT_DESC 矩阵就有2个元素。我们把这个矩阵叫做“输入布局描述”。
一个 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:语义学的索引。这个的用途如下图,一个顶点结构可能有不止一个贴图索引,我们增加一个索引来区分不同的贴图坐标而不是新加一个语义学名字。没有索引的意味着索引是0。
- Format:DXGI_FORMAT枚举类的一个成员,用来表示这个顶点的元素的格式。这里是几个常用的顶点格式:
DXGI_FORMAT_R32_FLOAT
DXGI_FORMAT_R32G32_FLOAT
DXGI_FORMAT_R32G32B32_FLOAT
DXGI_FORAMT_R32G32B32A32_FLOAT
DXGI_FORMAT_R8_UINT
DXGI_FORMAT_R16G16_SINT
DXGI_FORMAT_R32G32B32_UINT
DXGI_FORMAT_R8G8B8A8_SINT
DXGI_FORMAT_R8G8B8A8_UINT
4. InputSlot:描述这个元素出自于输入槽位的索引。Direct3D支持16个槽位,使用这些你可以组成顶点信息。如果一个顶点由位置和颜色元素组成,你既可以把两个元素都放在单个输入槽位,也可以把他们分开放在不同的输入槽位,Direct3D会从不同的输入槽位把这些元素收集起来。在本书中我们只使用一个槽位。
5. AlignedByteOffset:对于单个的输入槽位,这个是以byte为单位的、从C++定义的顶点结构体的开始处计算的偏移。比如如下这个数据的偏移:
- InputSlotClass: 现阶段就填成D3D11_INPUT_PER_VERTEX_DATA就好。其他的是更晚期的技术里边会用得到。
- InstanceDataStepRate:现阶段填成0就可以。
于是乎之前的两个例子,可以这样填充输入布局描述:
D3D11_INPUT_ELEMENT_DESC dest1 =
{
{“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}
};
…
在定义了一个输入布局描述之后,我们可以生成一个指向ID3D11InputLayout接口的指针,指向一个输入布局。使用的是ID3D11Device::CreateInputLayout方法:
HRESULT ID3D11Device::CreateInputLayout(
const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
UINT NumElements, //size of the array above
const void *pShaderBytecodeWithInputSignature,
SIZE_T BytecodeLength, // size of the array above
ID3D11InputLayout **ppInputLayout); // output pointer
pShaderBytecodeWithInputSignature: 一个指向顶点着色器的输入布局的字节码的指针。
这个需要精细阐述一下。一个顶点着色器使用一个顶点元素的列表作为输入——亦即输入签名。顶点结构里边的元素需要被映射到相应的顶点着色器的输入中去。图6.1有提到过这个。通过在输入布局创建的时候传入一个顶点着色器输入签名的秒数,Direct3D可以验证输入布局和输入签名一致,然后在创建的时候就创建从顶点结构到着色器输入的映射。输入布局可以再不同阶段被重复利用,提供相同的输入签名。
考虑这样一个输入签名和顶点结构:
这会出现错误,VC++ 调试输出窗口会显示这样的:
D3D11:ERROR:ID3D11Device::CreateInputLayout: 提供的输入签名需要包含’NORMAL’这个SemanticName/Index的元素,但是在描述里边没有这样一个匹配的名字。
现在考虑顶点结构和输入签名有相同的元素但是类型不同的情况:
事实上这是合法的,因为Direct3D允许输入寄存器的数据被重新使用,但是会出如下的警告:
D3D11:WARNING: ID3D11Device:CreateInputLayout: 提供的输入签名希望读入一个名字是’POSITION’而且类型是’int32’的元素。但是输入布局描述里边匹配的那个条目不是这个类型。这不是一个错误,因为这个操作被很好的处理了:元素类型决定了将会使用哪种数据转换程序,在进入着色器寄存器之前。与此相关的,着色器输入签名定义了在输入寄存器里边的数据将会如何被翻译,而不改变存储的数据。在顶点着色器中想要把数据重新解释为其他不同的类型也是可以的,所以这个WARNING只是为了防止提示作者如果这个重新解释不是你想要的呢。
翻译存疑:
a pointer to the shader byte-code of the input signature of the vertex shader: 一个指向顶点着色器的输入布局的字节码的指针
input signature: 输入签名
翻译存疑:
input layout:输入布局
input layout description: 输入布局描述
(未完待续)