Example Walkthrough: HoYo Toon

Deconstructing hoyo_toon: geometric SDF face shadows, two-tone body ramps, toon map sampling, and real HLSL implementation.

Introducing HoYo Toon (hoyo_toon)

hoyo_toon is the official reference shader pack bundled with MMDX12 (located in shaders/packs/hoyo_toon/). It delivers authentic anime cel-shading tailored for official Genshin Impact, Honkai: Star Rail, and Zenless Zone Zero MMD models.

It illustrates the three core patterns required by most advanced packs:

  1. Connecting user sliders via PackParam(i)
  2. Differentiating shading by material class (s.materialClass)
  3. Using the head bone coordinate frame (headPos, headRight, headForward)

Why Avoid Normal-Driven Face Shading?

Standard physically-based and Blinn-Phong renderers calculate illumination using the surface normal and lighting direction ($N \cdot L$). On anime character models, this produces severe visual defects:

  • Disjointed triangle shadows beneath the nose
  • Hollow sockets around the eyes
  • Undesirable cheek gradients that make the face look like a carved 3D statue
  • Ambient occlusion (SSAO/RTAO) dirtying facial geometry

In HoYoverse games, character face lighting is driven by artist-drawn 2D SDF (Signed Distance Field) textures based only on the horizontal angle between the head and the light. However, public MMD model distributions do not include these in-game SDF textures.

hoyo_toon solves this using a geometric SDF stand-in powered by the head bone.

Head-Driven Face Shadow (FaceLight)

The light direction is projected into the head bone’s horizontal plane to find the azimuth angle theta. The shadow boundary sweeps cleanly across the face without relying on per-pixel normals:

float FaceLight(PackSurface s) {
    if (!s.hasHead) return 1.0;

    // Project light vector onto head horizontal frame
    float lx = dot(s.L, s.headRight);
    float lz = dot(s.L, s.headForward);
    float theta = atan2(abs(lx), lz); // 0 (front) .. pi (back)

    // Sweep boundary position across the face
    float boundary = lerp(-1.15, 1.15, theta / 3.14159265);

    // Distance of pixel from head center along head right axis
    float side = dot(s.worldPos - s.headPos, s.headRight) / (P_FACE_WIDTH * s.headScale);
    side = clamp(side, -1.0, 1.0) * (lx >= 0.0 ? 1.0 : -1.0);

    return smoothstep(boundary - P_FACE_SOFTNESS, boundary + P_FACE_SOFTNESS, side);
}

Because surface normals are completely bypassed, frontal light leaves the face completely clean, side light shades exactly half the face, and backlight casts an even shadow over the whole face.

Branching on Material Classes

Inside PackShade, shading branches according to s.materialClass:

PackResult r;
r.alpha = s.alpha;
r.reflectivity = 0.0;
r.noAo = false;

float term;      // 0 = shadow, 1 = lit
float flatFill;  // 1 = punctual lights ignore normal (faces)
float warmth = 0.0;

if (s.materialClass == PACK_FACE || s.materialClass == PACK_EYE) {
    term = FaceLight(s) * lerp(1.0, s.shadow, P_FACE_CAST);
    flatFill = 1.0;
    warmth = 0.15;
    r.noAo = true; // Crucial: disable SSAO/RTAO on anime faces

    if (s.materialClass == PACK_EYE) {
        term = lerp(term, 1.0, 0.6); // Keep eyes legible in dark shadows
    }
} else {
    // Body and clothing: two-tone ramp on half-Lambert
    float halfLambert = dot(s.N, s.L) * 0.5 + 0.5;
    float soft = P_RAMP_SOFTNESS * (s.materialClass == PACK_SKIN ? 2.0 : 1.0);
    term = smoothstep(P_RAMP_THRESHOLD - soft, P_RAMP_THRESHOLD + soft, halfLambert) * s.shadow;
    flatFill = 0.0;
    if (s.materialClass == PACK_SKIN) warmth = 0.2;
}

Toon Ramp Sampling and Warmth Tint

Rather than multiplying by a dull dark color, hoyo_toon samples the model’s own MMD toon ramp at its dark end (PackSampleToon(1.0)) and tints it with a gentle warm rose tone (P_SHADOW_WARMTH):

float3 ShadowColour(float3 lit, float extraWarmth) {
    float3 toonDark = PackSampleToon(1.0);
    float3 warm = lerp(float3(1, 1, 1), float3(1.0, 0.82, 0.84), saturate(P_SHADOW_WARMTH + extraWarmth));
    return lit * lerp(float3(1, 1, 1), toonDark * warm, P_SHADOW_DARKNESS);
}

Stepped Hair Highlights and Rim Lighting

  • Hair Angel Rings: Instead of a soft Blinn-Phong specular gradient, hoyo_toon quantises specular power using smoothstep(0.45, 0.55, spec) to produce a distinct anime highlight band.
  • Rim Light: A crisp Fresnel rim band is added along the silhouette on lit sides, but intentionally excluded on face materials (PACK_FACE, PACK_EYE) to keep facial contours clean.