-
Notifications
You must be signed in to change notification settings - Fork 0
MKX Rendering and Materials
How MKX SkeletalMesh rendering works — per-section textures, material system, UV mapping, and the discoveries made during implementation.
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.
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.
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.
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).
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 |
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_tintandenvironment_tintnames only
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.
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.
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 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.
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.
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
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.
NRS Asset Manager
Architecture
Game Formats
Game Documentation
- Injustice 2 (DCF2)
- Mortal Kombat X (MK10)
- Mortal Kombat 11 (MK11)
- Mortal Kombat 1 (MK12)
Export Handlers
Reference