Skip to content

Toon Pack Demo

App: apps/toon_pack_demo/

Demonstrates the Toon / Stylized-Shading content pack (engine PLM-261): a whole stylized look — a cel/toon surface shader plus a depth-edge ink outline post effect — built with zero engine source changes, on only the public shader ABIs and the set-5 custom material params. It is the reference proof of Plume3D’s engine-vs-pack boundary: the engine ships neutral, look-free capabilities; the pack ships the look. Drop the two .slang files (and their compiled bin/*.spv) into any game’s content and reference them by path — exactly how the engine already resolves shaders.

Terminal window
% ./plume3d toon_pack_demo

Three primitives (a box, a cylinder, and a cone) rotate under the toon surface shader while the outline post effect inks their silhouettes. The demo captures a screenshot at frame 90 and exits at frame 110.

FileKindWhat it is
shaders/src/toon.slangsurface shaderCel diffuse: N·L quantized into hard bands, the darkest band tinted toward a cool shadow colour, plus a view-space Fresnel rim hardened into a toon rim light. Built on the mesh ABI (#include "plume3d.slang").
shaders/src/outline.slangpost effectA dark ink outline from scene-depth discontinuities (silhouettes + depth steps), composited over the scene. A 4-tap depth cross → central-difference gradient → ink where neighbours disagree. Built on the post ABI (#include "plume3d_post.slang").
  • Loads the toon surface shader (the active shader for the primitive draws) and the ink outline post shader:

    _toon = Graphics.loadShader("shaders/toon")
    Graphics.loadShader("shaders/outline") // built as a post pipeline on first use
  • Builds three saturated-colour primitives with MeshGen and draws them each frame with the toon surface shader:

    Graphics.drawMesh(_box, _toon, model) // the cel bands quantize each base colour
    Graphics.drawMesh(_cyl, _toon, model)
    Graphics.drawMesh(_cone, _toon, model)
  • Adds the outline post effect each frame (the post list is per-frame, like a draw call), passing the ink colour, thickness, and depth-edge tuning as params:

    // [ inkR, inkG, inkB, thicknessPx, depthThreshold, edgeGain ]
    Graphics.addPostEffect("shaders/outline", [0.05, 0.05, 0.06, 1.6, 0.0009, 3.0])

Why the outline needs no new engine pass. A depth-edge outline was expected to require a new engine pass (an inverted-hull or edge-detect pass). It does not: the post stack (Graphics.addPostEffect, RFX A#5) plus sampleable scene depth (RFX A#2) already expose everything an outline reads — the post shader #include "plume3d_post.slang" and samples plume3d_sceneColor + plume3d_sceneDepth. So the outline is pure pack content, and the whole Toon pack touches no engine source, descriptor layout, or frozen contract.

Both shaders are tuned entirely through pack params — no engine recompile.

All optional; the shown default applies when a param is left 0. A game re-skins the toon look by passing different set-5 custom params on the mesh (Mesh.setCustomColor / setCustomParamFloat) — still with no engine change. (The demo itself draws with the defaults.)

ParamMeaningDefault
customParam(0).xyzshadow tint (multiplies the darkest band)cool (0.40, 0.45, 0.60)
customParam(0).wnumber of light bands3
customParam(1).xyzrim colourwarm (1.00, 1.00, 0.90)
customParam(1).wrim power (higher = tighter rim)4
customParam(2).xrim strength 0..10.6

For art-directed bands, a pack can bind a 1-D ramp texture through the same set-5 custom-texture slot (Mesh.setCustomTexture) and sample it with plume3d_customTex(...) instead of the procedural quantize — again with no engine change.

Graphics.addPostEffect("shaders/outline", [inkR, inkG, inkB, thicknessPx, depthThreshold, edgeGain]) — default ink black, 1.5 px thickness, depth-edge threshold 0.0009 (raw non-linear depth units), and edge-softness gain 3.0.

  • Graphics — loadShader, addPostEffect (the post stack the outline rides).
  • Mesh — custom material (set 5) — how a game tunes the toon surface (setCustomColor / setCustomParamFloat / setCustomTexture).
  • MeshGen — box, cylinder, cone (the three primitives).
  • The mesh ABI plume3d.slang and the post ABI plume3d_post.slang — the two shipped shader includes the pack is built on.
  • The outline is depth-based. It catches silhouettes and depth steps but not interior creases on a continuous surface — those need scene normals, and the engine ships a depth buffer but no normal G-buffer. A normal-buffer outline is a future engine enhancement, not a pack change.
  • The toon surface uses a single fixed key-light direction. A game can drive it from a param or its own lighting; wiring a scene light into the pack shader is left to the consuming game.
  • Content pack, not an engine feature. Packaging, licensing, and production art for a shippable pack are owner/business decisions; this in-repo demo is the exercised, gate-covered reference. See the Custom Material Pack example and the Custom Material Packs guide for the set-5 mechanism this look is tuned through, and the Post Depth example for the post-and-depth stack the outline rides.