DirectX 11 学习笔记-Part2-1【Instancing】

Instancing

实例化(Instancing)的意思是指,在场景中多次绘制同一个物体,但是可以有不同的位置,方向,缩放,材质和贴图等。

我们在绘制相同几何体时,通常不会直接使用每个几何体的世界坐标顶点和索引来绘制,而是使用同一份顶点和索引数据,然后根据不同物体的不同世界坐标,将本地坐标变换成世界坐标再进行绘制。这种做法虽然减少了重复的存储,但每次绘制时依然需要为每个物体设置一个不同的世界坐标矩阵,独特的材质并调用一次Draw命令。而实际上,我们应当尽量减少draw API的调用,因为过多的draw call会加重cpu的负担带来额外的开销。

Direct3D提供一种机制用于绘制大量类似的几何体,这种机制被称为hardware instancing。

这种机制的核心思想是将需要绘制的实例的数据直接存放在顶点缓冲区中,并在每次绘制时使用顶点缓冲区中实例对应的数据。此时着色器代码中的输入顶点结构体可以定义为:

struct VertexIn
{
    float3 PosL : POSITION;
    float3 NormalL : NORMAL;
    float2 Tex : TEXCOORD;
    row_major float4x4 World : WORLD;
    float4 Color : COLOR;
    uint InstanceId : SV_InstanceID;
};

顶点数据可以分为两部分,前三个数据和之前的顶点数据一样,代表缓冲区中每个顶点的坐标,法线和纹理坐标。而后三个数据则是对应每个将要绘制的实例,即每个实例都有一个世界坐标,颜色和实例ID值。这里的InstanceId是系统提供的,第一个实例的id为0以此类推。使用中可以像之前几何着色器中的图元ID一样,用于纹理采样等。

着色器的代码基本上和之前不变,主要是输入布局的上有比较大的不同。之前在讲到Inputlayout时有提到Inputslot的使用,这里我们需要使用两个Inputslot,一个用于输入几何体的顶点数据,另一个用于输入每个实例的位置信息等,此时的输入布局描述如下

static const D3D11_INPUT_ELEMENT_DESC InstancedBasic32LayoutDesc[] = 
{
	{"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},

	{"WORLD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT,	1, 0,	D3D11_INPUT_PER_INSTANCE_DATA, 1},
	{"WORLD", 1, DXGI_FORMAT_R32G32B32A32_FLOAT,	1, 16,	D3D11_INPUT_PER_INSTANCE_DATA, 1},
	{"WORLD", 2, DXGI_FORMAT_R32G32B32A32_FLOAT,	1, 32,	D3D11_INPUT_PER_INSTANCE_DATA, 1},
	{"WORLD", 3, DXGI_FORMAT_R32G32B32A32_FLOAT,	1, 48,	D3D11_INPUT_PER_INSTANCE_DATA, 1},
	{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT,	1, 64,	D3D11_INPUT_PER_INSTANCE_DATA, 1}
};

除了Inputslot不同,还有两个参数InputSlotClass和InstanceDataStepRate。InputSlotClass为两个枚举值,用于表示这个数据是顶点的数据还是实例的数据,而InstanceDataStepRate表示这个数据,每个数据可以绘制的实例的数量。例如我们要绘制6个几何体,但只有三个颜色数据RGB,那么可以将颜色的InstanceDataStepRate设置为2,则前两个实例的颜色是R,然后是两个G然后是两个B。

创建好输入布后,我们需要在绘制前把相应的顶点缓冲区绑定到管线中。由于我们有两个InputSlot,所以应该准备两个顶点缓冲区

struct Basic32
{
    XMFLOAT3 Pos;
    XMFLOAT3 Normal;
    XMFLOAT2 Tex;
};
struct InstancedData
{
    XMFLOAT4X4 World;
    XMFLOAT4 Color;
};

UINT stride[2] = {sizeof(Vertex::Basic32), sizeof(InstancedData)};
UINT offset[2] = {0,0};
ID3D11Buffer* vbs[2] = {mSkullVB, mInstancedBuffer};
md3dImmediateContext->IASetVertexBuffers(0, 2, vbs, stride, offset);
md3dImmediateContext->IASetInputLayout(InputLayouts::InstancedBasic32);

此外绘制所用的API也要改为DrawIndexedInstanced。

void DrawIndexedInstanced(
  UINT IndexCountPerInstance,
  UINT InstanceCount,
  UINT StartIndexLocation,
  INT  BaseVertexLocation,
  UINT StartInstanceLocation
);

这个函数接受5个参数,IndexCountPerInstance是索引缓冲区的索引数量,InstanceCount是需要绘制的实例数量,StartIndexLocation为GPU读取顶点缓冲区的起始,一般为0,BaseVertexLocation和StartInstanceLocation也一般为0,只有当我们不想从第一个顶点/Instancing Data开始读取时,才会用到他们。这里我们后三个参数都为0。

pContext->DrawIndexedInstanced(mIndexNum, mVisibleObjectCount, 0, 0, 0);

 

你可能感兴趣的:(C++,DirectX)