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 inside surface.hlsl resolve 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:

FieldTypeDescription
worldPosfloat3Surface position in world space (MMD units, +Y up)
Nfloat3World normal vector (automatically flipped towards camera on back faces)
Vfloat3Unit vector towards the camera eye
Lfloat3Unit vector towards the primary directional sun light
uvfloat2Material texture coordinates
pixelfloat2Window pixel position (SV_Position.xy)
viewZfloatView-space linear depth
texfloat4Base albedo texture in gamma space with material morph factors applied (pure white if untextured)
alphafloatCombined material alpha multiplied by texture alpha (pixels below 0.004 are discarded earlier)
shadowfloatDirectional sun shadow factor (0 = full shadow, 1 = unshadowed; 1 if material ignores shadows)
materialClassuintClassified 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:

FieldTypeDescription
hasHeadboolTrue if a head bone (頭 or 首) was located; false falls back to default world axes
headPosfloat3World position of the head bone joint
headRightfloat3Model’s right unit vector (+X)
headUpfloat3Model’s upward unit vector (+Y)
headForwardfloat3Direction the face is looking (MMD models face -Z)
headScalefloatDisplay 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 index i (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.0 at the lit end, v = 1.0 at 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): texture i with 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. in PackEdge.
  • uint2 PackTexSize(uint i): level-0 size in pixels (0, 0 when i is 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.