WMO: Vertex Colors

WMOs, also known as MapObjs, make use of vertex colors to light everything from group geometry to units and game objects. These vertex colors are baked by the tooling employed by Blizzard’s artists–likely some kind of export plugin for the 3D modeling software they use. Vertex colors provide an efficient way of approximating a large number of lights at runtime.

The vertex colors baked into WMO data files undergo a few different runtime manipulations before being fed to the vertex and pixel shaders.

These manipulations are explained below.

CMapObj::AttenTransVerts #

If SMOHeader->flags is not flagged with 0x1, the client triggers the CMapObj::AttenTransVerts function. This function is called from inside CMapObj::RenderGroup, and only runs once per WMO group.

CMapObj::AttenTransVerts overwrites vertex alpha values based on the distance from the associated vertex to the nearest portal. The function only modifies vertex colors for vertices contained in trans batches. Vertex colors for vertices in int and ext batches are left alone.

CMapObjGroup::FixColorVertexAlpha #

The logic described in this section matches the behavior of the Wrath of the Lich King client. Newer versions of the client contain different / additional logic.

If a WMO group is flagged with SMOGroup::CVERTS (0x4) and SMOHeader->flags is not flagged with 0x8, the client triggers the CMapObjGroup::FixColorVertexAlpha function shown below. This function is called from inside CMapObjGroup::CreateOptionalDataPointers, and only runs once per WMO group.

CMapObjGroup::FixColorVertexAlpha overwrites vertex color values in the following ways:

Note that the WMO pixel shaders multiply the incoming color by 2, offsetting the division by 2 that occurs in FixColorVertexAlpha.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void CMapObjGroup::FixColorVertexAlpha() {

    if (this->colorVertexCount == 0) {
        return;
    }

    int32_t intBatchStart;

    // Identify the first post-trans-batch vertex
    if (this->transBatchCount > 0) {
        intBatchStart = this->batchList[this->transBatchCount].maxIndex + 1;
    } else {
        intBatchStart = 0;
    }

    for (int32_t i = 0; i < this->colorVertexCount; i++) {

        CImVector* color = &this->colorVertexList[i];

        // Int / ext batches
        if (i >= intBatchStart) {

            // Calculated adjusted color values
            int32_t r = (color->r + (color->a * color->r / 64)) / 2;
            int32_t g = (color->g + (color->a * color->g / 64)) / 2;
            int32_t b = (color->b + (color->a * color->b / 64)) / 2;

            // Overwrite existing color values
            color->r = std::min(r, 255);
            color->g = std::min(g, 255);
            color->b = std::min(b, 255);

            // Overwrite existing alpha value
            color->a = 255;

        // Trans batches
        } else {

            color->r /= 2;
            color->g /= 2;
            color->b /= 2;

        }

    }

}