Tier 2 · Visual effects

VFX

General VFX rules for any Unity project: layered effects, particles, shaders, warm-up, cost and accessibility.

General VFX rules for any Unity project: layered effects, particles, shaders, warm-up, cost and accessibility.

Vendor-neutral guidance for building effects in any Unity project, in URP or HDRP, with Shader Graph, the built-in Particle System or VFX Graph. Use it when you add the first effect to a project and again before you ship. Rules that were learned on a specific page stay there with their measurements and provenance; this page gives the short version and links to the full rule.

Build an effect: the layered recipe

  • EFFECT-IS-A-STACK-OF-CHEAP-LAYERS — build each effect from a few cheap layers, not one rich one.
  • Rule: nested soft additive glows, a shaped body (a flipbook, or a scrolled and eroded texture), accents (sparks, rings, spikes) and, only where needed, a little alpha-blended smoke. A bolt can be three nested stretched additive quads; a fire can be two glows, a flame and sparks.
  • Why: a few cheap layers read richer than one expensive one, and each can be tuned or cut on its own.
  • Check: every layer has a job you can name, and removing any layer visibly loses something.
  • BLOOM-BRIGHTNESS-IS-STACKED-ADDITIVE — reach the bloom threshold by stacking LDR additive layers, not one HDR colour.
  • Rule: several One/One additive layers that sum past the bloom threshold give more control and cost less than one quad with an HDR multiplier. Use the default render queues; additive layers need no sorting, so the blend mode does the layering. Keep alpha blending for smoke and soft rings.
  • Check: the effect reads the same with bloom on and off, apart from the glow.
  • MOTION-SELLS-THE-LOOK — timing and texture motion matter more than texture detail.
  • Rule: hard smoothstep alpha edges; a two- or three-tone colour ramp over the life of the effect; short flash pops staggered in small beats; near-instant scale-in; shape and erosion textures scrolling at different speeds; mesh layers (cones, rings, planes) with offset pivots for whooshes that move outward.
  • Check: play the effect at quarter speed; each beat is visible and the layers do not move in lockstep.
  • EXPLOSIONS-POP-THEN-ERODE — never a linear scale-up.
  • Rule: a violent front-loaded pop (an ease-out curve), then the layers erode away through a noise dissolve with a glowing burn edge; fade alpha only in the last beat.
  • Why: one mesh with a linear scale or fade reads as dated, whatever the texture quality.
  • Check: the silhouette changes over the burst instead of only growing.
  • SOLID-FORM-PLUS-ENERGY — a physical core is one mesh wrapped in additive shells.
  • Rule: when an effect must read as an object (a crystal bolt, a rune disc), use one low-poly mesh as the core and wrap it with one to three additive energy layers. A bare lit mesh at gameplay distance reads as a prop, not magic. Pivot at the flight centre.
  • Check: at the gameplay camera the effect reads as energy, not as a dropped prop.
  • PICK-THE-PATH-BY-WHAT-COST-SCALES-WITH — layered meshes for many actors, pooled particles for events.
  • Rule: effects that fly with many actors (projectiles, trails, auras) are mesh layers drawn instanced. Particle systems and VFX Graph are for event-rate effects (impacts, casts, heals) from a capped pool. Never put a particle system on every unit of a high-count actor.
  • Check: doubling the unit count does not double the number of live particle systems.

Particle systems

  • ONE-SHOTS-FIRE-ON-AN-EDGE — bursts fire once, on a state change, never from a per-frame condition.
  • Rule: emit from the transition (a value crossing a threshold, a flag going true, a counter incrementing) with ParticleSystem.Emit(count) or a Burst entry, and use Sub Emitters for secondary sparks instead of spawning new systems from code. A per-frame follower only moves the system; it never calls Clear and Play every frame.
  • Why: a burst fired from an Update condition restarts every frame and turns into a fountain, or pins particles at age 0 where size-over-lifetime curves make them invisible.
  • Check: holding the trigger value constant produces no new bursts. Full rules: ONE-SHOTS-ARE-EDGE-TRIGGERS and PARTICLE-SHOW-IS-EDGE-TRIGGERED on the Animation (Mecanim) page.
  • PARTICLE-SYSTEMS-ARE-POOLED — attached effects come from a pool and return to it.
  • Rule: pre-create effect prefabs, attach on use, and on release call Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear) (or let the effect finish) before returning it. If the pool scales the root, every descendant system uses scalingMode = Hierarchy, and scale is reset on return.
  • Check: using and releasing an effect 100 times creates no new GameObjects after warm-up. Full rule: POOLED-PARTICLE-SCALE-INCLUDES-DESCENDANTS on the Animation page.
  • SUB-EMITTERS-AND-MODULES-HAVE-TRAPS — author sub-emitters and curves so they cannot misfire.
  • Rule: sub-emitters have Play On Awake off; enabled velocity-over-lifetime axes share one curve mode; a test that calls Simulate resumes playback before it judges IsAlive.
  • Check: Play mode shows no velocity-mode errors and no sub-emitter firing on its own. Full rules: SUB-EMITTER-PLAYS-ON-ITS-OWN, PARTICLE-VELOCITY-AXES-SHARE-CURVE-MODE and PARTICLE-SIMULATE-PAUSES-THE-CLOCK on the Animation page.

Shaders and materials

  • SHADER-GRAPH-TARGETS-MATCH-THE-PIPELINE — a graph without your pipeline's target draws nothing, silently.
  • Rule: every effect graph lists the target of the pipeline you ship (URP or HDRP); converted vendor graphs are checked too.
  • Check: open the graph's Graph Settings and confirm the target. Full rule: SHADERGRAPH-TARGET-MUST-MATCH-THE-PIPELINE on the Rendering & URP page.
  • ADDITIVE-MEANS-THE-BLEND-MODE — check the blend state, not the material name.
  • Rule: a glow material named "Additive" can still be alpha-blended and draw black squares; read its blend mode. A layered hit core (flare, glow, ring, sparks) uses additive materials, because stacked alpha-blended HDR white saturates to an opaque disc.
  • Check: no glow quad shows a dark or opaque square in Play. Full rules: ADDITIVE-NAME-IS-NOT-ADDITIVE-BLEND and LAYERED-HIT-CORE-IS-ADDITIVE on the Rendering page.
  • SOFT-PARTICLES-NEED-DEPTH — soft particles render blank without the camera depth texture.
  • Rule: either enable the depth texture on every renderer that draws them, or strip the soft-particle keyword on tiers that turn depth off. Depth and scene-colour features (soft particles, depth glow, screen distortion) also cost mobile bandwidth, so keep them off high-density effects.
  • Check: test on the mobile renderer asset, not only in the Scene view. Full rule: SOFT-PARTICLES-NEED-THE-DEPTH-TEXTURE on the Rendering page.
  • DECALS-PROJECT-INTO-THE-SURFACE — impact decals point into the hit and are parented to what they mark.
  • Rule: point the projector's +Z into the surface; parent the mark to the object it sits on.
  • Check: live shots from several angles show no floating or missing marks. Full rule: DECAL-FACES-INTO-THE-HIT on the Rendering page.
  • ONE-PARAMETER-DRIVES-THE-EFFECT — a material-driven effect is one 0..1 float, and gameplay writes only that.
  • Rule: derive mask thresholds, emission, pulse speed and dissolve edge from one float (for example _Amount). For steps the player must notice, shape the float with a curve or thresholds instead of a slow fade.
  • Why: one value is easy to animate, save, replicate, reset and test; separate flags drift out of sync and leave half-applied looks.
  • Check: scrub the value from 0 to 1; every stage appears in order, and 0 is exactly the clean look.
  • PER-INSTANCE-VALUES-NEVER-NEW-MATERIALS (HARD) — set per-renderer values without creating materials at runtime.
  • Rule: cache Shader.PropertyToID once. Use one reused MaterialPropertyBlock, a GPU-instanced property, Entities Graphics per-instance overrides ([MaterialProperty]), or a material instance created once at spawn and destroyed with the object. Never touch renderer.material in a per-frame path: each first access clones the material.
  • Why: clones leak and break batching. In URP and HDRP a property block also drops the renderer out of the SRP Batcher, so for crowds prefer instanced properties or a few shared materials.
  • Check: the Memory Profiler shows the same material count before and after a long fight.
  • VALUES-NOT-KEYWORDS — stages and intensities change floats, not shader keywords.
  • Rule: use a keyword only for a genuinely different feature, and prefer shader_feature_local. A keyword enabled at runtime that no build material uses has no variant in the player. Remember the player strips anything it cannot prove is used.
  • Check: the build's variant report does not grow when you add a stage. Full rule: SHADER-VARIANTS-STRIP-IN-THE-PLAYER on the Rendering page.

Warm-up and first play

  • VFX-GRAPH-WARMS-BY-BEING-PLAYED (HARD) — play each graph once during loading.
  • Rule: the first enable of a VFX Graph compiles its shaders and builds its buffers on that frame; no preload list covers it. Play it once, off-screen, while loading.
  • Check: the first in-game play has no hitch. Full rule with measurements on the Rendering page; for other shaders see GPU-WARMUP-NEEDS-A-REAL-DRAW on the Performance & memory page.
  • VFX-GRAPH-ONLY-WHERE-COMPUTE-IS-SAFE — choose VFX Graph or the Particle System by platform.
  • Rule: VFX Graph needs compute shaders; use it for dense effects on PC and console, and the built-in Particle System on low-end mobile and WebGL. Particle shaders with procedural instancing can skip their draws on some Vulkan devices.
  • Check: the effect draws on the lowest target device. See PROCEDURAL-PARTICLE-INSTANCING-SKIPS-ON-VULKAN on the Rendering page.
  • VENDOR-DURATION-IS-A-LIFETIME — read a vendor graph's exposed curves before driving it from code.
  • Rule: to hold a one-shot graph, re-play it before it ends instead of raising its duration.
  • Check: the effect clears on release. Full rule: VENDOR-VFX-DURATION-IS-A-LIFETIME on the Rendering page.

Readability: hit cores, flashes and streaks

  • JUDGE-EFFECTS-AT-GAMEPLAY-DISTANCE — accept an effect from the game camera, not zoomed in.
  • Rule: glows narrower than the core, no full impact prefab replayed at every hop of a chained streak or beam, and a final look check at the real camera distance.
  • Check: a capture from the gameplay camera reads as the effect, not as one white blob. Full rule on the Rendering page.
  • FLASHES-ARE-CAPPED-TOGETHER — tint plus emission, and flash plus light, are budgeted as a sum.
  • Rule: cap a hit flash's tint and emission together so the unit never turns into a white silhouette under bloom; size a flash particle from the range of the light it sits on.
  • Check: struck units stay readable with bloom on. Full rules: HIT-FLASH-CAPS-TINT-PLUS-EMISSION and FLASH-AND-LIGHT-ADD-UP on the Rendering page.
  • READS-WITHOUT-HUE — every effect state is readable with colour removed.
  • Rule: pair each hue with a non-colour cue: shape, pattern or motion. Give each effect type one hue family and reuse it in the world, the particles and the UI icon.
  • Check: view effects through a greyscale or colour-blindness filter; each stays distinct.

Lifecycle and networking

  • ONE-OWNER-FOR-APPLY-TICK-CLEAR (HARD) — one component owns an effect from start to clear.
  • Rule: the owner sets the start value, attaches pooled effects, writes the shader parameter only when it changes, and on clear sets the value to 0 and returns every effect it turned on.
  • Check: ending the effect removes every visual in the same frame.
  • CLEANUP-ON-EVERY-EXIT-PATH (HARD) — death, despawn, scene unload and pool return all run the clear.
  • Rule: call the same clear from every exit path (OnDisable is a safe catch-all) and reset the parameter on the property block or material.
  • Why: pooled objects keep their property block values, so a fresh spawn can walk out of the pool mid-effect.
  • Check: apply an effect, kill the object, and spawn until the pool reuses it; it is clean.
  • REPLICATE-THE-VALUE-NOT-THE-VISUALS — the network carries the parameter; clients derive the effect.
  • Rule: replicate only the authoritative value, quantized, and let each client set its own shader, particles and audio. Detect start and end as state transitions on each client.
  • Check: a late joiner shows the same state without a replay of onset effects.

Cost and accessibility

  • ZERO-ALLOCATION-BY-DEFAULT applies to effects too.
  • Rule: cached property IDs, a reused MaterialPropertyBlock, no LINQ or closures in effect ticks, pooled systems (see ZERO-ALLOCATION-BY-DEFAULT on the C# style page).
  • Check: Profiler GC Alloc is 0 B on frames with many effects running.
  • OVERDRAW-IS-THE-REAL-COST — stacked transparent layers cost fill rate.
  • Rule: keep alpha smoke few and large, keep particle sizes in check near the camera, cap simultaneous puffs, and use alpha clipping where you can.
  • Check: Scene view Overdraw mode or the Rendering Debugger with the camera pushed into the densest effect.
  • PARTICLE-CAPS-ARE-MEASURED-ON-DEVICE — caps come from a real draw on the lowest target device.
  • Rule: Max Particles is a ceiling and CPU render time is not GPU cost; set caps from GPU frame time with the effect's pixels on screen. Mobile defaults first, with a raised ceiling for PC from the same pools.
  • Check: the device capture is recorded next to the cap. Full rule: PARTICLE-COST-NEEDS-A-REAL-DRAW on the Performance & memory page.
  • NO-RAPID-FULL-SCREEN-FLASHES (HARD) — no large-area flash faster than three per second.
  • Rule: keep full-screen or large-area flashes below three per second (WCAG 2.3.1), avoid saturated red flashes, prefer a slow edge vignette, and offer a screen-effects intensity setting. Never put critical information only in a screen effect.
  • Check: record the worst case and count flashes per second.
  • EFFECT-IS-PROVEN-BY-ITS-OUTPUT — a settings readback is not proof an effect renders.
  • Check: look at the pixels or the pass counters. Full rule on the Rendering page.

Two locked, field-tested rules live on general pages and apply to every effect:

  • ONE-SHOTS-ARE-EDGE-TRIGGERS on the Animation (Mecanim) page: impact bursts, per-tick puffs and pickup flashes fire once on a rising edge, with the baseline primed so late joiners and pooled objects never replay them.
  • EMISSIVE-MAPS-NEED-AN-EMISSIVE-COLOR on the Rendering & URP page: glowing or pulsing materials need a non-black emissive colour, emission enabled and the _EMISSION variant in the build.