实例化:模型空间相同的几何体经过变换等得到不同的场景几何体。系统只维护一套几何顶点数据,但同时维护一套实例数据的结构化缓冲(包含变换矩阵,材质等等)。
一、实现步骤
1).模型数据的准备
与我们之前的做法一样,准备几何体的数据,放入默认堆作为资源,并填写Layout进行数据说明;
2).绘制调用
cmdList->DrawIndexedInstanced(IndexCount,InstanceCount,StartIndex, BaseVertex, 0);
//单个几何体包含索引值个数,实例化个数,起始索引,起始顶点,起始实例化索引值
注意InstanceCount在使用帧frame来架构时也要设置;
mFrameResources.push_back(std::make_unique<FrameResource>(md3dDevice.Get(),
1, InstanceCount, (UINT)mMaterials.size()));
那么问题来了,我们此时仅仅只有模型几何数据,并没有对其变换,材质等进行定义以表现不同实例的差异,那该怎么办呢?答案就是建立一个实例数据的缓冲区,用来存储不同实例的差异数据(变换坐标,材质索引等),当我们使用上这些数据时,我们的VS会如下所示:
SV_InstanceID定义了实例化对象的索引,用来索引不同实例化对象的结构化数据
VertexOut VS(VertexIn vin, uint instanceID : SV_InstanceID)
{
VertexOut vout = (VertexOut)0.0f;
// Fetch the instance data.
InstanceData instData = gInstanceData[instanceID];
float4x4 world = instData.World;
float4x4 texTransform = instData.TexTransform;
uint matIndex = instData.MaterialIndex;
vout.MatIndex = matIndex;//传递给PS用于获得材质进行采样
// Fetch the material data.
MaterialData matData = gMaterialData[matIndex];//材质缓冲区数组。根据索引得到不同材质
// Transform to world space.
float4 posW = mul(float4(vin.PosL, 1.0f), world);
vout.PosW = posW.xyz;
// Assumes nonuniform scaling; otherwise, need to use inverse-transpose of world matrix.
vout.NormalW = mul(vin.NormalL, (float3x3)world);
// Transform to homogeneous clip space.
vout.PosH = mul(posW, gViewProj);
// Output vertex attributes for interpolation across triangle.
float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f), texTransform);
vout.TexC = mul(texC, matData.MatTransform).xy;
return vout;
}
3).实例数据结构化缓冲的创建
看到这里你应该已经明白了,这没什么难的,我们在shader端不过是根据SV_InstanceID定义的索引来从数组取到我们各实例保有的结构化数据,然后处理逻辑都和以前一样。简而言之,把原来从常量缓冲得到的数据现在直接变成从结构化数组中得到。因此我们主要的难度也就在于常见这样的结构化缓冲来存储我们需要的差异性数据。
想一想,这个数据也是每帧都要更新的,这和常量缓冲有很大的相似性,也是通过上传堆来传递,然后是绑定根签名,填充到我们需要的槽当中。所不同的是不再要求256字节对齐。其实这个部分的内容应该是在计算着色器部分就要讲解的,也就是使用计算着色器进行GPGPU部分,向显存传数据,这里我们还是来仔细的剖析一下这个数据传输的流程。
根签名:
没有使用根描述符表,则不需要建立描述符堆等
slotRootParameter[0].InitAsShaderResourceView(0, 1);//实例数据的数组,0#槽space1
slotRootParameter[1].InitAsShaderResourceView(1, 1);//材质数据的数组,1#槽space1
后边就是序列化加创建根签名
实例结构化数据结构:
struct InstanceData
{
DirectX::XMFLOAT4X4 World = MathHelper::Identity4x4();//世界空间变换矩阵
DirectX::XMFLOAT4X4 TexTransform = MathHelper::Identity4x4();//纹理变换矩阵
UINT MaterialIndex;//用来索引材质数组的材质信息
UINT InstancePad0;//补齐占位
UINT InstancePad1;
UINT InstancePad2;
}; vector<InstanceData> Instances;
实例化结构体数据填写:数据存入Instances
skullRitem->Instances[index].World = XMFLOAT4X4(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
x + j*dx, y + i*dy, z + k*dz, 1.0f);
XMStoreFloat4x4(&skullRitem->Instances[index].TexTransform, XMMatrixScaling(2.0f, 2.0f, 1.0f));
skullRitem->Instances[index].MaterialIndex = index % mMaterials.size();
建立上传堆数据映射:
InstanceBuffer = std::make_unique<UploadBuffer<InstanceData>>(device, maxInstanceCount, false);//使用UploadBuffer类,要注意此时不为常量缓冲,因此最后一个参数为false;
传输数据:data到上传堆
InstanceBuffer ->CopyData(visibleInstanceCount++, data);//visibleInstanceCount实例化索引
建立根签名寄存器数据映射:
mCommandList->SetGraphicsRootShaderResourceView(0, instanceBuffer->GetGPUVirtualAddress());
mCommandList->SetGraphicsRootShaderResourceView(1, matBuffer->GetGPUVirtualAddress());
我们这里没有讲材质数组数据的传输,因为是一样的。
在shader里边我们就从如下结构取数据:注意是t#,space1
StructuredBuffer<InstanceData> gInstanceData : register(t0, space1);
StructuredBuffer<MaterialData> gMaterialData : register(t1, space1);
有关InstanceData&MaterialData,shader中与cpu端保持一致。