Resident ready Static companion
kari_mikumao Lab

Rendering

WMMD rendering: a toon pipeline verified by golden images

Forward toon shading with WBOIT transparency, ESM shadows, SSAO and RT AO, IBL and bloom - plus the headless snapshot setup that keeps it honest.

  • wmmd
  • rendering
  • wgpu
  • toon

The renderer in an MMD engine has an unusual job. It has to produce stylized toon output that matches a look people already know, while carrying enough modern lighting to survive being dropped into a concert stage or an AR scene.

Get the toon part wrong and it stops looking like MMD. Get the lighting part wrong and it can’t be embedded anywhere interesting.

WMMD does this on wgpu, behind a render interface that knows nothing about GPUs.

The Passes

wmmd-render-wgpu owns the whole GPU side:

  • A forward toon pass
  • Weighted-blended order-independent transparency (WBOIT)
  • Screen-space ambient occlusion, with an optional ray-traced AO path behind the ray-query feature
  • Image-based lighting
  • Exponential shadow maps for soft shadows
  • Bloom

There is also an ONNX-based texture upscaling path behind its own feature flag, off by default because the dependency is heavy.

Then there is a second backend, sylveon, an EEVEE-like renderer compiled into the editor by default. It carries the more cinematic work - depth of field with autofocus, and a McGuire-style tile-based motion blur pipeline that ships off by default. It has its own GPU profiler that can append per-pass timings as JSON Lines for later analysis.

Hair Is the Hard Part

Transparency is where MMD content gets difficult, and hair is the worst of it.

Hair sits right at the intersection of alpha-blended materials, depth writes, and ambient occlusion. When you get it wrong you notice immediately: hair that fails to occlude what is behind it, or that disappears from the AO structure completely.

Both of those were real defects I tracked and fixed. Hair geometry now participates in the ray proxy structure, and hair depth keys drive a composite step that rejects incorrectly ordered fragments.

The Two-Pass Host Loop

Because the renderer never owns the swapchain, the editor’s frame is explicit:

  1. Render the scene into an encoder
  2. Render egui panels into the same encoder, with a load operation that preserves the scene
  3. Submit
  4. Present

Panels live in a completely separate crate from the renderer. That is what allows a host with no UI at all - an export job, an AR surface - to drive the same render path.

Golden Images Keep Me Honest

This is the part that makes everything above maintainable. WMMD renders headlessly to PNG and compares against golden images, using a snapshot runner in the tools crate.

A stylized renderer has no analytic ground truth. There is no formula that says this frame is correct. So the reference is a previously approved frame, and any change that alters pixels has to justify itself.

In practice this is how shadow fidelity, material and specular behavior, and depth-of-field rebuilds have been evaluated. Render the same scene and framing before and after, then compare against a calibrated reference instead of an opinion.

It is also why the engine’s rules forbid naming a specific model anywhere in shipped code. A snapshot is a reproduction case, not a target. The fix has to hold across arbitrary PMX and VMD inputs, and the test has to say so.