前言
本篇为DX12 实例化的学习笔记
什么是实例化,为什么需要它?
想象一下,在绘制时,可能需要绘制同一物体但该物体的位置、朝向、大小、材质、纹理可能各不相同,按照常规的思路:我们需要保存对应物体的位置、朝向、大小、材质、纹理数据,若需要绘制的物体十分多,会造成draw call次数增大
那么有没有方案来解决这一问题呢?你可能会想出这样的解决方法——将该物体存放在局部空间,当需要绘制它的不同状态时,再乘以世界矩阵。虽然此方法确实节省了不少空间和开销,但因绘制物体而调用API的开销还是不让人满意,毕竟需要物体的顶点数据需要乘以世界矩阵,材质同样需要。难道说还有其他的解决方案麻?没错确实有,这就是本章的主角实例化
通过使用实例化技术,可以使得仅需一次绘制调用
即可构造出该对象的多个实例
。再者,若搭配动态索引,还能进一步优化其开销
其实在前面我们一直在绘制实例数据,只不过我们将绘制实例的数量设为1,也就是只绘制一个实例。若把此项设为10,也就是绘制10个相同的实例,但这并不能满足我们的需求,因此接下来需要思考如何让这些实例对象拥有它独特的示例数据
实例数据
- 输入实例数据有两种方法
- 在IA阶段,用逐实例数据流代替逐顶点数据流,再将第二个顶点缓冲区和含有实例数据的输入流绑定:在创建输入布局时,将结构体D3D12_INPUT_ELEMENT_DESC中的InputSlotClass用枚举
D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA
代替D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA
- 为所有实例都创建一个存有对应实例数据的
结构化缓冲区
,也就是说将对象实例化n次,则创建一个含有100个实例数据元素的结构化缓冲区,再将结构化缓冲区资源绑定到渲染管线,并根据需要绘制的实例在VS中引用对应数据
- 在IA阶段,用逐实例数据流代替逐顶点数据流,再将第二个顶点缓冲区和含有实例数据的输入流绑定:在创建输入布局时,将结构体D3D12_INPUT_ELEMENT_DESC中的InputSlotClass用枚举
现在存在一个问题——如何引用需要的实例?D3D提供系统值SV_InstanceID
来解决这一问题。如,将第一个实例所用的顶点都统一为0,第二个实例所用的顶点都统一为1,以此类推
-
实例
-
创建实例缓冲区
一个渲染项对应一个结构化缓冲区
struct InstanceData { DirectX::XMFLOAT4X4 World = MathHelper::Identity4x4(); DirectX::XMFLOAT4X4 TexTransform = MathHelper::Identity4x4(); UINT MaterialIndex; UINT InstancePad0; UINT InstancePad1; UINT InstancePad2; }; std::unique_ptr<UploadBuffer<InstanceData>> InstanceBuffer = nullptr; InstanceBuffer = std::make_unique<UploadBuffer<InstanceData>>(device, maxInstanceCount, false); //上传堆 struct RenderItem { //... std::vector<InstanceData> Instances; UINT InstanceCount = 0; //... }
-
shader
物体常量缓冲区的工作将交给实例缓冲区.实例缓冲区将MaterialIndex传递给VertexOut,PS再从VertexOut中的MaterialIndex获取材质数据
struct InstanceData { float4x4 World; float4x4 TexTransform; uint MaterialIndex; uint InstPad0; uint InstPad1; uint InstPad2; }; struct MaterialData { float4 DiffuseAlbedo; float3 FresnelR0; float Roughness; float4x4 MatTransform; uint DiffuseMapIndex; uint MatPad0; uint MatPad1; uint MatPad2; }; //注意!这里类型不再是Texture2DArray,这里所用类型的特点是该数组存储的纹理尺寸和格式可以各不相同,更为灵活 //纹理数组位于space0 Texture2D gDiffuseMap[7] : register(t0); //该材质结构化缓冲区位于space1,使得纹理数组不会和该资源重叠 StructuredBuffer<InstanceData> gInstanceData : register(t0, space1); StructuredBuffer<MaterialData> gMaterialData : register(t1, space1); struct VertexOut { float4 PosH : SV_POSITION; float3 PosW : POSITION; float3 NormalW : NORMAL; float2 TexC : TEXCOORD; // nointerpolation表示该索引指向的是未插值的三角形 nointerpolation uint MatIndex : MATINDEX; }; VertexOut VS(VertexIn vin, uint instanceID : SV_InstanceID) { VertexOut vout = (VertexOut)0.0f; // 获取实例数据 InstanceData instData = gInstanceData[instanceID]; float4x4 world = instData.World; float4x4 texTransform = instData.TexTransform; uint matIndex = instData.MaterialIndex; vout.MatIndex = matIndex; // 获取材质数据 MaterialData matData = gMaterialData[matIndex]; //... } float4 PS(VertexOut pin) : SV_Target { // 获取材质数据 MaterialData matData = gMaterialData[pin.MatIndex]; float4 diffuseAlbedo = matData.DiffuseAlbedo; float3 fresnelR0 = matData.FresnelR0; float roughness = matData.Roughness; uint diffuseTexIndex = matData.DiffuseMapIndex; // 动态索引。在数组中查找纹理 diffuseAlbedo *= gDiffuseMap[diffuseTexIndex].Sample(gsamLinearWrap, pin.TexC); //... }
-
reference
Directx12 3D 游戏开发实战
标签:matData,VertexOut,DX12,实例,uint,缓冲区,绘制 From: https://www.cnblogs.com/chenglixue/p/17234509.html