Introduction to 3D Game Programming with DirectX 11学习笔记 6 Direct3D中的绘制(一)

顶点和顶点布局

在Direct3D中,顶点由空间位置和各种附加属性组成,Direct3D可以让我们灵活地建立属于我们自己的顶点格式;换句话说,它允许我们定义顶点的分量。要创建一个自定义的顶点格式,我们必须先创建一个包含顶点数据的结构体。例如,下面是两种不同类型的顶点格式;一个由位置和颜色组成,另一个由位置、法线和纹理坐标组成。

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

在定义了顶点结构体之后,我们必须设法描述该顶点结构体的分量结构,使Direct3D知道该如何使用每个分量。这一描述信息是以输入布局(ID3D11InputLayout)的形式提供给Direct3D的。输入布局是一个D3D11_INPUT_ELEMENT_DESC数组。D3D11_INPUT_ELEMENT_DESC数组中的每个元素描述了顶点结构体的一个分量。比如,当顶点结构体包含两个分量时,对应的D3D11_INPUT_ELEMENT_DESC数组会包含两个元素。我们将D3D11_INPUT_ELEMENT_DESC称为输入布局描述(input layout description)。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:一个与元素相关的字符串。它可以是任何有效的语义名。语义(semantic)用于将顶点结构体中的元素映射为顶点着色器参数(参见图6.1)。
这里写图片描述
图6.1 顶点结构体中的每个元素分别由D3D11_INPUT_ELEMENT_DESC数组中的对应元素描述。语义名和语义索引提供了一种将顶点元素映射为顶点着色器参数的方法。

2.SemanticIndex:附加在语义上的索引值。图6.1说明了使用该索引的原因;举例来说,当顶点结构体包含多组纹理坐标时,我们不是添加一个新的语义名,而是在语义名的后面加上一个索引值。在着色器代码中没有指定索引的语义默认索引为0,例如,在图6.1中的POSITION相当于POSITION0。

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),通过这些输入槽我们可以向着色器传入顶点数据。例如,当一个顶点由位置元素和颜色元素组成时,**我们既可以使用一个输入槽传送两种元素,也可以将两种元素分开,使用第一个输入槽传送顶点元素,使用第二个输入槽传送颜色元素。**Direct3D可以将来自于不同输入槽的元素重新组合为顶点。在本书中,我们只使用一个输入槽,但是在本章结尾的练习2中我们会引导读者做一个使用两个输入槽的练习。

5.AlignedByteOffset对于单个输入槽来说,该参数表示从顶点结构体的起始位置到顶点元素的起始位置之间的字节偏移量。例如在下面的顶点结构体中,元素Pos的字节偏移量为0,因为它的起始位置与顶点结构体的起始位置相同;元素Normal的字节偏移量为12,因为必须跳过由Pos占用的字节才能到达Normal的起始位置;元素Tex0的字节偏移量为24,因为必须跳过由Pos和Normal占用的字节才能到达Tex0的起始位置;元素Tex1的字节偏移量为32,因为必须跳过由Pos,Normal和Tex0占用的字节才能到达Tex1的起始位置。

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方法获取一个表示输入布局的ID3D11InputLayout接口的指针:

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

1.pInputElementDescs:一个用于描述顶点结构体的D3D11_INPUT_ELEMENT_DESC数组。

2.NumElements:D3D11_INPUT_ELEMENT_DESC数组的元素数量。

3.pShaderBytecodeWithInputSignature:指向顶点着色器参数的字节码的指针。

4.BytecodeLength:顶点着色器参数的字节码长度,单位为字节。

5.ppInputLayout:返回创建后的ID3D11InputLayout指针。

我们需要进一步解释一下第3个参数的含义。本质上,顶点着色器以一组顶点元素作为它的输入参数——也就是所谓的输入签名(input signature)。自定义顶点结构体中的元素必须被映射为与它们对应的顶点着色器参数,图6.1解释了这一问题。通过在创建输入布局时传入顶点着色器签名,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.

假如顶点结构和输入参数与输入元素匹配,但类型不同:

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

这样做是可行的,因为Direct3D允许输入寄存器中的字节被重新解释。但是,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 InputLayout 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 reinte rpretation  was not intended by the author.

下面的代码说明了该如何调用ID3D11Device::CreateInputLayout方法。注意,这些代码涉及了一些我们还未讨论的内容(比如ID3D11Effect)。本质上,一个effect可以封装一个或多个pass,而每个pass都会与一个顶点着色器相连。所以,我们可以从effect中得到有关pass的描述信息(D3D11_PASS_DESC),然后再从中得到顶点着色器的输入签名。

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对象被绑定到设备上时,如果不去改变它,那么它会始终驻留在那里。


顶点缓冲

为了让GPU访问顶点数组,我们必须把它放置在一个称为缓冲(buffer)的特殊资源容器中,该容器由ID3D11Buffer接口表示。

**用于存储顶点的缓冲区称为顶点缓冲(vertex buffer)。**Direct3D缓冲不仅可以存储数据,而且还说明了如何访问数据以及数据被绑定到图形管线的那个阶段。要创建一个顶点缓冲,我们必须执行以下步骤:

1.填写一个D3D11_BUFFER_DESC结构体,描述我们所要创建的缓冲区。

2.填写一个D3D11_SUBRESOURCE_DATA结构体,为缓冲区指定初始化数据。

3.调用ID3D11Device::CreateBuffer方法来创建缓冲区。

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:我们将要创建的顶点缓冲区的大小,单位为字节。

2.Usage:一个用于指定缓冲区用途的D3D11_USAGE枚举类型成员。有4个可选值:

(a)D3D10_USAGE_DEFAULT:表示GPU会对资源执行读写操作。在使用映射API(例如ID3D11DeviceContext::Map)时,CPU在使用映射API时不能读写这种资源,但它能使用ID3D11DeviceContext::UpdateSubresource。ID3D11DeviceContext::Map方法会在6.14节中介绍。

(b)D3D11_USAGE_IMMUTABLE:表示在创建资源后,资源中的内容不会改变。这样可以获得一些内部优化,因为GPU会以只读方式访问这种资源。除了在创建资源时CPU会写入初始化数据外,其他任何时候CPU都不会对这种资源执行任何读写操作,我们也无法映射或更新一个只读资源。

(c)D3D11_USAGE_DYNAMIC:表示应用程序(CPU)会频繁更新资源中的数据内容(例如,每帧更新一次)。GPU可以从这种资源中读取数据,使用映射API(ID3D11DeviceContext::Map)时,CPU可以向这种资源中写入数据。因为新的数据要从CPU内存(即系统RAM)传送到GPU内存(即显存),所以从CPU动态地更新GPU资源会有性能损失;若非必须,请勿使用D3D11_USAGE_DYNAMIC。

(d)D3D11_USAGE_STAGING:表示应用程序(CPU)会读取该资源的一个副本(即,该资源支持从显存到系统内存的数据复制操作)。显存到系统内存的复制是一个缓慢的操作,应尽量避免。使用ID3D11DeviceContext::CopyResource和ID3D11DeviceContext::CopySubresourceRegion方法可以复制资源,在12.3.5节会介绍一个复制资源的例子。

3.BindFlags:对于顶点缓冲区,该参数应设为D3D11_BIND_VERTEX_BUFFER。

4.CPUAccessFlags:指定CPU对资源的访问权限。设置为0则表示CPU无需读写缓冲。如果CPU需要向资源写入数据,则应指定D3D11_CPU_ACCESS_WRITE。具有写访问权限的资源的Usage参数应设为D3D11_USAGE_DYNAMIC或D3D11_USAGE_STAGING。如果CPU需要从资源读取数据,则应指定D3D11_CPU_ACCESS_READ。具有读访问权限的资源的Usage参数应设为D3D11_USAGE_STAGING。当指定这些标志值时,应按需而定。通常,CPU从Direct3D资源读取数据的速度较慢。CPU向资源写入数据的速度虽然较快,但是把内存副本传回显存的过程仍很耗时。所以,最好的做法是(如果可能的话)不指定任何标志值,让资源驻留在显存中,只用GPU来读写数据。

5.MiscFlags:我们不需要为顶点缓冲区指定任何杂项(miscellaneous)标志值,所以该参数设为0。有关D3D11_RESOURCE_MISC_FLAG枚举类型的详情请参阅SDK文档。

6.StructureByteStride:存储在结构化缓冲中的一个元素的大小,以字节为单位。这个属性只用于结构化缓冲,其他缓冲可以设置为0。所谓结构化缓冲,是指存储其中的元素大小都相等的缓冲。

D3D11_SUBRESOURCE_DATA结构体的定义如下:

typedef struct D3D11_SUBRESOURCE_DATA { 
    const void *pSysMem; 
    UINT SysMemPitch; 
    UINT SysMemSlicePitch; 
} D3D11_SUBRESOURCE_DATA;

1.pSysMem:包含初始化数据的系统内存数组的指针。当缓冲区可以存储n个顶点时,对应的初始化数组也应至少包含n个顶点,从而使整个缓冲区得到初始化。

2.SysMemPitch:顶点缓冲区不使用该参数。

3.SysMemSlicePitch:顶点缓冲区不使用该参数。

下面的代码创建了一个只读的顶点缓冲区,并以中心在原点上的立方体的8顶点来初始化该缓冲区。之所以说该缓冲区是只读的,是因为当立方体创建后相关的几何数据从不改变——始终保持为一个立方体。另外,我们为每个顶点指定了不同的颜色;这些颜色将用于立方体着色,我们会在本章随后的小节中对此进行讲解。

//  定义在d3dUtil.h中的Colors命名空间
//
// #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};

    XMGLOBALCONST XMVECTORF32 Silver    = {0.75f, 0.75f, 0.75f, 1.0f};
    XMGLOBALCONST XMVECTORF32 LightSteelBlue = {0.69f, 0.77f, 0.87f, 1.0f};
}

// 创建顶点缓冲
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, &vinitData, &mVB));

Vertex类型和颜色由以下结构定义:

struct Vertex
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};

在创建顶点缓冲区后, 我们必须把它绑定到设备的输入槽上,只有这样才能将顶点送入管线。这一工作使用如下方法完成:

void ID3D11DeviceContext::IASetVertexBuffers( 
    UINT StartSlot, 
    UINT NumBuffers, 
    ID3D10Buffer *const *ppVertexBuffers, 
    const UINT *pStrides, 
    const UINT *pOffsets);

1.StartSlot:顶点缓冲区所要绑定的起始输入槽。一共有16个输入槽,索引依次为0到15。

2.NumBuffers:顶点缓冲区所要绑定的输入槽的数量,如果起始输入槽为索引k,我们绑定了n个缓冲,那么缓冲将绑定在索引为Ik,Ik+1……Ik+n-1的输入槽上。

3.ppVertexBuffers:指向顶点缓冲区数组的第一个元素的指针。

4.pStrides:指向步长数组的第一个元素的指针(该数组的每个元素对应一个顶点缓冲区,也就是,第i个步长对应于第i个顶点缓冲区)。这个步长是指顶点缓冲区中的元素的字节长度。

5.pOffsets:指向偏移数组的第一个元素的指针(该数组的每个元素对应一个顶点缓冲区,也就是,第i个偏移量对应于第i个顶点缓冲区)。这个偏移量是指从顶点缓冲区的起始位置开始,到输入装配阶段将要开始读取数据的位置之间的字节长度。当希望跳过顶点缓冲区前面的一部分数据时,可以使用该参数。

因为IASetVertexBuffers方法支持将一个顶点缓冲数组设置到不同的输入槽中,因此这个方法看起来有点复杂。但是,大多数情况下我们只使用一个输入槽。本章最后的练习部分你会遇到使用两个输入插槽的情况。

顶点缓冲区会一直绑定在输入槽上时。如果不改变输入槽的绑定对象,那么当前的顶点缓冲区会一直驻留在那里。所以,当使用多个顶点缓冲区时,你可以按照下面的形式组织代码:

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... */

把顶点缓冲区指定给输入槽并不能实现顶点的绘制;它只是绘制前的准备工作(准备把顶点传送到管线)。顶点的实际绘制工作由ID3D11DeviceContext::Draw方法完成:

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

这两个参数定义了在顶点缓冲区中所要绘制的顶点的范围,如图6.2所示。

这里写图片描述
图6.2 StartVertexLocation 指定了在顶点缓冲区中所要绘制的第一个顶点的索引(从0开始)。VertexCount指定了所要绘制的顶点的数量。


索引和索引缓冲

由于索引要由GPU访问,所以它们必须放在一个特定的资源容器中,该容器称为索引缓冲(index buffer)。创建索引缓冲的过程与创建顶点缓冲的过程非常相似,只不过索引缓冲存储的是索引而非顶点。所以,这里不再赘述之前讨论过的内容,我们直接给出一个创建索引缓冲区的示例:

UINT indices [24] = {
    0, 1, 2,   //  Triangle  0
    0, 2, 3,   //  Triangle  1
    0, 3, 4,   //  Triangle  2
    0, 4, 5,   //  Triangle  3
    0, 5, 6,   //  Triangle  4
    0, 6, 7,   //  Triangle  5
    0, 7, 8,   //  Triangle  6
    0, 8, 1    //  Triangle  7
} ;

// 要创建的索引的描述
D3D11_BUFFER_DESC ibd;
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = sizeof(UINT) * 24;
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
ibd.MiscFlags = 0;
ibd.StructureByteStride = 0;

// 设定用于初始化索引缓冲的数据
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = indices;

// 创建索引缓冲
ID3D11Buffer* mIB;
HR(md3dDevice->CreateBuffer(&ibd, &iinitData, &mIB));

与顶点缓冲区相同,所有的Direct3D资源在使用之前都必须先绑定到管线上。我们使用ID3D11DeviceContext::IASetIndexBuffer方法将一个索引缓冲区绑定到输入装配阶段。下面是一个例子:

md3dImmediateContext->IASetIndexBuffer(mIB, DXGI_FORMAT_R32_UINT, 0);

第2个参数表示索引格式。在本例中,我们使用的是32位无符号整数(DWORD);所以,该参数设为DXGI_FORMAT_R32_UINT。如果你希望节约一些内存,不需要这大的取值范围,那么可以改用16位无符号整数。还要注意的是,在IASetIndexBuffer方法中指定的格式必须与D3D11_BUFFER_DESC::ByteWidth数据成员指定的字节长度一致,否则会出现问题。索引缓冲区只支持DXGI_FORMAT_R16_UINT和DXGI_FORMAT_R32_UINT两种格式。第3个参数是一个偏移值,它表示从索引缓冲区的起始位置开始、到输入装配时实际读取数据的位置之间的字节长度。如果希望跳过索引缓冲区前面的一部分数据,那么可以使用该参数。

最后,当使用索引时,我们必须用DrawIndexed方法代替Draw方法:

void ID3D11DeviceContext::DrawIndexed( 
    UINT IndexCount, 
    UINT StartIndexLocation, 
    INT BaseVertexLocation);

1.IndexCount:在当前绘图操作中使用的索引的数量。在一次绘图操作中不一定使用索引缓冲区中的全部索引;也就是说,我们可以绘制索引的一个连续子集。

2.StartIndexLocation:指定从索引缓冲区的哪个位置开始读取索引数据。

3.BaseVertexLocation:在绘图调用中与索引相加的一个整数。

我们通过分析如下情景来解释些参数。假设有三个物体:一个球体、一个立方体和一个圆柱体。每个物体都有它自己的顶点缓冲和索引缓冲。在每个独立的索引缓冲中的索引都与各自独立的顶点缓冲对应。现在,我们把这三个物体的顶点和索引数据合并到一个全局的顶点缓冲和一个全局的索引缓冲中,如图6.3所示(假如有许多小顶点缓冲和索引缓冲,并且它们可以很容易的合并,那么合并它们可以带来性能提升)。在合并之后,索引就不再正确了(因为这些索引是与各自独立的顶点缓冲区对应的,它们与全局顶点缓冲区没有对应关系);所以,这些索引必须被重新计算,使它们与全局顶点缓冲区建立正确的对应关系。

这里写图片描述
图6.3 将多个顶点缓冲合并为一个大的顶点缓冲,将多个索引缓冲合并为一个大的索引缓冲。

假设原先的立方体索引为0、1、…、numBoxVertices-1,通过个索引可以遍历立方体的所有顶点。在合并之后,索引应该变为firstBoxVertexPos、firstBoxVertexPos+1、…、firstBoxVertexPos+ numBoxVertices -1。所以,要更新索引,我们必须将firstBoxVertexPos与立方体的每个索引相加。而且,我们必须将firstCylVertexPos与圆柱体的每个索引相加。注意,球体的索引不需要修改(因为球体的顶点位置为0)。通常,只要将一个物体在全局顶点缓冲区中的第一个顶点位置与原索引相加就可以得到该物体的新索引值。所以,只要给出物体在全局顶点缓冲中的第一个顶点的位置(例如,BaseVertexLocation),Direct3D就可以在绘图调用中为我们重新计算索引。我们可以使用以下3条语句依次绘制球体、立方体和圆柱体:

md3dImmediateContext->DrawIndexed(numSphereIndices, 0, 0); 
md3dImmediateContex->DrawIndexed(numBoxIndices, firstBoxIndex, firstBoxVertexPos); 
md3dImmediateContex->DrawIndexed(numCylIndices, firstCylIndex, firstCylVertexPos);
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值