Compatible X-file HLSL-based vertex-blending with D3DX

On the whole, D3DX does a great job of making our lives easier, us Direct3D 9 programmers. But one topic that has generated a lot of confusion yet very little documentation is the correct usage of the BLENDINDICES shader semantic.

If you’re having trouble getting your CPU and GPU to communicate blend-indices correctly, the first thing to do is make sure that your source data is valid. If you’re using a custom mesh-loading routine then you’re on your own, but certainly under D3DXLoadMeshHierarchyFromX you can save yourself a lot of potential grief by prototyping with a reliable X-file. Support for X-file exporting is very shaky for most 3D modelling suites, but the SDK’s tiny.x sample is as close as you’ll get to a standard model. Once Tiny is happily running around in her fighter-pilot’s uniform you can start thinking about trying out your own models.

Nobody seems to be sure just what went wrong when the late ARB drafted up the usage semantics of blending parameters, but many current video cards lack hardware support for the UBYTE data type needed to store the indices. For this reason, developers have found themselves hammering unsigned-byte-quadruplets into the shape of D3DCOLORs, lying about the data content and praying that the vertex pipeline will have magically transformed the output into something that can be jimmied back into a tuple of UBYTEs. As far as I can see, this is the mother of all DirectX hacks, but it also seems to be the universal standard. That’s the only reason I’ll sleep at night after posting this.

After successfully calling ID3DXSkinInfo::ConvertToIndexBlendedMesh, the blend-index data is all present and correct. The challenge is to pipe it to the shader without misinterpretation. For that, I found it necessary to set all index data in the vertex declaration to be passed as D3DCOLOR data (DWORDS in disguise). By the way, if you are still using FVFs for something as finicky as GPU skinning then you don’t deserve any help: bite the bullet and write out those overly verbose, but oh-so-flexible vertex declarations.

// Fix UBYTE4 Support
D3DVERTEXELEMENT9 decl[MAX_FVF_DECL_SIZE];
skinned_mesh->GetDeclaration(decl);
{
    int i = 0;
    while (decl[i].Method != 0xFF)
    {
        if (decl[i].Usage == D3DDECLUSAGE_BLENDINDICES)
        {
            decl[i].Type = D3DDECLTYPE_D3DCOLOR;
            break;
        }
        ++i;
    }
}
skinned_mesh->UpdateSemantics(decl);

This takes care of the program code. All that remains is to interpret the data accordingly in the shader. For this, Microsoft generously provided us with the D3DCOLORtoUBYTE4 macro. HLSL (as of SM3) doesn’t have true integer support, let alone unsigned byte support, so we use their emulated int4 vector.

VS_OUTPUT VS_SkeletalBlend
(float3 pos           : POSITION0,
 float1 blend_weight  : BLENDWEIGHT0,
 float4 blend_indices : BLENDINDICES0,
 float3 normal        : NORMAL0,
 float2 tex_coord     : TEXCOORD0)
{
    int4 indices = D3DCOLORtoUBYTE4(blend_indices);
 
    float weight0 = blend_weight;
    float weight1 = 1 - weight0;
    int ind0 = indices.x;
    int ind1 = indices.y;
 
    // ...
}

Notice that the semantic here is BLENDINDICES, even though the vertex declaration says D3DCOLOR. Note further that the type becomes, somewhat wastefully, a float4 as all colour values are passed as such. The sample here is for a two-weights-per-vertex animation, but it’s easy enough to scale this up to four. If you need more than four weights then you need to have a word/fight with your artist, but the idea is the same, only you pass another parameter just like the first. Now I’m not certain that this is the best configuration - it certainly looks like a horrid mess - but it’s the only combination I know to work, after hours and hours of painful Googling. Suggestions for improvement are welcome.

Leave a Reply