Game guide · source of truth
Production

VFX pipeline

How every skill effect is built — layers, techniques (slash meshes, GPU particles, flipbooks, trails, decals), palettes per build, budgets per tier, the Low→Ultra feel stack (debris, shockwaves, camera kick, screen vibrate, haptics), pooling, readability rules, data format and tests.

Path of Exile-level flash on a phone budget means smart layers, not raw particle counts. Every skill effect is a small set of cheap, well-timed layers that read at a glance and degrade gracefully.

Layers of one effect over time (windup → contact → recovery)
Anticipation
Cast / release
Motion (slash/trail/projectile)
Impact
Aftermath (decals)
Status overlays
windup◆ contact (server)recovery+2 s

◆Richness and review standard

Follow the per-skill semantic roles and visual self-tests. Richness means readable cue, motion, contact and end, plus footprint/status where relevant; it is not a minimum particle/emitter count. Every movement gem and buff also needs an intentional effect. Profile overdraw/material cost, CPU/GPU time and pools as well as particle counts. The caps below are Zoen planning values, not universal AAA standards. Review both cameras, Low/High, bright/dark terrain, crowd overlap and accessibility settings; never lose threat/party-utility readability. On movement cancel, stop cast-owned future cues/trails/bursts; effects on independent released entities follow their own lifetime. Full contract: cancel rules.

Small details that this page does not cover (impact placement, near-camera fade, overdraw rules, prewarming, what gives way first when a pool is full) are proposed in the AAA detail catalog.

◆Layers of one effect

LayerPurposeTechniqueLow tier
1. Anticipation"something big is coming"glow on weapon, charge particles converging, ground glyphglow only
2. Cast / releasethe moment of actionflash sprite, short point light (≤ 2 active), muzzle burstflash sprite
3. Motionshape of the attackslash mesh (arc mesh + scrolling eroded-alpha shader), weapon trail ribbon, projectile with core + trailslash mesh, short trail
4. Impactcontact feedback per target materialspark/dust/debris burst, hit flash on target, ring shockwaveburst (half count)
5. Aftermaththe world remembersdecals (cracks, scorch, frost), lingering embers/mistnone (decals start at Mid, feel-quality.json)
6. Statusailment overlaysshader params on target (burn flicker, frost rim, shock crawl)shader only

◆Core techniques (Unity URP)

  • Slash meshes: pre-modelled arc strips (Blender) with UVs along the swing. A Shader Graph uses a greyscale noise mask, an erosion threshold over time and a gradient map (palette ramp) for colour, so one texture serves every build colour.
  • Particles: VFX Graph (GPU, compute) on native High/Ultra; the Particle System (Shuriken, CPU) on Low/Mid and in the Web build (WebGL2 has no compute shaders). Both definitions share timing and footprint.
  • Flipbooks: 8×8 sheets from Blender (ART-VFX-040/041), premultiplied alpha, on billboards or ribbons.
  • Trails: a Trail Renderer per weapon tip socket, enabled by animation events, width curve stretched by swing speed.
  • Decals: URP Decal Projectors (decal renderer feature) with lifetime fade, capped at 64 live, recycled oldest-first. Telegraphs and player ground previews are not in this pool: a recycled telegraph would break the never-cull rule (proposed own renderer: VFX-04).
  • Distortion: heat haze/shockwave as a renderer-feature pass only on High+, one at a time.
  • Lights: at most 2 short-lived dynamic point lights per frame on High (Forward+; 0 on Low). Fake the rest with emissive sprites.

◆Palettes (from builds.json)

Each build has three palette colours (e.g. Warblade #e2b04a #fff1c1 #6b4a1f). Elements override the tint: fire #ff7a2a, cold #9fd3ff, lightning #b48cff, spirit fire #ffb347, sonic #5fd1a6, bleed #8a1e1e. Monster telegraphs are always red-orange #ff5a2a with a pattern fill (colour-blind safe), and player ground previews are jade #4fd1a0.

◆Budgets

Per effectLowMidHigh
Live particles (signature)120350900
Live particles (chain skill)3080200
Emitters per effect246
Overdraw (fullscreen layers)0.51.01.5
Draw calls per effect3610

The frame-wide totals and the own/party/others split are in Performance and 1,000 players.

◆Data format

Each skill gets apps/client-unity/Assets/Zoen/Fx/Defs/<skillId>.json (read by the effect player; VFX Graph and Particle System assets referenced by id):

{ "skill": "warblade.horizon_cleave",
  "events": [
    { "at": "windup:0.00", "fx": "glint_blade", "socket": "weapon_r", "tier": "all" },
    { "at": "contact",     "fx": "slash_arc_160", "palette": "warblade", "scale": 3.2, "tier": "all" },
    { "at": "hit",         "fx": "impact_by_material", "tier": "all" },
    { "at": "contact",     "fx": "ground_dust_sheet", "tier": "mid+" } ] }

contact and hit are server events. windup:t is relative to the predicted cast start.

◆Pipeline per skill (tasks)

  1. Read the presentation.vfx brief and palette, then write the def JSON with greybox shapes (timing first).
  2. Build or reuse textures (flipbooks/slash meshes). New textures come from Blender scripts and are logged in the art library.
  3. Tune in the VFX Lab scene (Combat Lab + slow-motion scrubber + Low/High toggle).
  4. Tests: event alignment vs server contact (±16.7 ms), budget counters per tier, pool return (no leaks after 1,000 casts), telegraphs visible on Low.
  5. Evidence: 10 s clip at Low and High side by side, sent to the owner for review on signature skills.

◆Quality tiers and the Ultra feel stack

Tiers are data (feel-quality.json), read by the Feel Lab today and by the client settings menu later. Every tier keeps the same hit shapes, timings, reactions and telegraphs. Tiers only add presentation layers.

LayerLowMidHighUltra
Render scale0.751.01.0native DPR (max 2) + MSAA 4×
Shadows / glow / bloomoff / off / off512 / 0.4 / off1024 / 0.55 / 0.222048 / 0.65 / 0.3
Particles (× base count)0.40.71.0 (stretched spark streaks)1.6
Dynamic lights0112 (impact flash + lingering glow)
Debris chunks / scorch decals—6 / 814 / 1632 / 32
Ground dust rings, blade glint on windup—✓✓✓
Ambient sand motes——40140
Camera: shake scale / FOV punch / directional kick0.7 / — / —0.85 / 2° / —1.0 / 3° / 0.12 m1.15 / 4.5° / 0.28 m
Screen shockwave (refraction ring), colour-split pulse, impact frame, screen vibrate———✓
Grain / sharpen———subtle
  • Screen vibrate means two things. The camera gets a 0.22 s high-frequency rattle on massive hits (Ultra), on top of the trauma shake. The device gets haptics on every tier: Vibration API patterns on phones and dual-rumble on gamepads, scaled by impact. Haptics fire only after the player has interacted, at most once per 60 ms.
  • Settings: Graphics quality, Screen shake 0–100%, Haptics 0–100% and Reduce flashing. Reduce flashing removes impact frames and colour-split pulses and clamps bloom.
  • No white-outs. An impact frame is at most 60 ms, a 420 px burst around the hit and never full screen. The glow layer takes only 20% of a struck target's hit flash, so flashes read as rim light.
  • Implementation notes (Feel Lab, components/feel/engine.ts):
    • Debris is one thin-instanced mesh driven by typed arrays (one draw call, no per-frame allocation).
    • Decals are a fixed pool, recycled oldest first.
    • The shockwave is one full-screen post pass that exists only on Ultra.
    • One-shot particle bursts never dispose the shared sprite texture.
  • Measured (Playwright, desktop, Sunbreaker playing): High and Ultra both hold frame p95 ≈ 16.7 ms (logs/evidence/T048/frame-times.json). Clip: feel-ultra.mp4 on the Showcase page.

◆Readability rules

  • Never hide a telegraph. Player effects render under monster telegraphs.
  • Other players' effects default to Reduced (50% particles, no lights, no distortion). Own effects stay full.
  • Bloom is capped. Flash-reduction mode clamps peak brightness and removes white-outs.
  • The effect footprint must match the hit shape (a 3 m cone looks 3 m). The test overlays the shape on the capture.

Source: zoen/docs/production/VFX.md · 1,402 words · edit the Markdown, not this page.