Instancing & Skinning

Input layout entries for slot 1 use D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA with InstanceDataStepRate = 1.

24.1 Instancing with per-instance vertex data

Input layout entries for slot 1 use D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA with InstanceDataStepRate = 1. Draw with DrawIndexedInstanced(indexCount, instanceCount, ...).

struct InstancedIn
{
    float3   pos      : POSITION;      // slot 0, per vertex
    float3   normal   : NORMAL;
    float4x4 world    : INSTANCE_WORLD; // slot 1, per instance (occupies INSTANCE_WORLD0..3)
    float4   color    : INSTANCE_COLOR;
};

24.2 Instancing from a structured buffer (preferred today)

Combine with ExecuteIndirect and GPU culling for GPU-driven rendering. Remember SV_InstanceID does not include StartInstanceLocation — add it yourself (root constant, or SV_StartInstanceLocation in SM 6.8).

struct InstanceData { float4x4 world; float4 color; };
StructuredBuffer<InstanceData> gInstances : register(t0, space1);

struct InstOut { float4 pos : SV_Position; float3 n : NORMAL; float4 color : COLOR; };

InstOut InstancedVS(float3 pos : POSITION, float3 n : NORMAL, uint iid : SV_InstanceID)
{
    InstanceData inst = gInstances[iid];
    InstOut o;
    float4 posW = mul(inst.world, float4(pos, 1));
    o.pos   = mul(gViewProj, posW);
    o.n     = normalize(mul((float3x3)inst.world, n));
    o.color = inst.color;
    return o;
}

24.3 Skeletal animation (linear blend skinning)

Dual quaternion skinning avoids the "candy wrapper" volume loss of linear blending. Many engines skin in a compute shader once per frame and reuse the result for all passes (depth, shadows, main, motion vectors).

StructuredBuffer<float4x4> gBoneMatrices : register(t1, space1);   // bone → world (bind pose inverse included)

struct SkinnedIn
{
    float3 pos     : POSITION;
    float3 normal  : NORMAL;
    uint4  bones   : BLENDINDICES;   // DXGI_FORMAT_R8G8B8A8_UINT
    float4 weights : BLENDWEIGHT;    // DXGI_FORMAT_R8G8B8A8_UNORM, sum = 1
};

InstOut SkinnedVS(SkinnedIn i)
{
    float4x4 skin = gBoneMatrices[i.bones.x] * i.weights.x
                  + gBoneMatrices[i.bones.y] * i.weights.y
                  + gBoneMatrices[i.bones.z] * i.weights.z
                  + gBoneMatrices[i.bones.w] * i.weights.w;     // component-wise scale & sum is correct here
    float4 posW = mul(skin, float4(i.pos, 1));
    InstOut o;
    o.pos   = mul(gViewProj, posW);
    o.n     = normalize(mul((float3x3)skin, i.normal));
    o.color = 1;
    return o;
}

24.4 Morph targets (blend shapes)

StructuredBuffer<float3> gMorphDeltas : register(t2, space1);   // [target][vertex]
cbuffer Morph : register(b2, space1) { float4 gMorphWeights[2]; uint gVertexCount; };   // up to 8 targets

float3 ApplyMorphs(float3 pos, uint vid)
{
    [unroll] for (uint t = 0; t < 8; ++t)
        pos += gMorphDeltas[t * gVertexCount + vid] * gMorphWeights[t / 4][t % 4];
    return pos;
}

🧪 Part II Exercises

Write a cbuffer with float3, float, float2, float3, float[4], float4x4 members; compute every offset by hand, then verify with the -Fc disassembly and a static_assert on the matching C++ struct.

Convert a project from cbuffer syntax to ConstantBuffer<T> with explicit register(bN, spaceM) for per-frame, per-material, and per-draw data.

Author a root signature in HLSL with root constants, a root CBV, a descriptor table, and two static samplers; compile it with -T rootsig_1_1 and create the PSO from it.

Render a rotating, lit cube: world/view/projection matrices, inverse-transpose normals, and a camera you can orbit — verify you can switch between mul(v, M) and mul(M, v) conventions correctly.

Implement Blinn–Phong with directional, point, and spot lights stored in a StructuredBuffer<Light>; add a toon-shading toggle.

Add normal mapping with a TBN matrix; then implement the derivative-based version and compare quality.

Add a skybox and an environment-reflection shader whose blurriness depends on roughness via SampleLevel.

Build a post-processing chain: HDR target → bloom (downsample/upsample) → ACES tone mapping → vignette → sRGB back buffer.

Implement shadow mapping with PCF via SampleCmpLevelZero; then extend it to four cascades in a Texture2DArray.

Render 10,000 instances from a StructuredBuffer with per-instance colors; then skin an animated character with a bone-matrix buffer.