Directx11入门之第六章 使用Direct3D绘制

Directx11入门之第六章 使用Direct3D绘制

@(读书笔记)[读书笔记, 技术交流]

  • Directx11入门之第六章 使用Direct3D绘制
    • 6.1 顶点和输入布局(VERTICES AND INPUT LAYOUTS)
    • 6.2 顶点缓冲(VERTEX BUFFERS)
      • 6.3 目录和索引buffer(INDICES AND INDEX BUFFERS)


前一章我们着重讲了渲染管线的概念和一
些数学技巧。本章叙述配置渲染管线的Direct3D API和方法,如何定义顶点着色器(vertex shader)和像素着色器(pixel shader),如何将几何图形提交给渲染管线去绘制。最后你将学会如何绘制各式各种的几何图形。

本章要点:
1. 介绍如何使用Direct3D的接口定义,存储和绘制几何图形数据。
2. 学习如何写一个基础的顶点着色器和像素着色器。
3. 学习如何配置渲染管线的各阶段。
4. 学习着色器和渲染的阶段如何使用特效框架完成一些渲染技术,如何使用特效框架产生着色器。

6.1 顶点和输入布局(VERTICES AND INPUT LAYOUTS)

如 $5.5.1 中所说,顶点数据除了包含空间坐标外还可以包含其他的附加信息(如顶点的颜色等)。为了创建一个通用的顶点格式,我们需要定义一个结构体,根据具体所需的附加信息我们举两个例子:

struct Vertex1
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};
struct Vertex2
{
    XMFLOAT3 Pos;
    XMFLOAT3 Normal;
    XMFLOAT2 Tex0;
    XMFLOAT2 Tex1;
};

接下来我们需要把我们定义的结构体传给Direct3D。通过输入布局(input layout)ID3D11InputLayout来完成传输。输入布局是一个一维数组,数据类型是D3D11_INPUT_ELEMENT_DESCD3D11_INPUT_ELEMENT_DESC数据类型是一个顶点结构体(vertex structure)。因此若顶点结构体有两种,那么相应的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:与元素关联的字符串,用来做元素的映射。
    1. SemanticIndex:关联的下标。有时我们需要确定一个贴图纹理的顶点坐标,使用下标比使用字符串映射更合适。
    2. 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
  1. InputSlot:顶点数据的输入槽位。Direct3D支持16个输入槽位(下标范围为 [0,15] [ 0 , 15 ] )。若你想要输入一个带颜色的顶点数据,你可以通过一个槽位输入;或者把坐标用一个槽位输入,颜色用另一个槽位输入。本书中只会用到一个槽位输入,不过在练习2中你可以练习两个槽位输入。
  2. AlignedByteOffset:字节偏移。
struct Vertex2
{
    XMFLOAT3 Pos; // 0-byte offset
    XMFLOAT3 Normal; // 12-byte offset
    XMFLOAT2 Tex0; // 24-byte offset
    XMFLOAT2 Tex1; // 32-byte offset
};
  1. InputSlotClass:当前使用的数据类型是D3D11_INPUT_PER_VERTEX_DATA,其他类型仅会用在实现一些先进的技术。
  2. InstanceDataStepRate:当前只需为0,其他的值仅会用在实现一些先进的技术。对于前两个顶点结构体的例子,相应的输入布局是:
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方法获得输入布局的指针ID3D11InputLayout

HRESULT ID3D11Device::CreateInputLayout(
    const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
    UINT NumElements,
    const void *pShaderBytecodeWithInputSignature,
    SIZE_T BytecodeLength,
    ID3D11InputLayout **ppInputLayout);

解释一下上述的参数:
- pInputElementDescs:用以表示顶点结构体D3D11_INPUT_ELEMENT_DESC数组。
- NumElements:数组长度。
- pShaderBytecodeWithInputSignature顶点着色器输入信号的字节代码的指针。
- BytecodeLength顶点着色器的信号数据的长度。
- ppInputLayout:函数返回值,返回被创建出来的输入布局的指针。

解释一下上述提到的几个名词。顶点着色器需要一组顶点数据作为输入参数,把这种输入参数称之为输入信号(input signature)。自定义顶点结构体中的元素需要被映射到顶点着色器的相应输入位置。在Direct3D创建输入布局(input layout)时会检查顶点结构体顶点着色器的映射是否正确。若使用相同的输入信号可以复用输入布局
以下展示一个输入信号顶点结构体

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.

这是由于输入信号顶点结构体不匹配导致,接下来我们调整代码以达到匹配:

VertexOut VS(int3 Pos : POSITION, float4 Color : COLOR) { }
struct Vertex
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};

此时代码已经合法,Direct3D允许在输入寄存器(input registers)中的bits可以被重新解释。然而VC++仍会出现一个警告:

D3D11: WARNING: ID3D11Device::CreateInputLayout: The provided input signature expects to read an element
with SemanticName/Index: ‘POSITION’/0 and component(s) of the type ‘int32’. However, the matching entry in the
Input Layout declaration, element[0], specifies mismatched format: ‘R32G32B32_FLOAT’. This is not an error, since
behavior is well defined: The element format determines what data conversion algorithm gets applied before it shows
up in a shader register. Independently, the shader input signature defines how the shader will interpret the data that
has been placed in its input registers, with no change in the bits stored. It is valid for the application to reinterpret
data as a different type once it is in the vertex shader, so this warning is issued just in case reinterpretation was not
intended by the author.

下面我们给出一个调ID3D11Device::CreateInputLayout函数的代码。注意代码中包含了一些我们没有提到的点(如ID3D11Effect),一个特效包含一个或多个入口,而顶点着色器关联着每个入口。因此我们可以通过D3D11_PASS_DESC获取输入信号顶点着色器

ID3DX11Effect* mFX;
ID3DX11EffectTechnique* mTech;
ID3D11InputLayout* mInputLayout;
/* ...create the effect... */
mTech = mFX->GetTechniqueByName("Tech");
D3DX11_PASS_DESC passDesc;
mTech->GetPassByIndex(0)->GetDesc(&passDesc);
HR(md3dDevice->CreateInputLayout(vertexDesc, 4, passDesc.
    pIAInputSignature, passDesc.IAInputSignatureSize, &mInputLayout));

在创建输入布局后,最后还需要利用下面代码绑定设备。

ID3D11InputLayout* mInputLayout;
/* ...create the input layout... */
md3dImmediateContext->IASetInputLayout(mInputLayout);

若你利用一个输入布局绘制了物体,又用其他布局绘制了物体,那你需要使用这样的代码结构:

md3dImmediateContext->IASetInputLayout(mInputLayout1);
/* ...draw objects using input layout 1... */
md3dImmediateContext->IASetInputLayout(mInputLayout2);
/* ...draw objects using input layout 2... *

6.2 顶点缓冲(VERTEX BUFFERS)

为了使GPU能存取顶点数据,顶点数据需要放置在一些特定的资源中,称之为缓冲(buffer),对应的接口是ID3D11Buffer
一个存储顶点的buffer被称作顶点buffer。Direct3D的buffers不止可以存储数据,还能描述如何存取数据,如何将数据绑定到渲染管线。依照以下步骤创建一个顶点buffer:
1. 填写结构体D3D11_BUFFER_DESC
2. 填写结构体D3D11_SUBRESOURCE_DATA,用来指定buffer内容中的数据类型。
3. 调用ID3D11Device::CreateBuffer创建buffer。
D3D11_BUFFER_DESC数据结构定义如下:

typedef struct D3D11_BUFFER_DESC {
    UINT ByteWidth;
    D3D11_USAGE Usage;
    UINT BindFlags;
    UINT CPUAccessFlags;
    UINT MiscFlags;
    UINT StructureByteStride;
} D3D11_BUFFER_DESC;
  1. ByteWidth:我们想要创建顶点buffer的字节宽度。
  2. Usage:枚举型D3D11_USAGE是指定buffer的访问方式。共有4种枚举型:
    Ⅰ. D3D11_USAGE_DEFAULT:GPU可以读写资源。CPU在使用ID3D11DeviceContext::Map接口时不能读写资源。不过若使用ID3D11DeviceContext::UpdateSubresource接口则可以读写资源。
    Ⅱ. D3D11_USAGE_IMMUTABLE:GPU只读资源,CPU除了初始化资源外也不能修改资源,同时无法读取资源。我们也不能映射或更新资源。
    Ⅲ. D3D11_USAGE_DYNAMIC:若CPU需要频繁更新资源则可以使用此数据类型,资源可以被CPU修改也可以被GPU读取。CPU通过ID3D11DeviceContext::Map映射修改资源。此方式消耗较大因此尽量避免使用。
    Ⅳ. D3D11_USAGE_STAGING:此方式可以让CPU读取资源副本(当然此资源要能支持从图像存储内存(video memory)复制到系统存储内存(system memory))。这种复制操作比较缓慢,应尽量避免。复制操作的函数是ID3D11DeviceContext::CopyResourceID3D11DeviceContext::CopySubresourceRegio。$12.3.5是CopyResource的一个例子。

  3. BindFlags:对于顶点buffer,这里填D3D11_BIND_VERTEX_BUFFER

  4. CPUAccessFlags:指定CPU访问buffer的方式。填 0 0 表示CPU在创建buffer后无法读写buffer。若CPU确实需要修改buffer则填D3D11_CPU_ACCESS_WRITE,注意此时buffer的Usage必须为D3D11_USAGE_DYNAMICD3D11_USAGE_STAGING;若CPU需要读取buffer则填D3D11_CPU_ACCESS_READ,注意此时buffer的Usage必须为D3D11_USAGE_STAGING。CPU修改资源速度很快,但是从system memory传输到video memory是一个缓慢的操作,因此推荐让GPU直接操作video memory中的资源。
  5. MiscFlags:我们暂时不需要这个混合标记,填0即可。
  6. StructureByteStride:存储在structured buffer中的单个元素的字节大小。这个buffer是只读的,绑定到这个buffer的资源必须被定义为DXGI_FORMAT_UNKNOWN。不过若创建其他buffer只需填0。

D3D11_SUBRESOURCE_DATA结构体定义如下:

typedef struct D3D11_SUBRESOURCE_DATA {
    const void *pSysMem;
    UINT SysMemPitch;
    UINT SysMemSlicePitch;
} D3D11_SUBRESOURCE_DATA;
  1. pSysMem:指向system memory的指针,其中需要包含创建顶点buffer的数据。若buffer可以存储n个顶点数据,则此指针需包含至少n个顶点数据。
  2. SysMemPitch:顶点buffer中不使用。
  3. SysMemSlicePitch:顶点buffer中不使用。
    下面给出创建一个不可修改的顶点buffer的代码,使用在原点的立方体的8个坐标点初始化buffer。同时我们给每个点加上不同颜色。
// Colors namespace defined in d3dUtil.h.
//
// #define XMGLOBALCONST extern CONST __declspec(selectany)
// 1. extern so there is only one copy of the variable, and not a
// separate private copy in each .obj.
// 2. __declspec(selectany) so that the compiler does not complain
// about multiple definitions in a .cpp file (it can pick anyone
// and discard the rest because they are constant--all the same).
namespace Colors
{
    XMGLOBALCONST XMVECTORF32 White = { 1.0f, 1.0f, 1.0f, 1.0f };
    XMGLOBALCONST XMVECTORF32 Black = { 0.0f, 0.0f, 0.0f, 1.0f };
    XMGLOBALCONST XMVECTORF32 Red = { 1.0f, 0.0f, 0.0f, 1.0f };
    XMGLOBALCONST XMVECTORF32 Green = { 0.0f, 1.0f, 0.0f, 1.0f };
    XMGLOBALCONST XMVECTORF32 Blue = { 0.0f, 0.0f, 1.0f, 1.0f };
    XMGLOBALCONST XMVECTORF32 Yellow = { 1.0f, 1.0f, 0.0f, 1.0f };
    XMGLOBALCONST XMVECTORF32 Cyan = { 0.0f, 1.0f, 1.0f, 1.0f };
    XMGLOBALCONST XMVECTORF32 Magenta = { 1.0f, 0.0f, 1.0f, 1.0f };
}
// define raw vertex data
Vertex vertices[] =
{
    { XMFLOAT3(-1.0f, -1.0f, -1.0f), (const float*)&Colors::White },
    { XMFLOAT3(-1.0f, +1.0f, -1.0f), (const float*)&Colors::Black },
    { XMFLOAT3(+1.0f, +1.0f, -1.0f), (const float*)&Colors::Red },
    { XMFLOAT3(+1.0f, -1.0f, -1.0f), (const float*)&Colors::Green },
    { XMFLOAT3(-1.0f, -1.0f, +1.0f), (const float*)&Colors::Blue },
    { XMFLOAT3(-1.0f, +1.0f, +1.0f), (const float*)&Colors::Yellow },
    { XMFLOAT3(+1.0f, +1.0f, +1.0f), (const float*)&Colors::Cyan },
    { XMFLOAT3(+1.0f, -1.0f, +1.0f), (const float*)&Colors::Magenta }
};
D3D11_BUFFER_DESC vbd;
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof(Vertex) * 8;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
vbd.MiscFlags = 0;
vbd.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA vinitData;
vinitData.pSysMem = vertices;
ID3D11Buffer* mVB;
HR(md3dDevice->CreateBuffer(
    &vbd, // description of buffer to create
    &vinitData, // data to initialize buffer with
    &mVB)); // return the created buffer

那么顶点数据定义如下:

struct Vertex
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};

创建完顶点buffer后,还需将其绑定到设备的输入插槽,之后就能将顶点传给渲染管线。通过以下方法完成:

void ID3D11DeviceContext::IASetVertexBuffers(
    UINT StartSlot,
    UINT NumBuffers,
    ID3D11Buffer *const *ppVertexBuffers,
    const UINT *pStrides,
    const UINT *pOffsets);
  1. StartSlot:用来绑定顶点buffer的第一个输入插槽,取值为 [0,15] [ 0 , 15 ]
  2. NumBuffers:绑定到输入插槽的顶点buffer数量,一个输入插槽绑定一个顶点buffer。若绑定的第一个输入插槽下标是 k k ,有 n n 顶点buffer需要绑定,那么绑定的输入插槽有 Ik,Ik+1,,Ik+n1 I k , I k + 1 , · · · , I k + n − 1
  3. ppVertexBuffers:指向顶点buffer的第一个元素的指针。
  4. pStrides:指向步长(strides)数组第一个元素的指针(第 i i 个步长对应第 i i 个顶点buffer)。步长的大小和一个顶点buffer的大小相同。
  5. pOffsets:指向偏移(offsets)数组第一个元素的指针(第 i i 个偏移对应第 i i 个顶点buffer)strong text。你可以利用此来跳过前几个顶点buffer。

IASetVertexBuffers方法是用来设置多个顶点buffer放在多个输入插槽。不过多数时候我们只使用一个输入插槽。本章结尾会给出使用两个输入插槽的练习题。
一个顶点buffer将一直绑定在同一个输入插槽(除非你修改此buffer的绑定插槽),若你希望同时使用多个顶点buffer,可以如下操作:

ID3D11Buffer* mVB1; // stores vertices of type Vertex1
ID3D11Buffer* mVB2; // stores vertices of type Vertex2
                    /*...Create the vertex buffers...*/
UINT stride = sizeof(Vertex1);
UINT offset = 0;
md3dImmediateContext->IASetVertexBuffers(0, 1, &mVB1, &stride, &offset);
/* ...draw objects using vertex buffer 1... */
stride = sizeof(Vertex2);
offset = 0;
md3dImmediateContext->IASetVertexBuffers(0, 1, &mVB2, &stride, &offset);
/* ...draw objects using vertex buffer 2... */

我们设置顶点buffer并绑定到输入插槽,但不会绘制这些顶点,只是能够让渲染管线读取到这些顶点,最终渲染需要调用ID3D11DeviceContext::Draw方法完成:

void ID3D11DeviceContext::Draw(UINT VertexCount, UINT StartVertexLocation);

上述代码中两个参数含义如图:

6.3 目录和索引buffer(INDICES AND INDEX BUFFERS)

你可能感兴趣的:(DirectX11从入门到放弃,DX11学习)