Skip to content

MKX Rendering and Materials

thethiny edited this page Jun 21, 2026 · 1 revision

MKX Rendering & Materials

How MKX SkeletalMesh rendering works — per-section textures, material system, UV mapping, and the discoveries made during implementation.

3D Mesh Rendering Pipeline

Coordinate System

MKX uses UE3's left-handed coordinate system. OpenGL uses right-handed. Fix: negate X on all vertex positions during extraction (ue3_common.py line 256). Without this, the mesh appears mirrored and face/body texture regions don't align.

IJ2 has the same issue and applies the same fix.

Separated Vertex Buffers

Unlike IJ2 (which uses interleaved FSoftSkinVertex chunks), MKX stores vertex data in separated GPU buffers:

PositionVertexBuffer  → (x, y, z) float triplets, stride 12
TangentVertexBuffer   → packed normals, stride 8
WeightVertexBuffer    → bone indices + weights
UVVertexBuffer        → half-precision UVs, stride = num_texcoords × 4

The UV buffer interleaves multiple UV channels per vertex. With num_texcoords=2 and stride=8, each vertex has 8 bytes: UV0 (4 bytes) + UV1 (4 bytes). We read UV channel 0 by stepping halfs_per_vert = stride // 2 and taking the first 2 half-floats per vertex.

Per-Section Texture Rendering

Each mesh section references a material via material_index. The scene assembler resolves:

Section → MaterialIndex → Materials array → MIC export → Texture parameters → Texture2D export

Textures are loaded per-section and rendered as OpenGL display lists for real-time performance. Sections without textures render with vertex colors. Display lists are recompiled only when textures change.

Winding Order & Depth

Overlapping sections (e.g., eyes rendered on top of skin) use GL_LEQUAL depth testing to ensure proper layering. Section toggle checkboxes in the fullscreen viewer filter both index buffers AND texture arrays — unchecked sections are fully invisible (not darkened).

Material System

Texture Slots

MKX MaterialInstanceConstants reference textures via TextureParameterValues:

Parameter Name Purpose Notes
DiffuseMap Base color (albedo) Primary texture applied to mesh
NormalMap Surface normal detail Baked into diffuse as directional shading
Pmsk Material mask (PMSK) Channel-based region assignment
Graypack_Blue_Envmap Environment map Reflection/environment texture

Material Tint System

VectorParameterValues in MICs provide color tints. Only exact name matches are applied:

Tint Name Effect
Diffuse_Tint Multiplied with diffuse texture
Environment_Tint Applied to environment/reflection

Discovery: Initially, ANY VectorParameterValue was matched as a tint. This caused bugs:

  • Scorpion/Shinnok turned black (GRIME_Color = dark brown matched as tint)
  • IrisColor (eye color) applied to full body
  • Fix: restrict matching to exact diffuse_tint and environment_tint names only

PMSK (Material Mask)

PMSK stands for Material Mask, NOT Parameter Mask. Each PMSK channel's color value maps pixel regions to specific MaterialInstanceConstants. This enables:

  • Different materials on different body regions (skin vs armor vs cloth)
  • Per-region AlbedoTint application for costume recoloring

PMSK channels are NOT PBR parameters — they're a lookup table for which MIC controls each pixel.

AlbedoTint (P1/P2 Palettes)

PRIMARY_AlbedoTint and ALTERNATE_AlbedoTint in VectorParameterValues control mirror-match color variants:

  • P1 uses PRIMARY palette
  • P2 uses ALTERNATE palette
  • Values > 1.0 brighten/shift the masked regions

Found on _AP_MIC material variants. Requires PMSK channel reverse engineering to determine which regions receive the tint. Currently disabled — needs PMSK channel RE from game code.

RMA (Roughness/Metallic/AO)

MKX uses packed RMA textures where:

  • R channel = Roughness
  • G channel = Metallic
  • B channel = Ambient Occlusion

These are PBR parameters that require GLSL shaders for proper rendering. The current fixed-function OpenGL renderer does not use RMA data. Proper PBR would need:

  • Cook-Torrance BRDF lighting model
  • Per-pixel normal mapping from NormalMap
  • Cubemap environment reflections
  • Metallic/roughness material workflow

Normal Map Handling

Normal maps are baked into the diffuse texture as directional shading:

  • Sample the normal map's RGB as a direction vector
  • Compute a simple NdotL lighting factor
  • Multiply the diffuse color by this factor

The normal map's alpha channel contains height/opacity data:

  • For ice materials: alpha = height map (used for ambient blue boost and ice overlay effect)
  • For regular materials: alpha is typically 1.0 (opaque)

Ice/subsurface materials receive an ambient blue color boost based on the normal alpha.

Duplicate Export Resolution

Character meshes can have duplicate export names (e.g., EyesMouth_MIC exists in both HQMaterials/ and Materials/). The mesh's material references point to specific export indices. Resolution: when the materials_map contains a candidate reference, prefer that over the first name match.

Example: SubZero's mesh references export index 494 (Materials/EyesMouth_MIC), but name lookup finds index 478 (HQMaterials/EyesMouth_MIC) first. Fix: check if the candidate ref exists in the materials_map before falling back to name matching.

Rendering Limitations

The current renderer uses fixed-function OpenGL with baked textures. Missing features:

  • PBR shader pipeline (GLSL)
  • Per-pixel metallic/roughness from RMA
  • Real-time normal mapping (currently baked)
  • Environment reflections from cubemaps
  • Refraction and transparency
  • PMSK-masked AlbedoTint palettes

Sonya Purple Hair Bug

With Material Tints enabled, Sonya's hair renders purple instead of blonde. Suspected cause: a wrong VectorParameterValue is being matched as a tint for the hair material. Needs investigation — the hair MIC likely has a non-tint vector param being incorrectly applied.

Clone this wiki locally