可可西

D3D11绘制函数详解

D3D11有如下几个绘制函数:

Draw函数

ID3D11DeviceContext::Draw 是 D3D11 中绘制非索引图元的函数,用于按顶点缓冲区的顺序渲染几何图形。

函数原型

void Draw(
  UINT VertexCount,    // 要绘制的顶点总数
  UINT StartVertexLocation  // 顶点缓冲区中起始顶点的索引(从0开始)
);
 

核心要点

1. 绘制前需完成顶点缓冲区绑定(IASetVertexBuffers)、输入布局绑定(IASetInputLayout)、图元拓扑设置(IASetPrimitiveTopology),否则绘制无效果。

2. 顶点数据直接按StartVertexLocationStartVertexLocation+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。   核心作用是多个模型共享同一个顶点缓冲区时,通过该参数区分不同模型的顶点段,无需拆分顶点缓冲区。

 

工作流程

1. 绑定资源前置条件
调用 DrawIndexed 前,必须通过 IASetIndexBufferIASetVertexBuffersIASetPrimitiveTopology 完成以下绑定:
  • 索引缓冲区(Index Buffer,IB):存储顶点索引序列;
  • 顶点缓冲区(Vertex Buffer,VB):存储顶点数据(位置、法线、纹理坐标等);
  • 图元拓扑(Primitive Topology):指定索引如何组装图元(如 D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST 三角形列表)。
 
2. 绘制执行逻辑
  • 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);
 

使用要点

1. 索引复用,减少显存占用
相比无索引绘制(Draw),DrawIndexed 可通过索引复用重复顶点(如立方体共享顶点),大幅减少顶点数据存储量,提升渲染效率。
 
2. 索引格式限制
索引缓冲区仅支持两种格式:
  • DXGI_FORMAT_R16_UINT(16 位索引,最大顶点数 65535)
  • DXGI_FORMAT_R32_UINT(32 位索引,支持更大规模模型)

 

3. 基顶点偏移的实用场景
多个模型合并存储在同一个顶点 / 索引缓冲区时,通过 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_DATAInstanceDataStepRate = 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. 性能优势:相比循环调用DrawDrawInstanced仅 1 次 CPU-GPU 通信,实例数越多,性能提升越明显(适用于 100 + 批量相同模型)。

4.SV_InstanceID:顶点着色器中可直接使用系统值SV_InstanceID(从 0 开始的实例编号),用于索引实例数据数组,无需手动在实例缓冲区中存储编号。

 

与 Draw函数的区别

 
函数渲染方式实例数据支持CPU 调用次数适用场景
Draw 单实例非索引 不支持 实例数 = 调用次数 单个模型渲染
DrawInstanced 多实例非索引 支持 1 次 批量相同非索引模型渲染
 

DrawInstancedIndirect函数 

ID3D11DeviceContext::DrawInstancedIndirect 是 D3D11 中基于间接参数缓冲区执行实例化绘制的核心接口
无需 CPU 传递绘制参数,直接从 GPU 显存读取实例化绘制的数量、起始索引等信息,适合动态批量绘制、GPU 驱动的实例化渲染场景。

核心特点

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:头文件与全局变量定义

先声明 D3D11 核心设备、上下文、资源对象,定义顶点 / 实例 / 间接参数结构体
#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 设备初始化(基础窗口 + 交换链)

基础的 D3D11 设备、交换链、渲染目标创建,适配窗口大小(宽 800、高 600 示例)
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:资源释放与窗口消息处理

实现资源销毁(避免内存泄漏)和基础窗口消息循环,适配 Windows 程序
// 释放所有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. 实例无差异:检查多顶点缓冲区绑定的stridesoffsets是否正确,实例数据的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   单个实例需要的索引个数,与 DrawIndexedIndexCount 逻辑一致,决定单个实例的图元数量(如单个立方体需 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) 标记为实例数据,关键是设置 InputSlotClassD3D11_INPUT_PER_INSTANCE_DATA,并指定 InstanceDataStepRate(通常为 1,即每个实例步进 1 次数据)。
 

③ 顶点着色器接收实例数据

在 HLSL 顶点着色器中,通过 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 定义的索引化间接绘制参数结构,参数需4 字节对齐,可由 CPU 初始化或 GPU(计算着色器)动态修改:
// 索引化间接绘制核心参数(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 个顶点,复用索引减少数据)
包含索引缓冲区创建、参数配置、间接绘制调用全流程,可直接整合到 D3D11 工程。
 

步骤 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:资源释放 + 窗口消息(无修改)

复用上篇的CleanupWndProcWinMain函数,只需在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 零开销
 

posted on 2026-02-28 09:48  可可西  阅读(26)  评论(0)    收藏  举报

导航