D3D11绘制函数详解
D3D11有如下几个绘制函数:
Draw函数
ID3D11DeviceContext::Draw 是 D3D11 中绘制非索引图元的函数,用于按顶点缓冲区的顺序渲染几何图形。
函数原型
void Draw(
UINT VertexCount, // 要绘制的顶点总数
UINT StartVertexLocation // 顶点缓冲区中起始顶点的索引(从0开始)
);
核心要点
1. 绘制前需完成顶点缓冲区绑定(IASetVertexBuffers)、输入布局绑定(IASetInputLayout)、图元拓扑设置(IASetPrimitiveTopology),否则绘制无效果。
2. 顶点数据直接按StartVertexLocation到StartVertexLocation+VertexCount-1的顺序读取,无索引重排。
使用示例
// 假设已初始化d3d11设备/上下文,绑定好顶点缓冲区、输入布局
pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // 设置为三角形列表
pImmediateContext->Draw(3, 0); // 绘制1个三角形(3个顶点),从第0个顶点开始
搭配拓扑
IASetPrimitiveTopology的拓扑类型匹配顶点数量,常见适配:D3D11_PRIMITIVE_TOPOLOGY_POINTLIST:1个顶点对应1个点D3D11_PRIMITIVE_TOPOLOGY_LINELIST:2个顶点对应1条线D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST:3个顶点对应1个三角形- 带 STRIP 的拓扑(如
TRIANGLESTRIP):n个顶点可绘制 n-2个图形,减少顶点冗余。
常见错误
1. 若VertexCount不足拓扑要求,Direct3D 会静默丢弃无效图元,无报错但无渲染结果。
2. 绘制前需确保管线状态(PS/VS 着色器、Rasterizer/Blend 等状态)已正确绑定。
3. 高效绘制建议:批量绘制减少Draw调用次数,索引绘制优先用DrawIndexed,大量重复模型用实例化绘制。
DrawIndexed函数
ID3D11DeviceContext::DrawIndexed 是 Direct3D 11 中用于绘制索引化几何体的 API 函数。
它通过索引缓冲区(Index Buffer) 复用顶点数据,高效绘制三角形、线、点等图元,是 3D 渲染中最常用的绘制接口之一。
函数原型
void DrawIndexed(
UINT IndexCount, // 要绘制的索引数量
UINT StartIndexLocation, // 起始索引在索引缓冲区中的偏移(单位:索引)
INT BaseVertexLocation // 基顶点偏移,最终顶点索引 = 索引值 + BaseVertexLocation
);
关键参数含义
1. IndexCount:本次绘制需要读取的索引个数,决定绘制的图元数量(如三角形数量 = IndexCount / 3)。
2. StartIndexLocation:从索引缓冲区的第几个索引开始读取(偏移量,非字节偏移),用于分批次绘制同一缓冲区中的不同模型 / 片段。
3. BaseVertexLocation :基顶点偏移。顶点着色器最终访问的顶点索引 = 索引缓冲区读取的值 + BaseVertexLocation。 核心作用是多个模型共享同一个顶点缓冲区时,通过该参数区分不同模型的顶点段,无需拆分顶点缓冲区。
工作流程
DrawIndexed 前,必须通过 IASetIndexBuffer、IASetVertexBuffers、IASetPrimitiveTopology 完成以下绑定:- 索引缓冲区(Index Buffer,
IB):存储顶点索引序列; - 顶点缓冲区(Vertex Buffer,
VB):存储顶点数据(位置、法线、纹理坐标等); - 图元拓扑(Primitive Topology):指定索引如何组装图元(如
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST三角形列表)。
- 从
StartIndexLocation开始,读取IndexCount个索引值; - 每个索引值 +
BaseVertexLocation,得到最终顶点索引; - 根据最终顶点索引,从顶点缓冲区读取对应顶点数据;
- 按图元拓扑组装图元,送入顶点着色器及后续渲染管线完成绘制。
使用示例
// 1. 绑定索引缓冲区、顶点缓冲区、图元拓扑
UINT stride = sizeof(Vertex); // 顶点数据步长
UINT offset = 0;
pDeviceContext->IASetVertexBuffers(0, 1, &pVertexBuffer, &stride, &offset);
pDeviceContext->IASetIndexBuffer(pIndexBuffer, DXGI_FORMAT_R32_UINT, 0); // 索引格式为32位无符号整数
pDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// 2. 调用DrawIndexed绘制
UINT indexCount = 36; // 立方体索引数量(12个三角形 × 3个索引)
UINT startIndex = 0; // 从索引缓冲区起始位置开始
INT baseVertex = 0; // 无基顶点偏移,直接使用索引值
pDeviceContext->DrawIndexed(indexCount, startIndex, baseVertex);
使用要点
相比无索引绘制(
Draw),DrawIndexed 可通过索引复用重复顶点(如立方体共享顶点),大幅减少顶点数据存储量,提升渲染效率。索引缓冲区仅支持两种格式:
DXGI_FORMAT_R16_UINT(16 位索引,最大顶点数 65535)DXGI_FORMAT_R32_UINT(32 位索引,支持更大规模模型)
BaseVertexLocation 定位每个模型的顶点起始位置,StartIndexLocation 定位索引起始位置,实现批量绘制,减少 API 调用次数。与 Draw函数的区别
| 函数 | 数据组织方式 | 适用场景 | 效率 |
|---|---|---|---|
DrawIndexed |
索引 + 顶点缓冲区 | 模型有重复顶点、复杂几何体 | 更高(顶点复用,数据量小) |
Draw |
仅顶点缓冲区 | 无重复顶点的简单图元 | 较低(顶点数据冗余) |
常见错误
1. 参数越界:StartIndexLocation + IndexCount 超过索引缓冲区实际索引数,或 索引值 + BaseVertexLocation 超过顶点缓冲区顶点数,会导致渲染崩溃 / 异常。
2. 资源未绑定:未绑定索引 / 顶点缓冲区、图元拓扑,调用后无渲染结果,需检查 IASet* 系列调用是否成功。
3. 索引格式不匹配:索引缓冲区创建格式与 IASetIndexBuffer 绑定格式不一致(如创建用 R16,绑定时用 R32),会导致顶点索引读取错误,渲染出畸形图元。
DrawInstanced函数
ID3D11DeviceContext::DrawInstanced 是 D3D11 中实例化绘制的函数,用于一次性渲染多个相同几何结构的模型(如批量物体、粒子),仅需 1 次 Draw 调用,大幅减少 CPU 开销。
函数原型
void DrawInstanced(
UINT VertexCountPerInstance, // 单个实例的顶点数量
UINT InstanceCount, // 要绘制的实例总数
UINT StartVertexLocation, // 顶点缓冲区的起始顶点索引(从0开始)
UINT StartInstanceLocation // 实例缓冲区的起始实例索引(从0开始)
);
1. 需同时绑定顶点缓冲区(存储模型几何)和实例缓冲区(存储每个实例的独有关联数据,如位置、旋转、颜色,通过INSTANCE语义在顶点着色器中读取)。
2. 绘制时,GPU 会为每个实例重复读取顶点数据,并搭配对应实例的独有关联数据,完成批量渲染。
3. 仅适用于非索引的实例化绘制,索引实例化需用DrawIndexedInstanced(更常用)。
前置步骤
1. 定义顶点着色器输入布局,包含顶点语义(如POSITION/TEXCOORD)和实例语义(如SV_InstanceID或自定义INSTANCE+ 编号,需设置InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA,InstanceDataStepRate = 1)。
2. 分别创建顶点缓冲区和实例缓冲区,通过IASetVertexBuffers绑定(实例缓冲区需指定独立的输入槽位)。
3. 设置图元拓扑(如TRIANGLELIST),绑定好管线状态(VS/PS、渲染状态等)。
使用示例
// 假设:单个立方体有36个顶点(6面×2三角×3顶点),要绘制100个实例,从第0顶点/第0实例开始
UINT vertexCountPerInstance = 36;
UINT instanceCount = 100;
UINT startVertex = 0;
UINT startInstance = 0;
// 绑定顶点缓冲区(槽0)、实例缓冲区(槽1)+ 输入布局、拓扑后
pImmediateContext->DrawInstanced(vertexCountPerInstance, instanceCount, startVertex, startInstance);
注意事项
1. 实例语义要求:实例缓冲区的输入布局必须正确设置PER_INSTANCE_DATA,否则 GPU 无法识别实例数据。
2. 数据匹配:InstanceCount不能超过实例缓冲区中实际的实例数量,否则会读取非法内存。
3. 性能优势:相比循环调用Draw,DrawInstanced仅 1 次 CPU-GPU 通信,实例数越多,性能提升越明显(适用于 100 + 批量相同模型)。
4.SV_InstanceID:顶点着色器中可直接使用系统值SV_InstanceID(从 0 开始的实例编号),用于索引实例数据数组,无需手动在实例缓冲区中存储编号。
与 Draw函数的区别
| 函数 | 渲染方式 | 实例数据支持 | CPU 调用次数 | 适用场景 |
|---|---|---|---|---|
| Draw | 单实例非索引 | 不支持 | 实例数 = 调用次数 | 单个模型渲染 |
| DrawInstanced | 多实例非索引 | 支持 | 1 次 | 批量相同非索引模型渲染 |
DrawInstancedIndirect函数
ID3D11DeviceContext::DrawInstancedIndirect 是 D3D11 中基于间接参数缓冲区执行实例化绘制的核心接口核心特点
1. 从D3D11_BUFFER类型的间接参数缓冲区中读取绘制参数,参数结构为D3D11_DRAW_INSTANCED_INDIRECT_ARGS:
typedef struct D3D11_DRAW_INSTANCED_INDIRECT_ARGS {
UINT VertexCountPerInstance; // 每个实例的顶点数
UINT InstanceCount; // 实例数量
UINT StartVertexLocation; // 起始顶点偏移
UINT StartInstanceLocation; // 起始实例偏移
} D3D11_DRAW_INSTANCED_INDIRECT_ARGS;
2. 间接缓冲区需绑定为D3D11_BIND_INDIRECT_ARGUMENT标识,且可由 GPU(如计算着色器)动态修改参数,实现 CPU 无介入的绘制控制。
3. 仅支持非索引化的实例化绘制,索引化实例化需使用DrawIndexedInstancedIndirect。
基础调用流程
1. 创建间接参数缓冲区,初始化 / 由 GPU 写入D3D11_DRAW_INSTANCED_INDIRECT_ARGS数据;
2. 调用ID3D11DeviceContext::GSSetShader/VSSetShader设置实例化相关着色器;
3. 绑定顶点缓冲区、实例缓冲区等资源;
4. 调用接口,指定间接缓冲区和参数偏移:
void DrawInstancedIndirect(
ID3D11Buffer* pIndirectArgumentBuffer, // 间接参数缓冲区
UINT AlignedByteOffsetForArgs // 参数在缓冲区中的字节偏移(需4字节对齐)
);
关键注意事项
1. 间接缓冲区的字节偏移必须 4 字节对齐,否则会触发绘制错误;
2. 若InstanceCount为 0,GPU 会直接跳过本次绘制,无性能开销;
3. 需保证缓冲区中参数合法(如顶点数不超过顶点缓冲区范围),否则会导致设备挂起 / 渲染异常;
4.该接口为即时绘制,需在ID3D11DeviceContext的即时上下文 / 延迟上下文中调用,延迟上下文需提交后生效。
适用场景
1. 大规模动态实例化渲染(如粒子系统、植被、海量模型批量绘制);
2. GPU 驱动的视锥体裁剪(计算着色器剔除不可见实例,更新InstanceCount);
3. 批处理绘制优化,减少 CPU-GPU 通信开销。
示例
1. 绘制100 个彩色三角形实例,每个实例有独立的位置和颜色
2. 间接参数缓冲区由 CPU 初始化(也可替换为 CS GPU 动态更新)
3. 包含顶点着色器(处理实例化数据)、像素着色器(输出颜色)
4. 所有资源创建、绑定、绘制流程完整,附带关键注释
步骤 1:头文件与全局变量定义
#include <d3d11.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")
using namespace DirectX;
// 全局D3D11对象
ID3D11Device* g_pd3dDevice = nullptr;
ID3D11DeviceContext* g_pImmediateContext = nullptr;
IDXGISwapChain* g_pSwapChain = nullptr;
ID3D11RenderTargetView* g_pRenderTargetView = nullptr;
// 绘制资源
ID3D11Buffer* g_pVertexBuffer = nullptr; // 基础顶点缓冲区(单个三角形)
ID3D11Buffer* g_pInstanceBuffer = nullptr; // 实例数据缓冲区(位置+颜色)
ID3D11Buffer* g_pIndirectArgBuffer = nullptr; // 间接绘制参数缓冲区
ID3D11VertexShader* g_pVS = nullptr; // 顶点着色器
ID3D11PixelShader* g_pPS = nullptr; // 像素着色器
ID3D11InputLayout* g_pInputLayout = nullptr; // 输入布局
// 基础顶点结构体(仅位置)
struct Vertex
{
XMFLOAT3 pos;
};
// 实例数据结构体(位置偏移+颜色)
struct InstanceData
{
XMFLOAT3 offset; // 每个实例的位置偏移
XMFLOAT4 color; // 每个实例的颜色
};
// 间接绘制参数结构体(与D3D11_DRAW_INSTANCED_INDIRECT_ARGS完全一致)
struct DrawInstancedIndirectArgs
{
UINT VertexCountPerInstance; // 每个实例的顶点数
UINT InstanceCount; // 实例总数
UINT StartVertexLocation; // 起始顶点偏移
UINT StartInstanceLocation; // 起始实例偏移
};
// 窗口句柄(需根据实际工程替换)
HWND g_hWnd = nullptr;
步骤 2:着色器代码(HLSL)
InstancedIndirect.hlsl文件,编写顶点 / 像素着色器,支持实例化语义SV_InstanceID// InstancedIndirect.hlsl
cbuffer CBPerFrame : register(b0)
{
matrix g_ViewProj; // 视图投影矩阵(示例简化,可自行传入)
};
// 顶点输入结构:基础顶点 + 实例数据
struct VSInput
{
float3 pos : POSITION;
float3 offset : INSTANCEOFFSET; // 实例化位置偏移
float4 color : INSTANCECOLOR; // 实例化颜色
};
struct PSInput
{
float4 pos : SV_POSITION;
float4 color : COLOR;
};
// 顶点着色器
PSInput VS(VSInput input)
{
PSInput output;
// 计算实例最终位置:基础顶点 + 实例偏移
float3 worldPos = input.pos + input.offset;
output.pos = mul(float4(worldPos, 1.0f), g_ViewProj);
output.color = input.color;
return output;
}
// 像素着色器
float4 PS(PSInput input) : SV_TARGET
{
return input.color;
}
步骤 3:D3D11 设备初始化(基础窗口 + 交换链)
HRESULT InitD3D(HWND hWnd)
{
g_hWnd = hWnd;
RECT rc;
GetClientRect(hWnd, &rc);
UINT width = rc.right - rc.left;
UINT height = rc.bottom - rc.top;
// 交换链描述
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(sd));
sd.BufferCount = 1;
sd.BufferDesc.Width = width;
sd.BufferDesc.Height = height;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
// 创建设备、交换链、即时上下文
UINT createDeviceFlags = 0;
#ifdef _DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
D3D_FEATURE_LEVEL featureLevel;
const D3D_FEATURE_LEVEL featureLevelArray[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, };
HRESULT hr = D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags,
featureLevelArray, ARRAYSIZE(featureLevelArray), D3D11_SDK_VERSION,
&sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pImmediateContext
);
if (FAILED(hr)) return hr;
// 创建渲染目标视图
ID3D11Texture2D* pBackBuffer = nullptr;
hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
if (FAILED(hr)) return hr;
hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_pRenderTargetView);
pBackBuffer->Release();
if (FAILED(hr)) return hr;
g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, nullptr);
// 设置视口
D3D11_VIEWPORT vp;
vp.Width = (FLOAT)width;
vp.Height = (FLOAT)height;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
g_pImmediateContext->RSSetViewports(1, &vp);
return S_OK;
}
步骤 4:创建绘制资源(核心)
HRESULT InitResources()
{
// ===================== 1. 创建基础顶点缓冲区(单个三角形,3个顶点)=====================
Vertex vertices[] =
{
{ XMFLOAT3(-0.1f, -0.1f, 0.0f) }, // 左下
{ XMFLOAT3( 0.1f, -0.1f, 0.0f) }, // 右下
{ XMFLOAT3( 0.0f, 0.1f, 0.0f) } // 顶部
};
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof(vertices);
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
D3D11_SUBRESOURCE_DATA vinitData;
vinitData.pSysMem = vertices;
HRESULT hr = g_pd3dDevice->CreateBuffer(&vbd, &vinitData, &g_pVertexBuffer);
if (FAILED(hr)) return hr;
// ===================== 2. 创建实例数据缓冲区(100个实例,随机位置+颜色)=====================
const UINT INSTANCE_COUNT = 100;
InstanceData instanceData[INSTANCE_COUNT];
srand(time(NULL));
for (UINT i = 0; i < INSTANCE_COUNT; i++)
{
// 随机位置偏移(-1.5~1.5范围,平铺屏幕)
instanceData[i].offset.x = (float)(rand() % 300 - 150) / 100.0f;
instanceData[i].offset.y = (float)(rand() % 300 - 150) / 100.0f;
instanceData[i].offset.z = 0.0f;
// 随机颜色(RGBA)
instanceData[i].color.x = (float)rand() / RAND_MAX;
instanceData[i].color.y = (float)rand() / RAND_MAX;
instanceData[i].color.z = (float)rand() / RAND_MAX;
instanceData[i].color.w = 1.0f;
}
D3D11_BUFFER_DESC ibd;
ZeroMemory(&ibd, sizeof(ibd));
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = sizeof(instanceData);
ibd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // 实例缓冲区绑定为顶点缓冲区
ibd.CPUAccessFlags = 0;
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = instanceData;
hr = g_pd3dDevice->CreateBuffer(&ibd, &iinitData, &g_pInstanceBuffer);
if (FAILED(hr)) return hr;
// ===================== 3. 创建间接绘制参数缓冲区(核心)=====================
DrawInstancedIndirectArgs indirectArgs;
indirectArgs.VertexCountPerInstance = 3; // 每个实例3个顶点(三角形)
indirectArgs.InstanceCount = INSTANCE_COUNT;// 100个实例
indirectArgs.StartVertexLocation = 0; // 从第0个顶点开始
indirectArgs.StartInstanceLocation = 0; // 从第0个实例开始
D3D11_BUFFER_DESC iabd;
ZeroMemory(&iabd, sizeof(iabd));
iabd.Usage = D3D11_USAGE_DEFAULT; // 支持GPU写入(若用CS更新需此类型)
iabd.ByteWidth = sizeof(DrawInstancedIndirectArgs);
iabd.BindFlags = D3D11_BIND_INDIRECT_ARGUMENT; // 必须绑定为间接参数标识
iabd.CPUAccessFlags = 0;
D3D11_SUBRESOURCE_DATA iainitData;
iainitData.pSysMem = &indirectArgs;
hr = g_pd3dDevice->CreateBuffer(&iabd, &iainitData, &g_pIndirectArgBuffer);
if (FAILED(hr)) return hr;
// ===================== 4. 编译着色器并创建输入布局=====================
// 编译顶点着色器
ID3DBlob* pVSBlob = nullptr;
hr = D3DCompileFromFile(
L"InstancedIndirect.hlsl", nullptr, nullptr, "VS", "vs_5_0",
D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &pVSBlob, nullptr
);
if (FAILED(hr)) return hr;
hr = g_pd3dDevice->CreateVertexShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), nullptr, &g_pVS);
if (FAILED(hr)) { pVSBlob->Release(); return hr; }
// 编译像素着色器
ID3DBlob* pPSBlob = nullptr;
hr = D3DCompileFromFile(
L"InstancedIndirect.hlsl", nullptr, nullptr, "PS", "ps_5_0",
D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &pPSBlob, nullptr
);
if (FAILED(hr)) { pVSBlob->Release(); return hr; }
hr = g_pd3dDevice->CreatePixelShader(pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), nullptr, &g_pPS);
if (FAILED(hr)) { pVSBlob->Release(); pPSBlob->Release(); return hr; }
// 定义输入布局(基础顶点 + 实例数据,注意INSTANCE_DATA语义)
D3D11_INPUT_ELEMENT_DESC ied[] =
{
// 基础顶点数据:每顶点一次
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
// 实例数据:每实例一次(关键:D3D11_INPUT_PER_INSTANCE_DATA)
{ "INSTANCEOFFSET", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "INSTANCECOLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 12, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
};
hr = g_pd3dDevice->CreateInputLayout(
ied, ARRAYSIZE(ied),
pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(),
&g_pInputLayout
);
pVSBlob->Release();
pPSBlob->Release();
if (FAILED(hr)) return hr;
// 设置着色器和输入布局
g_pImmediateContext->VSSetShader(g_pVS, nullptr, 0);
g_pImmediateContext->PSSetShader(g_pPS, nullptr, 0);
g_pImmediateContext->IASetInputLayout(g_pInputLayout);
// 设置图元拓扑为三角形列表
g_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
return S_OK;
}
步骤 5:绘制函数(核心:DrawInstancedIndirect 调用)
void Render()
{
// 清屏(深灰背景)
const float clearColor[] = { 0.2f, 0.2f, 0.2f, 1.0f };
g_pImmediateContext->ClearRenderTargetView(g_pRenderTargetView, clearColor);
// ===================== 绑定多顶点缓冲区(基础顶点 + 实例缓冲区)=====================
ID3D11Buffer* pVertexBuffers[] = { g_pVertexBuffer, g_pInstanceBuffer };
UINT strides[] = { sizeof(Vertex), sizeof(InstanceData) }; // 每个缓冲区的步长
UINT offsets[] = { 0, 0 }; // 偏移量
g_pImmediateContext->IASetVertexBuffers(0, 2, pVertexBuffers, strides, offsets);
// ===================== 核心:调用DrawInstancedIndirect间接绘制=====================
// 参数1:间接参数缓冲区
// 参数2:参数偏移(4字节对齐,此处仅一个参数结构,偏移0)
g_pImmediateContext->DrawInstancedIndirect(g_pIndirectArgBuffer, 0);
// 交换缓冲区(显示渲染结果)
g_pSwapChain->Present(1, 0);
}
步骤 6:资源释放与窗口消息处理
// 释放所有D3D11资源
void Cleanup()
{
if (g_pInputLayout) g_pInputLayout->Release();
if (g_pPS) g_pPS->Release();
if (g_pVS) g_pVS->Release();
if (g_pIndirectArgBuffer) g_pIndirectArgBuffer->Release();
if (g_pInstanceBuffer) g_pInstanceBuffer->Release();
if (g_pVertexBuffer) g_pVertexBuffer->Release();
if (g_pRenderTargetView) g_pRenderTargetView->Release();
if (g_pSwapChain) g_pSwapChain->Release();
if (g_pImmediateContext) g_pImmediateContext->Release();
if (g_pd3dDevice) g_pd3dDevice->Release();
}
// 窗口过程函数(基础实现,可根据工程扩展)
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
// 主函数(Windows程序入口)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// 注册窗口类
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L,
hInstance, nullptr, nullptr, nullptr, nullptr,
L"D3D11InstancedIndirectClass", nullptr };
RegisterClassEx(&wc);
// 创建窗口
HWND hWnd = CreateWindow(wc.lpszClassName, L"D3D11 DrawInstancedIndirect 示例",
WS_OVERLAPPEDWINDOW, 100, 100, 800, 600,
nullptr, nullptr, wc.hInstance, nullptr);
// 初始化D3D和资源
if (SUCCEEDED(InitD3D(hWnd)) && SUCCEEDED(InitResources()))
{
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// 消息循环
MSG msg = { 0 };
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
Render(); // 持续渲染
}
}
}
// 释放资源
Cleanup();
UnregisterClass(wc.lpszClassName, wc.hInstance);
return 0;
}
在cs中修改间接参数缓冲区
补充1:InstancedIndirect_CS.hlsl代码
// D3D11 DrawInstancedIndirect args (16 bytes): // [0] VertexCountPerInstance // [1] InstanceCount // [2] StartVertexLocation // [3] StartInstanceLocation RWByteAddressBuffer IndirectArgs : register(u0); cbuffer CB : register(b0) { uint gVertexCountPerInstance; uint gVisibleInstanceCount; uint gStartVertex; uint gStartInstance; }; [numthreads(1,1,1)] void CSMain(uint3 tid : SV_DispatchThreadID) { IndirectArgs.Store( 0, gVertexCountPerInstance); IndirectArgs.Store( 4, gVisibleInstanceCount); IndirectArgs.Store( 8, gStartVertex); IndirectArgs.Store(12, gStartInstance); }
补充2:初始化
// 创建ConstantBuffer D3D11_BUFFER_DESC cbd; ZeroMemory(&cbd, sizeof(cbd)); cbd.Usage = D3D11_USAGE_DYNAMIC; cbd.ByteWidth = sizeof(ConstantBuffer); cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; HR(g_pd3dDevice->CreateBuffer(&cbd, nullptr, g_pConstantBuffer)); // 创建IndirectArgBuffer的UAV D3D11_UNORDERED_ACCESS_VIEW_DESC uavd = {}; uavd.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; uavd.Format = DXGI_FORMAT_R32_TYPELESS; uavd.Buffer.FirstElement = 0; uavd.Buffer.NumElements = 16 / 4; // 4 DWORDs uavd.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW; ComPtr<ID3D11UnorderedAccessView> indirectArgsUAV; g_pd3dDevice->CreateUnorderedAccessView(g_pIndirectArgBuffer, &uavd, &indirectArgsUAV);
补充3:绘制之前使用cs修改间接参数缓冲区
// Compute shader:写 indirectArgs buffer g_pImmediateContext->CSSetShader(g_pCS, nullptr, 0); ID3D11UnorderedAccessView* uavs[] = { indirectArgsUAV.Get() }; UINT initialCounts[] = { 0 }; g_pImmediateContext->CSSetUnorderedAccessViews(0, 1, uavs, initialCounts); g_pImmediateContext->CSSetConstantBuffers(0, 1, g_pConstantBuffer); g_pImmediateContext->Dispatch(1, 1, 1); // DrawIndirect:读取 indirectArgs buffer g_pImmediateContext->VSSetShader(g_pVS, nullptr, 0); g_pImmediateContext->PSSetShader(g_pPS, nullptr, 0); // IASetVertexBuffers / IASetInputLayout / IASetPrimitiveTopology 等照常设置... g_pImmediateContext->DrawInstancedIndirect(g_pIndirectArgBuffer, 0);
核心技术点
1. GPU 动态更新间接参数:将间接参数缓冲区的Usage设为D3D11_USAGE_DEFAULT,通过计算着色器(CS) 修改InstanceCount(如视锥体裁剪后剔除不可见实例),实现 CPU 无介入的动态绘制
2. 索引化间接绘制:若需使用索引缓冲区,替换为DrawIndexedInstancedIndirect,参数结构体改为D3D11_DRAW_INDEXED_INSTANCED_INDIRECT_ARGS
3. 实例数据动态更新:将实例缓冲区设为D3D11_USAGE_DYNAMIC,通过Map/Unmap实时更新实例位置 / 颜色
4. 批量绘制:在间接参数缓冲区中存放多个DrawInstancedIndirectArgs结构,通过偏移量调用,实现一次绘制多个批次的实例
常见问题排查
1. 绘制无结果:检查输入布局的语义名 / 格式是否与 HLSL 一致,实例数据的D3D11_INPUT_PER_INSTANCE_DATA是否正确
2. 设备挂起:检查间接参数缓冲区的4 字节对齐,参数值是否合法(如VertexCountPerInstance不超过顶点缓冲区数量)
3. 实例无差异:检查多顶点缓冲区绑定的strides和offsets是否正确,实例数据的PER_INSTANCE_DATA步长是否为 1
4. 着色器编译失败:检查 HLSL 的语法,确保使用vs_5_0/ps_5_0(D3D11 最低支持)
DrawIndexedInstanced函数
ID3D11DeviceContext::DrawIndexedInstanced 是 Direct3D 11 中用于索引化实例化渲染的 API,它在 DrawIndexed 的基础上增加了 ** 实例化(Instancing)** 能力
适合高效绘制大量相同几何体(如树木、草地、粒子、重复建筑),通过一次绘制调用批量生成多个实例,大幅减少 API 调用开销与 CPU 负担。
函数原型
void DrawIndexedInstanced(
UINT IndexCountPerInstance, // 单个实例的索引数量
UINT InstanceCount, // 要绘制的实例总数
UINT StartIndexLocation, // 单个实例的起始索引偏移(同DrawIndexed)
INT BaseVertexLocation, // 单个实例的基顶点偏移(同DrawIndexed)
UINT StartInstanceLocation // 实例ID的起始偏移值
);
1. IndexCountPerInstance 单个实例需要的索引个数,与 DrawIndexed 的 IndexCount 逻辑一致,决定单个实例的图元数量(如单个立方体需 36 个索引)。
2. InstanceCount 本次批量绘制的实例总数,例如绘制 1000 棵树,该值设为 1000。
3. StartIndexLocation / BaseVertexLocation 与 DrawIndexed 完全相同,用于定位单个实例的顶点 / 索引数据,支持多个模型共享顶点 / 索引缓冲区。
4. StartInstanceLocation 实例 ID 的起始偏移,顶点着色器中读取的 SV_InstanceID 系统值 = 实际实例索引 + 该值,用于分批次绘制实例缓冲区中的不同片段。
核心原理
DrawIndexed 是「1 套顶点 / 索引数据 → 绘制 1 个物体」,而 DrawIndexedInstanced 是「1 套基础顶点 / 索引数据 + N 组实例数据 → 绘制 N 个物体」,核心是实例数据复用 + 批量绘制:两类数据分离
- ** per-vertex 数据(顶点级)**:存储单个物体的基础几何信息(位置、法线、UV),存入普通顶点缓冲区(VB),所有实例共享;
- ** per-instance 数据(实例级):存储每个实例的差异化信息(世界矩阵、颜色、缩放、纹理 ID),存入实例顶点缓冲区(Instance VB)**,每个实例独有一组数据。
管线数据读取逻辑
- 按
IndexCountPerInstance读取索引,组装单个实例的基础几何体; - 顶点着色器通过
SV_InstanceID系统值获取当前实例编号,从实例缓冲区读取对应实例数据(如自身的世界矩阵); - 结合顶点数据与实例数据,完成每个实例的独立变换(位置、旋转、缩放)与渲染。
使用前置条件
DrawIndexedInstanced 前,必须完成以下资源绑定与配置:① 绑定基础渲染资源(同 DrawIndexed)
索引缓冲区(IASetIndexBuffer):单个实例的索引数据;
普通顶点缓冲区(IASetVertexBuffers):单个实例的基础顶点数据;
图元拓扑(IASetPrimitiveTopology):如三角形列表 D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST。
② 绑定实例顶点缓冲区(核心新增步骤)
IASetVertexBuffers 绑定,且必须配置输入布局(Input Layout) 标记为实例数据,关键是设置 InputSlotClass 为 D3D11_INPUT_PER_INSTANCE_DATA,并指定 InstanceDataStepRate(通常为 1,即每个实例步进 1 次数据)。③ 顶点着色器接收实例数据
SV_InstanceID 获取实例编号,或直接接收实例缓冲区传入的参数(如世界矩阵、颜色),示例:// 顶点着色器输入结构
struct VSInput {
// per-vertex 数据(普通顶点属性)
float3 pos : POSITION;
float2 uv : TEXCOORD0;
// per-instance 数据(实例属性,从实例缓冲区传入)
float4x4 worldMat : INSTANCE_WORLD; // 每个实例的世界矩阵
float4 color : INSTANCE_COLOR; // 每个实例的颜色
};
// 顶点着色器主函数
VSOutput VS(VSInput input) {
VSOutput output;
// 结合世界矩阵、视图投影矩阵完成顶点变换
float4x4 wvp = mul(input.worldMat, g_ViewProj);
output.pos = mul(float4(input.pos, 1.0f), wvp);
output.uv = input.uv;
output.color = input.color;
return output;
}
使用示例
// 1. 绑定基础资源(索引、普通顶点、图元拓扑)
UINT stride[] = { sizeof(Vertex), sizeof(InstanceData) }; // 普通顶点步长 + 实例数据步长
UINT offset[] = { 0, 0 };
ID3D11Buffer* vbs[] = { pVertexBuffer, pInstanceBuffer }; // 普通VB + 实例VB
pDeviceContext->IASetVertexBuffers(0, 2, vbs, stride, offset); // 同时绑定两类VB
pDeviceContext->IASetIndexBuffer(pIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
pDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// 2. 绑定输入布局(包含per-vertex和per-instance属性)
pDeviceContext->IASetInputLayout(pInstanceInputLayout);
// 3. 调用DrawIndexedInstanced批量绘制
UINT indexPerInstance = 36; // 单个实例(立方体)的索引数
UINT instanceCount = 1000; // 绘制1000个实例
UINT startIndex = 0;
INT baseVertex = 0;
UINT startInstance = 0;
pDeviceContext->DrawIndexedInstanced(indexPerInstance, instanceCount, startIndex, baseVertex, startInstance);
核心优势
大幅降低 CPU 开销:1 次 API 调用绘制 N 个物体,避免循环调用 N 次 DrawIndexed,减少 CPU 与 GPU 的通信瓶颈;
显存利用率更高:共享基础几何数据,仅存储差异化实例数据,显存占用远低于「每个物体单独存顶点 / 索引」;
渲染效率提升:GPU 批量处理实例,减少管线状态切换,适合大规模重复物体渲染。
典型适用场景
游戏场景:草地、树木、石块、建筑群、粒子特效;
工业可视化:批量零件、重复设备模型;
通用 3D 渲染:大量相同几何体的场景(如点阵、网格物体)。
与 DrawIndexed / DrawInstanced 函数的区别
| 函数 | 索引支持 | 实例化支持 | 适用场景 |
|---|---|---|---|
DrawIndexed |
是 | 否 | 单个复杂模型、无重复几何体 |
DrawInstanced |
否 | 是 | 无索引的简单实例化几何体 |
DrawIndexedInstanced |
是 | 是 | 大规模重复的复杂模型(最常用) |
常见错误
1. 输入布局配置错误:实例数据的 InputSlotClass 未设为 D3D11_INPUT_PER_INSTANCE_DATA,或 InstanceDataStepRate 配置错误,会导致实例数据读取异常(所有实例显示相同)。
2. 实例缓冲区步长 / 数据不匹配:实例缓冲区的创建大小、步长与 IASetVertexBuffers 配置不一致,或实例数据结构与 HLSL 输入不匹配,会引发渲染崩溃或画面错乱。
3. 实例数量超限:InstanceCount 超过实例缓冲区存储的实例数据总数,会导致超出部分实例读取无效数据,出现画面异常。
4. SV_InstanceID 使用误区:SV_InstanceID 是从 0 开始的无符号整数,若依赖该值索引实例数据,需确保实例缓冲区数据量与 InstanceCount 匹配,避免越界。
实例数据的优化方式
1. 实例数据压缩:将世界矩阵拆分为平移、旋转、缩放向量,减少实例数据大小,提升显存带宽效率;
2. 动态实例缓冲区:对于动态变化的实例(如移动粒子),使用 D3D11_USAGE_DYNAMIC 类型的实例缓冲区,配合 Map/Unmap 更新数据;
3. 多实例缓冲区混合:可绑定多个实例缓冲区,分别存储不同类型的实例数据(如变换、材质 ID),灵活适配复杂场景。
DrawIndexedInstancedIndirect函数
DrawIndexedInstancedIndirect是 D3D11 中索引化 + 实例化 + 间接绘制的核心接口
在DrawInstancedIndirect基础上增加了索引缓冲区支持,适合绘制带索引的复杂模型的大规模实例化(如网格模型、三维物体),同样从 GPU 显存读取绘制参数,彻底减少 CPU-GPU 通信开销。
与 DrawInstancedIndirect的核心差异
1. 新增索引缓冲区绑定,支持顶点索引复用,减少顶点数据冗余;
2. 间接参数结构体为D3D11_DRAW_INDEXED_INSTANCED_INDIRECT_ARGS,比无索引版本多起始索引偏移和顶点偏移两个参数;
3. 绘制流程需额外绑定索引缓冲区,其余间接绘制核心逻辑一致。
核心参数结构体
// 索引化间接绘制核心参数(D3D11原生定义)
typedef struct D3D11_DRAW_INDEXED_INSTANCED_INDIRECT_ARGS {
UINT IndexCountPerInstance; // 每个实例的索引数量
UINT InstanceCount; // 实例总数
UINT StartIndexLocation; // 索引缓冲区的起始偏移(索引数,非字节)
INT BaseVertexLocation; // 顶点缓冲区的基础偏移(顶点数,可负数)
UINT StartInstanceLocation; // 实例缓冲区的起始偏移
} D3D11_DRAW_INDEXED_INSTANCED_INDIRECT_ARGS;
核心特点
1. 支持索引化实例化,是实际工程中最常用的间接绘制接口(复杂模型几乎都带索引);
2. 间接参数缓冲区需绑定D3D11_BIND_INDIRECT_ARGUMENT标识,Usage建议设为D3D11_USAGE_DEFAULT(支持 GPU 写入);
3. IndexCountPerInstance为 0 时,GPU 直接跳过绘制,无性能开销;
4. BaseVertexLocation用于顶点缓冲区子区域绘制,可实现多个模型共享一个顶点缓冲区。
完整可运行示例代码
DrawInstancedIndirect示例改造,核心实现100 个带索引的彩色正方形实例的间接绘制(正方形由 2 个三角形组成,共 6 个索引、4 个顶点,复用索引减少数据)步骤 1:头文件 + 全局变量(新增索引缓冲区)
#include <d3d11.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")
using namespace DirectX;
// D3D11核心对象
ID3D11Device* g_pd3dDevice = nullptr;
ID3D11DeviceContext* g_pImmediateContext = nullptr;
IDXGISwapChain* g_pSwapChain = nullptr;
ID3D11RenderTargetView* g_pRenderTargetView = nullptr;
// 绘制资源(新增索引缓冲区g_pIndexBuffer)
ID3D11Buffer* g_pVertexBuffer = nullptr; // 顶点缓冲区(4个顶点:正方形)
ID3D11Buffer* g_pIndexBuffer = nullptr; // 索引缓冲区(6个索引:2个三角形)
ID3D11Buffer* g_pInstanceBuffer = nullptr; // 实例数据缓冲区(位置+颜色)
ID3D11Buffer* g_pIndirectArgBuffer = nullptr;// 间接参数缓冲区
ID3D11VertexShader* g_pVS = nullptr;
ID3D11PixelShader* g_pPS = nullptr;
ID3D11InputLayout* g_pInputLayout = nullptr;
// 顶点/实例数据结构体(与无索引版本一致)
struct Vertex { XMFLOAT3 pos; };
struct InstanceData { XMFLOAT3 offset; XMFLOAT4 color; };
HWND g_hWnd = nullptr;
const UINT INSTANCE_COUNT = 100; // 实例总数
const UINT VERTEX_COUNT = 4; // 正方形顶点数
const UINT INDEX_COUNT = 6; // 正方形索引数
步骤 2:HLSL 着色器代码(与无索引版本完全一致)
IndexedInstancedIndirect.hlsl,无需修改,实例化语义和顶点变换逻辑通用:cbuffer CBPerFrame : register(b0) { matrix g_ViewProj; };
struct VSInput {
float3 pos : POSITION;
float3 offset : INSTANCEOFFSET;
float4 color : INSTANCECOLOR;
};
struct PSInput {
float4 pos : SV_POSITION;
float4 color : COLOR;
};
PSInput VS(VSInput input) {
PSInput output;
float3 worldPos = input.pos + input.offset;
output.pos = mul(float4(worldPos, 1.0f), g_ViewProj);
output.color = input.color;
return output;
}
float4 PS(PSInput input) : SV_TARGET { return input.color; }
步骤 3:D3D11 设备初始化(无修改)
InitD3D函数即可,此处省略(可参考上一篇示例的InitD3D)。步骤 4:创建绘制资源(核心:新增索引缓冲区 + 修改间接参数)
HRESULT InitResources()
{
// ===================== 1. 创建顶点缓冲区(4个顶点:正方形,中心在原点)=====================
Vertex vertices[VERTEX_COUNT] = {
{ XMFLOAT3(-0.1f, -0.1f, 0.0f) }, // 左下
{ XMFLOAT3( 0.1f, -0.1f, 0.0f) }, // 右下
{ XMFLOAT3(-0.1f, 0.1f, 0.0f) }, // 左上
{ XMFLOAT3( 0.1f, 0.1f, 0.0f) } // 右上
};
D3D11_BUFFER_DESC vbd = {};
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof(vertices);
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
D3D11_SUBRESOURCE_DATA vinitData = { .pSysMem = vertices };
HRESULT hr = g_pd3dDevice->CreateBuffer(&vbd, &vinitData, &g_pVertexBuffer);
if (FAILED(hr)) return hr;
// ===================== 2. 创建索引缓冲区(6个UINT索引:绘制2个三角形)=====================
UINT indices[INDEX_COUNT] = { 0,1,2, 1,3,2 }; // 正方形的三角带索引
D3D11_BUFFER_DESC ibd = {};
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = sizeof(indices);
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER; // 绑定为索引缓冲区
ibd.CPUAccessFlags = 0;
D3D11_SUBRESOURCE_DATA iinitData = { .pSysMem = indices };
hr = g_pd3dDevice->CreateBuffer(&ibd, &iinitData, &g_pIndexBuffer);
if (FAILED(hr)) return hr;
// ===================== 3. 创建实例缓冲区(100个实例,随机位置+颜色)=====================
InstanceData instanceData[INSTANCE_COUNT];
srand(time(NULL));
for (UINT i = 0; i < INSTANCE_COUNT; i++) {
instanceData[i].offset.x = (float)(rand() % 300 - 150) / 100.0f;
instanceData[i].offset.y = (float)(rand() % 300 - 150) / 100.0f;
instanceData[i].offset.z = 0.0f;
instanceData[i].color = XMFLOAT4((float)rand()/RAND_MAX, (float)rand()/RAND_MAX, (float)rand()/RAND_MAX, 1.0f);
}
D3D11_BUFFER_DESC instbd = {};
instbd.Usage = D3D11_USAGE_IMMUTABLE;
instbd.ByteWidth = sizeof(instanceData);
instbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
D3D11_SUBRESOURCE_DATA instInitData = { .pSysMem = instanceData };
hr = g_pd3dDevice->CreateBuffer(&instbd, &instInitData, &g_pInstanceBuffer);
if (FAILED(hr)) return hr;
// ===================== 4. 创建索引化间接参数缓冲区(核心修改)=====================
D3D11_DRAW_INDEXED_INSTANCED_INDIRECT_ARGS indirectArgs = {};
indirectArgs.IndexCountPerInstance = INDEX_COUNT; // 每个实例6个索引
indirectArgs.InstanceCount = INSTANCE_COUNT; // 100个实例
indirectArgs.StartIndexLocation = 0; // 索引从0开始
indirectArgs.BaseVertexLocation = 0; // 顶点从0开始
indirectArgs.StartInstanceLocation = 0; // 实例从0开始
D3D11_BUFFER_DESC iabd = {};
iabd.Usage = D3D11_USAGE_DEFAULT; // 支持GPU(CS)动态更新
iabd.ByteWidth = sizeof(indirectArgs);
iabd.BindFlags = D3D11_BIND_INDIRECT_ARGUMENT; // 必须的标识
D3D11_SUBRESOURCE_DATA iainitData = { .pSysMem = &indirectArgs };
hr = g_pd3dDevice->CreateBuffer(&iabd, &iainitData, &g_pIndirectArgBuffer);
if (FAILED(hr)) return hr;
// ===================== 5. 编译着色器+创建输入布局(无修改)=====================
ID3DBlob* pVSBlob = nullptr, *pPSBlob = nullptr;
hr = D3DCompileFromFile(L"IndexedInstancedIndirect.hlsl", nullptr, nullptr, "VS", "vs_5_0", D3DCOMPILE_DEBUG, 0, &pVSBlob, nullptr);
if (FAILED(hr)) return hr;
hr = D3DCompileFromFile(L"IndexedInstancedIndirect.hlsl", nullptr, nullptr, "PS", "ps_5_0", D3DCOMPILE_DEBUG, 0, &pPSBlob, nullptr);
if (FAILED(hr)) { pVSBlob->Release(); return hr; }
// 创建着色器
hr = g_pd3dDevice->CreateVertexShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), nullptr, &g_pVS);
hr |= g_pd3dDevice->CreatePixelShader(pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), nullptr, &g_pPS);
if (FAILED(hr)) { pVSBlob->Release(); pPSBlob->Release(); return hr; }
// 输入布局(基础顶点+实例数据,PER_INSTANCE_DATA关键)
D3D11_INPUT_ELEMENT_DESC ied[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "INSTANCEOFFSET", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "INSTANCECOLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 12, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
};
hr = g_pd3dDevice->CreateInputLayout(ied, ARRAYSIZE(ied), pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), &g_pInputLayout);
pVSBlob->Release(); pPSBlob->Release();
if (FAILED(hr)) return hr;
// ===================== 6. 绑定基础资源=====================
g_pImmediateContext->VSSetShader(g_pVS, nullptr, 0);
g_pImmediateContext->PSSetShader(g_pPS, nullptr, 0);
g_pImmediateContext->IASetInputLayout(g_pInputLayout);
g_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// 绑定索引缓冲区(核心:索引化绘制必须绑定)
g_pImmediateContext->IASetIndexBuffer(g_pIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
return S_OK;
}
步骤 5:渲染函数(核心:DrawIndexedInstancedIndirect 调用)
void Render()
{
// 清屏
const float clearColor[] = { 0.2f, 0.2f, 0.2f, 1.0f };
g_pImmediateContext->ClearRenderTargetView(g_pRenderTargetView, clearColor);
// 绑定顶点缓冲区+实例缓冲区(2个缓冲区,步长分别为顶点/实例结构体大小)
ID3D11Buffer* pVertexBuffers[] = { g_pVertexBuffer, g_pInstanceBuffer };
UINT strides[] = { sizeof(Vertex), sizeof(InstanceData) };
UINT offsets[] = { 0, 0 };
g_pImmediateContext->IASetVertexBuffers(0, 2, pVertexBuffers, strides, offsets);
// ===================== 核心:调用索引化间接绘制接口=====================
g_pImmediateContext->DrawIndexedInstancedIndirect(
g_pIndirectArgBuffer, // 间接参数缓冲区
0 // 参数字节偏移(4字节对齐,此处为0)
);
// 交换缓冲区显示
g_pSwapChain->Present(1, 0);
}
步骤 6:资源释放 + 窗口消息(无修改)
Cleanup、WndProc、WinMain函数,只需在Cleanup中释放索引缓冲区即可:void Cleanup()
{
// 新增释放索引缓冲区
if (g_pIndexBuffer) g_pIndexBuffer->Release();
if (g_pInputLayout) g_pInputLayout->Release();
if (g_pPS) g_pPS->Release();
if (g_pVS) g_pVS->Release();
if (g_pIndirectArgBuffer) g_pIndirectArgBuffer->Release();
if (g_pInstanceBuffer) g_pInstanceBuffer->Release();
if (g_pVertexBuffer) g_pVertexBuffer->Release();
if (g_pRenderTargetView) g_pRenderTargetView->Release();
if (g_pSwapChain) g_pSwapChain->Release();
if (g_pImmediateContext) g_pImmediateContext->Release();
if (g_pd3dDevice) g_pd3dDevice->Release();
}
关键注意事项
1. 索引缓冲区格式:必须与创建时一致,示例中为DXGI_FORMAT_R32_UINT(最常用),也可使用R16_UINT(节省显存);
2. 参数合法性:IndexCountPerInstance不能超过索引缓冲区总索引数,BaseVertexLocation偏移后顶点数不能越界,否则会导致设备挂起;
3. 四字节对齐:间接参数缓冲区的字节偏移必须是 4 的倍数,否则绘制失败;
4. GPU 动态更新:若需用计算着色器修改间接参数(如视锥体裁剪),只需将参数缓冲区Usage设为D3D11_USAGE_DEFAULT,通过CSSetUnorderedAccessViews绑定后由 CS 写入即可,参数结构体无需修改;
5. 多批次绘制:可在间接参数缓冲区中存放多个D3D11_DRAW_INDEXED_INSTANCED_INDIRECT_ARGS结构,通过修改字节偏移实现一次调用绘制多批次实例。
与其他绘制接口的性能对比
| 接口 | 特点 | 性能 / 适用场景 |
|---|---|---|
| DrawIndexedInstanced | 索引化 + 实例化,CPU 传参 | 小规模实例化,CPU 开销随实例数增加 |
| DrawInstancedIndirect | 无索引 + 间接,GPU 读参 | 简单模型(无索引)大规模实例化 |
| DrawIndexedInstancedIndirect | 索引化 + 实例化 + 间接 | 复杂模型大规模实例化(最优),CPU 零开销 |
浙公网安备 33010602011771号