Shader API (surface.hlsl)
The PackShade contract, PackSurface inputs, PackResult outputs, built-in helper functions, and engine variables.
Entry Point Contract
Every shader pack must provide a single entry function in surface.hlsl:
PackResult PackShade(PackSurface s);
This function runs in the scene pass for every pixel of the character’s materials, replacing the default MMD shading model.
Compilation Environment and Rules
- Compiler: Built using DirectX Shader Compiler (DXC), targeting Shader Model 6.0 (and 6.5 for ray tracing pipelines).
- HLSL Dialect:
-HV 2018(HLSL 2018 conformance). - Includes (
#include): File includes insidesurface.hlslresolve relative to the pack folder containing the file. - Reserved Words: DXC reserves the token
line; avoid using it as variable or identifier names. - Shader Cache: Compiled on first draw when a model opts into the pack, stored in
<MMDX12>/shader_cache. Modifying any file inside the pack folder invalidates the cache and triggers automatic recompilation.
Input: PackSurface
Passed from the engine’s rasterizer or ray generation pass:
| Field | Type | Description |
|---|---|---|
worldPos | float3 | Surface position in world space (MMD units, +Y up) |
N | float3 | World normal vector (automatically flipped towards camera on back faces) |
V | float3 | Unit vector towards the camera eye |
L | float3 | Unit vector towards the primary directional sun light |
uv | float2 | Material texture coordinates |
pixel | float2 | Window pixel position (SV_Position.xy) |
viewZ | float | View-space linear depth |
tex | float4 | Base albedo texture in gamma space with material morph factors applied (pure white if untextured) |
alpha | float | Combined material alpha multiplied by texture alpha (pixels below 0.004 are discarded earlier) |
shadow | float | Directional sun shadow factor (0 = full shadow, 1 = unshadowed; 1 if material ignores shadows) |
materialClass | uint | Classified material ID: PACK_BODY(0), PACK_SKIN(1), PACK_FACE(2), PACK_EYE(3), PACK_HAIR(4), PACK_WEAPON(5) |
Head Bone Coordinate Frame
Anime faces often suffer from harsh geometric shading around noses and cheekbones when lit by surface normals. To remedy this, MMDX12 provides the posed orientation frame of the character’s head bone:
| Field | Type | Description |
|---|---|---|
hasHead | bool | True if a head bone (頭 or 首) was located; false falls back to default world axes |
headPos | float3 | World position of the head bone joint |
headRight | float3 | Model’s right unit vector (+X) |
headUp | float3 | Model’s upward unit vector (+Y) |
headForward | float3 | Direction the face is looking (MMD models face -Z) |
headScale | float | Display scale factor (world units per model unit) |
Output: PackResult
The structure returned by PackShade:
struct PackResult {
float3 color; // Linear HDR radiance (must multiply by gSunIntensity)
float alpha; // Output opacity
float reflectivity; // 0.0 .. 1.0 (screen-space SSR and ray-traced reflection weight)
bool noAo; // When true, ambient occlusion (SSAO/RTAO) and reflections are disabled (recommended for faces)
};
Built-in Helper Functions (pack_api.hlsli)
float PackParam(uint i): Retrieves the current slider value for parameter indexi(0 to 15).- Material Values (Gamma space, material morphs applied):
float4 PackDiffuse(): Diffuse color and alpha.float3 PackAmbient(): Ambient reflection factor.float3 PackSpecular(): Specular color.float PackSpecularPower(): Specular exponent (roughness/gloss).float PackEdgeReflectivity(): Edge outline reflectivity.
- MMD Classic Shading Helper:
float3 PackMmdLit(PackSurface s): Calculates traditional MMD lighting:saturate(ambient + diffuse * lightColor) * tex.rgb.
- Sphere Mapping:
uint PackSphereMode(): Model sphere mode (0 = None, 1 = Multiply, 2 = Add).float3 PackSampleSphere(float3 N): Samples the sphere map using view-space transformed normal.
- MMD Toon Ramp:
bool PackHasToon(): True if the material has an assigned MMD toon texture.float3 PackSampleToon(float v): Samples the toon gradient (v = 0.0at the lit end,v = 1.0at the shadowed end).
Engine Scene Variables
Visible from common engine headers:
gLightColor(float3): Directional sun color.gSunIntensity(float): Global light intensity multiplier.gRimColor(float3),gRimStrength(float): Global rim lighting settings.float3 Hemisphere(float3 n): Ambient sky-ground hemispherical light interpolation.float3 SrgbToLinear(float3 c): Gamma sRGB to linear HDR color space conversion.
Pack Textures (API 2)
Textures declared in pack.json ("textures", see Manifest) are sampled by index, in array order:
float4 PackSampleTex(uint i, float2 uv): textureiwith its declared address mode (wrap/clamp).float4 PackSampleTexLevel(uint i, float2 uv, float lod): explicit mip level (ramps and data maps:lod = 0). Also works outside the pixel stage, e.g. inPackEdge.uint2 PackTexSize(uint i): level-0 size in pixels (0, 0wheniis out of range).uint PackTexCount(): number of declared textures.
Textures with "srgb": true (the default) return linear values: use them directly, without SrgbToLinear. s.tex and
PackMmdLit stay in gamma space as before. "srgb": false textures return the stored values. Missing textures and
out-of-range indices return white, so a pack whose user has not set up the texture folder still renders.
float3 ramp = PackSampleTexLevel(0, float2(saturate(dot(s.N, s.L) * 0.5 + 0.5), 0.5), 0).rgb; // clamp ramp, linear
float4 lm = PackSampleTex(1, s.uv); // "srgb": false light map: raw channel values
Outlines: PackEdge (optional)
Outlines are drawn by the engine’s edge pass, separately from PackShade. A pack can set their colour and width per
material by defining PACK_HAS_EDGE at the top of surface.hlsl and implementing PackEdge:
#define PACK_HAS_EDGE 1
PackEdgeResult PackEdge(uint materialClass, float4 mmdEdgeColor, float mmdEdgeSize) {
PackEdgeResult e;
e.color = materialClass == PACK_HAIR ? float4(0.25, 0.12, 0.10, 1.0) : mmdEdgeColor; // gamma space RGBA
e.widthScale = materialClass == PACK_FACE ? 0.5 : 1.0; // multiplies the MMD width; 0 = no outline
return e;
}
mmdEdgeColor / mmdEdgeSize are the PMX material’s edge values. Without PACK_HAS_EDGE the edges are exactly the
engine’s default. Only a real #define PACK_HAS_EDGE line opts in; a mention in a comment does not.