Materials (Metroid Prime)
The format for materials is seen in both the CMDL and MREA formats and is identical in both. This particular material format appears in both Metroid Prime and Metroid Prime 2 with minor differences.
This file format is almost completely documented There are a couple unknown values left. Also, basically everything that was introduced in Echoes (two general settings and a vertex attribute) is unknown. |
GX Overview
The materials system in Metroid Prime is heavily dependent on the GameCube and Wii's graphics system, GX. GX is a fixed-function graphics pipeline, similar to old versions of OpenGL. In GX, rendering is done through a series of steps called Texture EnVironment Stages, or or TEV stages for short.
Prior to TEV stages, per-vertex calculations are performed. Lighting calculations are performed and passed to GX as a rasterized vertex color; texture coordinate generation (texgen) is also performed and made available to TEV.
How TEV stages actually work: Each TEV stage takes in four color (RGB) inputs and four alpha inputs, from one of eight sources. Then these four input colors are combined into one output color, and the output is saved into one of four registers, which can subsequently be used as input in the next TEV stages. The final stage must always save its output into the "previous TEV stage" register; that register is used as the final pixel color that gets displayed onscreen.
Material Set Format
Materials come as part of a set; often there will only be one set per file, but many CMDLs can have more than one. The set begins with a short header before the actual material data begins.
Type | Count | Description |
---|---|---|
long | 1 | Texture count (TC) |
long | TC | Texture file IDs |
long | 1 | Material count (MC) |
long | MC | Material end offsets (relative to the start of the first material) |
Materials begin |
Material Format
General Settings
Each material begins with a flags value, followed by a list of texture indices:
Type | Count | Description |
---|---|---|
long | 1 | Flags |
long | 1 | Texture count (TC) |
long | TC | Texture indices (these are indices into the material set texture array) |
long | 1 | Vertex attribute flags |
long | 1 | Unknown (note: only in Echoes) |
long | 1 | Unknown (note: only in Echoes) |
long | 1 | Unknown |
long | 1 | Konst count (KC); max setting is 4. (note: only present if flag 0x8 is set!) |
long | KC | Konst colors - 32-bit RGBA colors (note: not present if the Konst count isn't) |
short | 1 | Blend destination factor |
short | 1 | Blend source factor |
long | 1 | Unknown (note: only present if flag 0x400 is set!) |
long | 1 | Color channel count (CC) |
long | CC | Color channel flags (note: only one flag value, but you must advance 4*CC bytes instead of 4 for some reason) |
long | 1 | TEV stage count |
Flags
These are the known flag settings:
Bit | Hex | Description |
---|---|---|
0 | 0x1 | Appears unused, but always set |
1 | 0x2 | Appears unused, but always set |
2 | 0x4 | Appears unused, but always unset |
3 | 0x8 | Enable Konstant values |
4 | 0x10 | Mark transparent material |
5 | 0x20 | Enable punchthrough alpha |
6 | 0x40 | Enable Samus's reflection |
7 | 0x80 | Enable depth writing |
8 | 0x100 | Unknown |
9 | 0x200 | Don't draw |
10 | 0x400 | Enable second unknown value |
11 | 0x800 | Unknown |
12 | 0x1000 | Unknown |
13 | 0x2000 | Enable lightmaps; first UV coordinate uses short array instead of float array |
14 | 0x4000 | Unknown |
15 | 0x8000 | Unknown |
16 | 0x10000 | Unknown |
17 | 0x20000 | Unknown |
18 | 0x40000 | Unknown |
19 | 0x80000 | Unknown |
20+ | 0x100000+ | Unused; never set |
Vertex Attribute Flags
These flags are generally toggled in pairs, with each pair corresponding to a vertex attribute; if a pair is set, then vertices using this material will have the corresponding attribute. This is vital for reading geometry.
An important note is that while the leftmost byte is not used in Prime 1, in Echoes there's occasionally a value there that toggles an extra vertex attribute preceding position. This needs research to determine what the new attribute actually is. Also, GX supports up to 8 texture coords per vertex, but the game doesn't seem to allow you to assign more than 7 (though this could do with some double-checking).
These are the possible attributes:
Bit pair | Attribute |
---|---|
0x3 | Position |
0xC | Normal |
0x30 | Color 0 |
0xC0 | Color 1 |
0x300 | Tex 0 |
0xC00 | Tex 1 |
0x3000 | Tex 2 |
0xC000 | Tex 3 |
0x30000 | Tex 4 |
0xC0000 | Tex 5 |
0x300000 | Tex 6 |
Konst Colors
These values are only present when flag 0x8 is enabled. These allow you to set Konstant values, which can subsequently be used as inputs in TEV stages. The maximum number of Konst values you can set on one material is 4. The colors themselves are simply 32-bit RGBA values.
Blend Mode
The two blend factors set the blending mode used. The most common values you'll see are 0/1, which is used on opaque materials; transparent materials will usually have either 1/1, for additive blending, or 5/4, for alpha blending. Here's the full range of possible settings for each value:
Value | Blend factor |
---|---|
0 | GX_BL_ZERO |
1 | GX_BL_ONE |
2 | GX_BL_SRCCLR / GX_BL_DSTCLR |
3 | GX_BL_INVSRCCLR / GX_BL_INVSRCCLR |
4 | GX_BL_SRCALPHA |
5 | GX_BL_INVSRCALPHA |
6 | GX_BL_DSTALPHA |
7 | GX_BL_INVDSTALPHA |
Color Channels
The color channels data consists of a count value indicating the number of color channels, and then one set of flags that controls the COLOR0 channel. The flags correspond to arguments passed to GX_SetChanCtrl(). These are the settings:
Bits | Hex | Description |
---|---|---|
0 | 0x1 | Enable lighting (enable) |
1 | 0x2 | Ambient color source (ambsrc) |
2 | 0x4 | Material color source (matsrc) |
3-10 | 0x7F8 | Light mask; always 0, filled in at runtime (litmask) |
11-12 | 0x1800 | Diffuse function (diff_fn) |
13-14 | 0x6000 | Attenuation function (attn_fn) |
There's only one set of flags, which operates on COLOR0. COLOR1's settings can't be directly modified. Here's how the other color channel settings work.
COLOR1A1's settings vary depend whether shadowmaps are enabled (they are enabled for the arm cannon and morph ball; otherwise they'll be disabled):
- No processing will be done if the count value is 0. Otherwise, the count value is ignored; the game will always set up both color channels.
- COLOR1A1's ambient color is black, and its material color is white.
- COLOR1 will use the diffuse function GX_DF_CLAMP and the attenuation function GX_AF_SPOT. It will have lighting disabled entirely if there are no lights in range
If shadowmaps are disabled:
- COLOR1 is disabled if the count value is not 2. If it is 2, then its ambient color will be black and its material color will be white.
COLOR0 is set up the same way either way:
- COLOR0 will use the flags from the material, with the light mask merged in. It will also have lighting disabled if there are no lights in range.
- If there are no lights in range, COLOR0A0 will have its material color set to match its ambient color. Otherwise, its material color will be set to white.
Finally, an important note is that the formatting of the color channel info is bugged. There is always a color channel count and a single set of flags; 8 bytes total. However, for some reason the game is expecting one set of flags per color channel. To reach the TEV stages, you need to advance (4 * count) bytes past the count value, rather than 4. A likely explanation is that there was one set of flags per channel earlier in development, and the material loading code wasn't fully updated when this was changed. It's not normally an issue because the count is always 1 in every file the game uses, but beware for experimentation/custom materials.
TEV Stages
There'll be one of these structures per TEV stage:
Offset | Size | Description |
---|---|---|
0x0 | 4 | Color Input Flags |
0x4 | 4 | Alpha Input Flags |
0x8 | 4 | Color Combine Flags |
0xC | 4 | Alpha Combine Flags |
0x10 | 1 | Padding |
0x11 | 1 | Konst Alpha Input (possible values) |
0x12 | 1 | Konst Color Input (possible values) |
0x13 | 1 | Rasterized Color Input (possible values) |
0x14 | End of TEV stage |
After looping through each TEV stage, there will be one of these structures per stage:
Offset | Size | Description |
---|---|---|
0x0 | 2 | Padding |
0x2 | 1 | Texture TEV Input |
0x3 | 1 | Tex Coord TEV Input |
0x4 | End of structure |
Color Input Flags
These flags set the four color inputs that are used by the TEV stage. Each color is allocated 5 bits, even though only 4 are actually used; the lower bits correspond to the lower input numbers (eg. the bottom 5 bits refer to the first input).
There are 16 possible color sources:
Binary | Hex | Color Source |
---|---|---|
00000 | 0x0 | Previous stage RGB |
00001 | 0x1 | Previous stage AAA |
00010 | 0x2 | Color 0 RGB |
00011 | 0x3 | Color 0 AAA |
00100 | 0x4 | Color 1 RGB |
00101 | 0x5 | Color 1 AAA |
00110 | 0x6 | Color 2 RGB |
00111 | 0x7 | Color 2 AAA |
01000 | 0x8 | Texture RGB |
01001 | 0x9 | Texture AAA |
01010 | 0xA | Rasterized RGB |
01011 | 0xB | Rasterized AAA |
01100 | 0xC | One |
01101 | 0xD | Half |
01110 | 0xE | Konstant RGB |
01111 | 0xF | Zero |
Alpha Input Flags
Similar to the color input flags, these set the four alpha inputs used by the TEV stage. Each value is allocated 5 bits, although only 3 are used. The main difference with alpha is that there are only 8 possible sources instead of 16:
Binary | Hex | Alpha Source |
---|---|---|
00000 | 0x0 | Previous stage alpha |
00001 | 0x1 | Color 0 alpha |
00010 | 0x2 | Color 1 alpha |
00011 | 0x3 | Color 2 alpha |
00100 | 0x4 | Texture alpha |
00101 | 0x5 | Rasterized alpha |
00110 | 0x6 | Konstant alpha |
00111 | 0x7 | Zero |
Color Combine Flags
These flags specify how the operation that combines the four input colors into one output color is performed.
This is the combiner function:
tevrigid = (d (tevop) ((1.0-c)*a + c*b) + tevbias) * tevscale;
The values set in the color operation flags correspond to the parameters passed to GX_SetTevColorOp(). These are the settings:
Bits | Hex | Description |
---|---|---|
0-3 | 0xF | Combiner operator (tevop) |
4-5 | 0x30 | Bias (tevbias); 00 does nothing; 01 adds 0.5; 10 subtracts 0.5; 11 does nothing. |
6-7 | 0xC0 | Scale (tevscale); 00 does nothing; 01 multiples by 2; 10 multiplies by 4; 11 divides by 2. |
8 | 0x100 | Clamp flag; enabling this will clamp the output color between 0 and 1. |
9-10 | 0x600 | Output register (tevrigid); can be Previous Stage or Color 0/1/2 |
11+ | 0xFFFFF800 | Unused. |
Note that the vast majority of materials in the game, if not all of them, don't really bother with any of this; they enable clamping, set an output register, and leave everything else at their defaults of 0.
Alpha Combine Flags
This is exactly the same as the color combine flags; the only difference is it operates on alpha instead of color.
Texgen
After the TEV stages comes a sequence of flags determining how texgen is executed. Texgen is the process of taking input values and using them to generate texture coordinates (or UV coordinates), which can then be used by the TEV stages. It's important to note that any vertex attribute can be used as a texgen input; in fact, it's rather common for materials to use the vertex normal as an input to simulate reflections. That means the number of texture coords present on each vertex is not the same as the number of texture coords available to TEV.
Type | Count | Description |
---|---|---|
long | 1 | Texgen count (TC) |
long | TC | Texgen flags |
The flags correspond to arguments passed to GX_SetTexCoordGen2(). These are the settings:
Bits | Hex | Description |
---|---|---|
0-3 | 0xF | Texture coord generation type (tgen_typ) |
4-8 | 0x1F0 | Texture coord source (tgen_src) |
9-13 | 0x3E00 | Texture matrix index (mtxsrc - add 30) |
14 | 0x4000 | Normalize flag - normalizes texture coord after it's multiplied by the texture matrix (normalize) |
15-20 | 0x1F8000 | Post-transform texture matrix index (postmtx - add 64) |
21+ | 0xFFE00000 | Unused |
UV Animations
The UV animations section immediately follows the texgen flags. Its purpose is to generate texture matrices and post-transform matrices, which are then used to transform UV coordinates. This is commonly used to animate textures via simple UV scrolls, but it's also often used to simulate reflective surfaces that move with the camera. Materials can have multiple UV animations; in that case, each animation generates a separate texture/post-transform matrix and are loaded into GX sequentially. Texgen is used to set which matrices are used by which UV coordinates (if any).
The section starts with this short header:
Offset | Size | Description |
---|---|---|
0x0 | 4 | Material animations section size (includes animation count, so will be 4 at minimum) |
0x4 | 4 | Animation count |
The structure of the animations themselves is somewhat simple. Each animation has a 32-bit mode setting, followed by a number of float parameters. The number and usage of these float parameters varies depending on what mode is set. There are 8 possible modes.
For all of the following modes, s refers to seconds mod 900.
Mode 0: Inverse ModelView Matrix (No Translation)
This mode is commonly used with vertex normals to simulate reflective surfaces using spheremaps that move with the camera. It takes no parameters, and will generate both a texture matrix and a post-transform matrix. Translation is ignored.
The texture matrix is calculated like this. Note that the multiplication of the view matrix by the model matrix should be modified slightly rather than being a straight multiplication to ignore translation on the model matrix. (The game uses a function called MultiplyIgnoreTranslation for this.)
texmtx = inverse(ViewMatrix) * ModelMatrix; texmtx[0][3] = texmtx[1][3] = texmtx[2][3] = 0;
The post-transform matrix is a constant value. [1][1] and [1][2] may need to be swapped around for correct playback.
0.5, 0.0, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0
Mode 1: Inverse ModelView Matrix
This mode is nearly identical to mode 0; the only difference is that translation is left as-is. The multiplication of the view matrix by the model matrix actually is a straight multiplication in this mode, and the translation values on the texture matrix aren't set to 0.
Mode 2: UV Scroll
This mode is used to scroll both U and V at the same time. It will only generate a texture matrix. It has four float parameters: offsetA, offsetB, scaleA, and scaleB.
uOffset = (s * scaleA) + offsetA; vOffset = (s * scaleB) + offsetB;
Mode 3: Rotation
This mode rotates the texture. It will only generate a texture matrix. It has two float parameters: offset and scale.
float angle = (s * scale) + offset; float acos = cos(angle); float asin = sin(angle); float translateX = (1.0 - (acos - asin)) * 0.5; float translateY = (1.0 - (asin + acos)) * 0.5;
The resulting texture matrix is laid out as:
acos, -asin, 0.0, translateX, asin, acos, 0.0, translateY, 0.0, 0.0, 1.0, 0.0
Mode 4/5: Horizontal/Vertical Filmstrip
These modes can be used to create a filmstrip-like effect. The texture steps a set distance each animation frame, rather than the scroll being smoothly interpolated every game frame. The same calculation is done for both modes, with the only difference being whether the calculated offset is applied to the U or V coordinate. It will only generate a texture matrix. There are four float parameters: scale, numFrames, step, and offset.
The animation is made up of a number of pseudo-frames, where step controls the amount that the texture scrolls by each frame, and numFrames sets how many frames are iterated through before resetting back to 0. scale roughly controls animation playback speed, and offset modifies the time input value.
float value = step * scale * (offset + s); float uv_offset = (float)(short)(float)(numFrames * fmod(value, 1.0f)) * step;
Mode 6: Model Matrix
This mode is similar to modes 0 and 1 in that it simulates reflective surfaces, but it only takes the model matrix into account, which means camera movement doesn't affect the reflection. It takes no parameters and it generates both a texture matrix and a post-transform matrix.
The texture matrix is simply the model matrix, with the translation values set to 0. The post-transform matrix is set to the following:
0.5, 0.0, 0.0, ModelMatrix[0][3] * 0.50000001, 0.0, 0.0, 0.5, ModelMatrix[1][3] * 0.50000001, 0.0, 0.0, 0.0, 1.0
Mode 7: Mode-Who-Must-Not-Be-Named
Mode 7 takes two parameters and generates both a texture matrix and a post-transform matrix. We don't know what to name it.
The texture matrix is generated the same way as in mode 0; translation is ignored.
texmtx = inverse(ViewMatrix) * ModelMatrix; texmtx[0][3] = texmtx[1][3] = texmtx[2][3] = 0;
The post-transform matrix is where it gets a little complicated. A little math is required to calculate some values:
float xy = ((ViewMatrix[0][3] + ViewMatrix[1][3]) * 0.025f * ParamB; xy = (xy - (int) xy); // This truncates the integer portion of the value, leaving only the fractional part (mantissa). float z = ViewMatrix[2][3] * 0.05f * ParamB; z = (z - (int) z); float halfA = ParamA * 0.5f;
The post-transform matrix is then constructed as:
halfA, 0.0f, 0.0f, xy, 0.0f, 0.0f, halfA, z, 0.0f, 0.0f, 0.0f, 1.0f