Skip to content
Stijn Herfst edited this page Sep 22, 2020 · 1 revision

In this document I will ramble a bit regarding MDX animation trivia. I will not go into file loading as an excellent MDX specification and guide on how to read the model into memory can be found here.

Interesting facts

Vertex Weights

When writing an animation system you will usually have several limitations to improve performance and make the implementation easier. For instance how many bones a vertex can be attached to. In theory WC3 classic does not have a limit on this, but in practice there are no models with vertices attached to more than 9 bones as can be seen in the graph.

In WC3 Reforged the maximum number of bones a vertex can be attached to is always 4. This number also seems to work well enough to support classic models, so you can easily use the same code/shader for both asset modes. Note that for WC3 classic all the bones a vertex is attached to are weighted the same and thus every additional bone lowers the total contribution of all the other bones. This is why even for a classic model that has 9 bones attached to a vertex, the visual difference will be small compared to limiting it to 4. In numbers:
With 4 bones every bone contributes 100%/4 = 25%
With 9 bones every bone contributes 100%/9 = ~11%

Reforged does have weights per bone per vertex in the SKIN chunk. The SKIN chunk is composed of 8 byte chunks. The first four bytes of the SKIN chunk are four bone numbers and then the other 4 bytes are four vertex weights. If you upload this as one big blob to the shader then you can get the matrices and interpolate them like so:

	const mat4 b0 = fetchMatrix(int(vSkin.x & 0x000000FF));
	const mat4 b1 = fetchMatrix(int(vSkin.x & 0x0000FF00) >> 8);
	const mat4 b2 = fetchMatrix(int(vSkin.x & 0x00FF0000) >> 16);
	const mat4 b3 = fetchMatrix(int(vSkin.x & 0xFF000000) >> 24);
	const float w0 = (vSkin.y & 0x000000FF) / 255.f;
	const float w1 = ((vSkin.y & 0x0000FF00) >> 8) / 255.f;
	const float w2 = ((vSkin.y & 0x00FF0000) >> 16) / 255.f;
	const float w3 = ((vSkin.y & 0xFF000000) >> 24) / 255.f;

	vec4 position = vec4(vPosition, 1.f);
	position = b0 * position * w0 + b1 * position * w1 + b2 * position * w2 + b3 * position * w3;
	position.w = 1.f;

Where fetchMatrix is a function that takes the bone ID and retrieves the matrix from some buffer.

There are four models in the game with more than 255 bones:

1.32.x/ui\glues\singleplayer\undead3d_exp\undead3d_exp.mdx
1.32.x/ui\glues\mainmenu\mainmenu3d\mainmenu3d.mdx
1.32.x/ui\glues\singleplayer\nightelfcampaign3d\nightelfcampaign3d.mdx
1.32.x/ui\glues\singleplayer\nightelf_exp\nightelf_exp.mdx

None of them HD models ofcourse as Reforged supports a maximum of 255 bones per MDX model.

Blend Mode Distribution

Had a feeling that Reforged models use transparent blendmodes less so I checked. 0/1 can be used in opaque drawing, 2 and up are transparent. The comparison is not entirely fair as the "classic" graph contains some ui elements and such, but it can still provide some insights.


We can see that Reforged doesn't use blendmode 5 and 6 at all. Them being:

5: gl->glBlendFunc(GL_ZERO, GL_SRC_COLOR);
6: gl->glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);

Furthermore we can see that blendmode 0 and 1 are very popular. 0 being completely opaque and 1 being effectively 1 bit alpha (on/off). The big number for columns 0 and 1 can be explained by Reforged having 3 layers for every material. These 3 layers are the albedo texture, a normal map and ORM map (occlusion, roughness, and metallic). These three layers are very probably drawn in a single pass compared to the classic layers which are drawn separately (but maybe this changed in Reforged).