DirectX11 使用Instancing技术提高重复模型的绘制效率

来源:互联网 发布:通过 相信 马克思 知乎 编辑:程序博客网 时间:2024/05/18 14:12

在游戏场景中,经常需要我们绘制大量相同的模型,比如英雄联盟中,战场上的小兵可以达到非常多的数量。

这里写图片描述

(图为英雄联盟游戏截图)

如果我们用以前的方法绘制,一个模型draw call一次,那么就会造成巨大的性能损耗。因为每次数据从内存传入显存都需要不少时间;而且每次draw call需要CPU和GPU进行周期同步,会导致CPU或GPU在等待;每次传入顶点数据后,都需要走一遍渲染管线,上下文也要进行切换等等。如果我们有一种方法,对于模型相同的游戏对象,只需要传一次模型的顶点数据,每个实例也可以包含不同的位置信息、颜色信息等,那么就可以极大增加渲染效率。DirectX10开始有Instancing技术的硬件支持,帮助我们完成解决这个问题。(这个问题在之前要通过程序员自己batch合并模型顶点来一次draw call来完成,而现在是通过硬件支持,更加方便)

一、指定输入布局

顶点元素描述如下:

typedef struct D3D11_INPUT_ELEMENT_DESC {    LPCSTR SemanticName;    UINT SemanticIndex;    DXGI_FORMAT Format;    UINT InputSlot;    UINT AlignedByteOffset;    D3D11_INPUT_CLASSIFICATION InputSlotClass;    UINT InstanceDataStepRate;} D3D11_INPUT_ELEMENT_DESC;

有几个新增的字段需要介绍一下,

  • InputSlotClass:为了区分顶点数据和实例数据,在输入布局元素中的InputSlotClass字段应当用D3D11_INPUT_PER_VERTEX_DATAD3D11_INPUT_PER_INSTANCE_DATA进行区分。
  • InstanceDataStepRate:指定每个实例数据有多少个实例要绘制。比如说我们有3种不同的实例颜色,但是有6个模型,我们可以设置2,那么前2个、中间2个、后面2个都分别占有不同的三种颜色。如果每个实例都有一个实例数据,那么就设为1。顶点数据只需要设为0。

我们的Demo中,实例数据包含了世界矩阵和每个实例的颜色,下面是一段代码示例:

const D3D11_INPUT_ELEMENT_DESC InputLayoutDesc::InstancedBasic32[8] ={{"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}};

二、顶点结构

因为矩阵包含了四行的四列浮点数,所以上面有四个WORD实例数据,代表着不同行。我们在着色器中定义的顶点结构如下:

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

三、绘制

我们不再用以前的Draw或DrawIndexed方法来绘制,而是要用DrawIndexedInstanced来使用instancing技术。它的函数声明如下:

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

IndexCountPerInstance:每个实例的索引个数。我们不需要指定所有实例的索引了,因为每绘制一个实例都会使用相同的索引。
InstanceCount:绘制实例的个数。
StartIndexLocation:索引的开始位置。
BaseVertexLocation:添加到索引开始前的基本顶点。
StartInstanceLocation:第一个实例在实例缓存开始的位置。

下面是一个调用实例:

md3dImmediateContext->DrawIndexedInstanced(mSkullIndexCount, // number of indices in the skull meshmVisibleObjectCount, // number of instances to draw0, 0, 0);

顶点着色器等函数与以前的也没有多大的区别,因为DirectX会自动处理不同的实例,将其当做一个模型的普通的顶点来处理即可:

struct VertexIn{    float3 PosL     : POSITION;    float3 NormalL  : NORMAL;    float2 Tex      : TEXCOORD;    row_major float4x4 World  : WORLD;    float4 Color    : COLOR;    uint InstanceId : SV_InstanceID;};struct VertexOut{    float4 PosH    : SV_POSITION;    float3 PosW    : POSITION;    float3 NormalW : NORMAL;    float2 Tex     : TEXCOORD;    float4 Color   : COLOR;};VertexOut VS(VertexIn vin){    VertexOut vout;    // Transform to world space space.    vout.PosW    = mul(float4(vin.PosL, 1.0f), vin.World).xyz;    vout.NormalW = mul(vin.NormalL, (float3x3)vin.World);    // Transform to homogeneous clip space.    vout.PosH = mul(float4(vout.PosW, 1.0f), gViewProj);    // Output vertex attributes for interpolation across triangle.    vout.Tex   = mul(float4(vin.Tex, 0.0f, 1.0f), gTexTransform).xy;    vout.Color = vin.Color;    return vout;}

最后程序运行结果:
这里写图片描述

程序示例中在不同的位置绘制不同颜色的大量相同模型,每帧只需要一次DrawIndexedInstanced调用,因此比起以前效率高了很多呢。

项目源代码:
https://github.com/ljcduo/Introduction-to-3D-Game-Programming-With-DirectX11/tree/master/Chapter%2015%20Instancing%20and%20Frustum%20Culling/InstancingAndCulling

原创粉丝点击