Shaders & VFX (game engines)
Author shaders, materials, and full-screen effects across Godot, Unity, and Unreal. One
mental model — the GPU pipeline — mapped onto each engine's authoring surface, plus recipes
for the effects people actually ask for.
Version contract — read first
Target the current stable line of each engine and never emit its retired APIs. If unsure a
symbol is current, say so rather than guess.
| Engine |
Target |
Never emit → use instead |
| Godot |
4.x (4.4/4.5 stable) |
SCREEN_TEXTURE/DEPTH_TEXTURE/NORMAL_TEXTURE built-ins → declare a uniform sampler2D … : hint_screen_texture / hint_depth_texture / hint_normal_roughness_texture. hint_color/hint_albedo → source_color. hint_white/hint_black → hint_default_white/hint_default_black. GLES2-era guides. |
| Unity |
6 (6000.x LTS), URP/HDRP |
Surface shaders (#pragma surface) — Built-in-RP only, they do not compile under URP/HDRP. CGPROGRAM+UnityCG.cginc, UnityObjectToClipPos, mul(UNITY_MATRIX_MVP, v) → HLSLPROGRAM + URP Core.hlsl and TransformObjectToHClip(posOS). OnRenderImage/Graphics.Blit post FX → URP Renderer Feature / Fullscreen Shader Graph. |
| Unreal |
5.x (5.4+) |
SceneTexture:PostProcessInput0 outside a Post Process material; Opacity without a translucent blend mode; Opacity Mask without a Masked blend mode. Prefer graph nodes; drop to a Custom HLSL node only for logic nodes can't express. |
shader_type tokens in Godot 4 are exact: spatial, canvas_item (underscore), particles
(plural), sky, fog.
Shader fundamentals (the pipeline)
Every engine compiles your material into the same GPU stages. Two you write:
- Vertex stage — runs once per vertex. Transforms position into clip space and passes
interpolated data (UVs, normals, custom
varyings) down. Cheap; scales with mesh vertex count.
- Rasterizer (fixed) turns triangles into fragments and interpolates the vertex outputs.
- Fragment / pixel stage — runs once per covered pixel (× overdraw). Samples textures,
does lighting, writes the final color. Expensive; scales with screen coverage.
Per-vertex vs per-pixel is the core performance lever: compute anything that interpolates
linearly (position offsets, un-normalized directions, scalar masks) in the vertex stage and pass
it as a varying; keep only what must be exact per-pixel (normalizing interpolated normals,
texture sampling, lighting, fresnel) in the fragment stage.
UVs are per-vertex 2D texture coordinates (0–1), interpolated across the face — you sample
textures and drive scrolling/tiling/masks with them. Normals are surface directions used for
lighting and rim; interpolated normals must be re-normalized per pixel. Normal maps store
directions in tangent space (unpack with ×2−1); respect handedness.
Coordinate spaces — know which space each value is in before you do math on it:
| Space |
Meaning |
Typical use |
| Object / model |
mesh-local, origin at the pivot |
authoring positions/normals start here |
| World |
scene-global |
world-space effects, triplanar, lighting |
| View / camera |
relative to the camera |
Godot spatial NORMAL/VIEW live here |
| Clip / NDC |
post-projection homogeneous coords |
the vertex stage's required output |
| Tangent |
per-fragment surface basis (T,B,N) |
normal maps are decoded here |
| Screen / UV |
0–1 across the framebuffer |
post-process sampling (SCREEN_UV) |
Per-engine authoring
Godot 4.x
Godot ships its own GLSL-like language (.gdshader). Pick a shader_type, declare uniforms
(exposed as material parameters), pass data with varying, write vertex()/fragment()
(+light()). A ShaderMaterial binds the shader to a node and holds uniform values; set them
from code with material.set_shader_parameter("name", value). Uniform hints: source_color
(sRGB→linear color pickers), hint_range(a,b), hint_default_white, hint_screen_texture,
plus texture filters/repeats (filter_linear_mipmap, repeat_enable).
Small canvas_item (2D) shader — scroll and tint a texture:
shader_type canvas_item;
uniform sampler2D noise : repeat_enable;
uniform vec4 tint : source_color = vec4(1.0);
uniform float speed = 0.1;
void fragment() {
vec2 uv = UV + vec2(TIME * speed, 0.0); // UV is the node's texcoord
COLOR = texture(noise, uv) * tint * COLOR; // in COLOR = vertex/modulate color
}
Small spatial (3D) shader — a fresnel rim glow (NORMAL and VIEW are view-space here):
shader_type spatial;
render_mode blend_add, cull_back;
uniform vec3 rim_color : source_color = vec3(0.2, 0.6, 1.0);
uniform float power : hint_range(0.0, 8.0) = 3.0;
varying vec3 v_normal;
void vertex() { v_normal = NORMAL; } // pass to fragment via varying
void fragment() {
float f = pow(1.0 - dot(normalize(v_normal), normalize(VIEW)), power);
EMISSION = rim_color * f;
ALPHA = f;
}
Deep dive (built-in variables per type, render_modes, particles/sky/fog, screen/depth reads) →
references/godot-shading-language.md.
Unity 6
Two authoring paths, both on the Scriptable Render Pipeline (URP for most projects, HDRP
for high-end):
- Shader Graph — visual node graph feeding a master stack (Vertex + Fragment blocks).
Artist-friendly, URP/HDRP only, compiles to HLSL. Default choice for surface looks and VFX.
- Hand-written HLSL — a
Shader "…" { … } (ShaderLab) wrapping Properties and Pass
blocks; the program goes in an HLSLPROGRAM … ENDHLSL block that #includes URP's Core.hlsl
/ Lighting.hlsl. Use for full control, custom lighting, or compute-driven effects.
Surface shaders are not an option under URP/HDRP (see the Version contract) — a lit look is an
HLSL pass or a Shader Graph. Set parameters at runtime through a MaterialPropertyBlock or
Material.SetFloat/SetColor/SetTexture. Full URP unlit + lit HLSL pass and a Shader Graph
mapping → references/unity-and-unreal-shaders.md.
Unreal 5.x
A Material is a node graph the engine compiles to HLSL. You wire outputs on the main result
node — Base Color, Metallic, Roughness, Emissive Color, Normal, Opacity / Opacity Mask, World
Position Offset. Key knobs on the material:
- Material Domain — what the material drives: Surface (default meshes), Deferred Decal,
Light Function, Volume, Post Process (full-screen), User Interface.
- Blend Mode (Opaque/Masked/Translucent/Additive…) and Shading Model (Default Lit,
Unlit, Subsurface, …). Opacity needs Translucent; Opacity Mask needs Masked.
- Material Instances expose parameters (scalar/vector/texture/switch) for cheap variants and
runtime tweaks via a Dynamic Material Instance (
SetScalarParameterValue, …).
- Custom node — a raw HLSL escape hatch: set Output Type, add named Inputs,
return …;.
Reach for it only when the node set can't express the logic (loops, bitops). Reuse via Material
Functions. UE5.5+ adds Substrate as an opt-in shading system; the standard material is still default.
Custom-node HLSL, domains, and a Godot↔Unreal recipe mapping → references/unity-and-unreal-shaders.md.
Common effect recipes (concepts)
Each is a technique, engine-agnostic — the reference has full per-engine code.
| Effect |
Core idea |
| Dissolve |
Threshold a noise texture against an animated cutoff; discard/clip below it; add an emissive band at the edge. |
| Rim / outline |
Rim = fresnel pow(1 − N·V, p). Outline = inverted-hull pass (scale along normals, flip culling) or a post-process depth/normal edge detect. |
| Toon / cel |
Quantize diffuse N·L into bands (step/smoothstep or a ramp texture); hard-stepped specular. |
| Water / flow |
Scroll two normal maps at different speeds (or advect a flow-map's RG); refract the screen texture; depth-difference foam at shorelines. |
| Force field |
Fresnel + scrolling hex/pattern texture + intersection glow from a scene-depth difference; additive. |
| Hologram |
Scanlines sin(worldY·f + TIME) + fresnel + flicker + slight RGB channel offset; additive/translucent. |
Worked example — dissolve (Godot spatial):
shader_type spatial;
render_mode cull_disabled;
uniform sampler2D dissolve_noise : hint_default_white;
uniform float threshold : hint_range(0.0, 1.0) = 0.0; // animate 0 → 1
uniform float edge = 0.05;
uniform vec3 edge_color : source_color = vec3(1.0, 0.4, 0.0);
void fragment() {
float n = texture(dissolve_noise, UV).r;
if (n < threshold) discard; // cut the hole
float e = smoothstep(threshold, threshold + edge, n);
EMISSION = edge_color * (1.0 - e); // glowing burn ring
ALBEDO = vec3(0.6);
}
Drive threshold from an AnimationPlayer or set_shader_parameter. The same math ports to
Unity (clip(n - threshold)) and Unreal (Opacity Mask + a threshold parameter). All six recipes,
per engine → references/effect-recipes.md.
Post-processing / full-screen effects
- Godot — a
canvas_item shader on a full-rect ColorRect reading hint_screen_texture, or
a spatial unshaded full-screen quad reading hint_screen_texture/hint_depth_texture; or a
CompositorEffect (4.3+) for a custom render pass. Environment already covers glow/tonemap/SSAO.
- Unity (URP) — a Full Screen Pass Renderer Feature driving a Fullscreen Shader Graph
(or a Blit pass). HDRP uses Custom Pass / Fullscreen. Legacy
OnRenderImage is Built-in-RP only.
- Unreal — a Post Process Material (Material Domain = Post Process) on a Post Process
Volume; read the frame with SceneTexture nodes (SceneColor, SceneDepth, custom stencil).
Blendable Location orders it against tonemapping.
Performance
- Overdraw is the top cost: transparent/additive layers each re-shade the same pixels. Prefer
opaque, sort and minimize overlap, keep particle fill low.
discard/clip disables early-Z
— don't use it as a cheap "invisible".
- Texture sampling = a memory fetch + filter each call; dependent reads (UV derived from a
prior sample) stall the pipeline. Pack masks into channels, atlas, and cache samples in locals.
- Branching: a divergent
if across a GPU warp can execute both sides. Prefer
step/mix/clamp; branches on a uniform (same value for all pixels) are cheap; static
branches compile out.
- LOD & precision: use mipmaps, shader LOD variants, and
mediump/half precision on mobile;
full float only where banding shows. Move linear work to the vertex stage.
- Mobile / tile GPUs: bandwidth-bound — keep render targets small, avoid mid-pass framebuffer
reads, and note that
discard and large full-screen passes break tile hidden-surface removal.
Anti-patterns
| Anti-pattern |
Do instead |
| Porting a tutorial verbatim from Godot 3, Built-in RP, or pre-5.0 UE |
Translate it through the Version contract table first — retired symbols still compile in old guides, not in your project. |
| Reading a texture or writing a color without minding linear vs sRGB |
Author color uniforms as source_color (Godot) / sRGB-marked properties and check the space at every read and output — the #1 "looks washed out / too dark" bug. |
| Using interpolated normals raw, or a normal map straight from the sample |
Re-normalize per pixel; unpack with ×2−1 and mind tangent handedness. |
| Writing the shader before choosing the target surface |
Pick shader_type / render pipeline / material domain first (2D vs 3D, URP vs HDRP, Surface vs Post Process) — it decides which built-ins and blend modes exist. |
| Fresnel, normalization, or lighting math in the vertex stage |
Only linearly-interpolating work goes per-vertex; exact math stays per-pixel. |
discard/clip as a cheap "make it invisible" |
Cull it or scale to zero — discard disables early-Z and breaks tile hidden-surface removal on mobile. |
| Stacking additive/translucent layers until the look works |
Count the overdraw: each layer re-shades the same pixels. Prefer opaque, minimize overlap, keep particle fill low. |
Full-screen effects via OnRenderImage/Graphics.Blit, or SceneTexture outside the Post Process domain |
Use the engine's supported path — URP Renderer Feature / Fullscreen Shader Graph, UE Post Process material, Godot ColorRect + hint_screen_texture or CompositorEffect. |
Related skills
godot / unity / unreal — gameplay
code, nodes/components, input, scene wiring; this skill owns the shading, not the C#/GDScript/Blueprint around it.
gamedev-physics — simulation, collision, rigid bodies, character
controllers (a shader that fakes refraction is here; simulating fluid dynamics is not).
gamedev-shipping — platform export and shader-variant stripping in
the build (this skill keeps the per-shader performance work).
Checklist
1---2name: gamedev-shaders3description: Use when authoring or debugging a shader, material, VFX, or full-screen post-process in a game engine — Godot 4.x `.gdshader`, Unity 6 URP/HDRP, Unreal 5.x Materials, effect recipes, shader performance. NOT gameplay or engine-API code (that is `godot`/`unity`/`unreal`), NOT physics (`gamedev-physics`), NOT build variant stripping (`gamedev-shipping`).4---56# Shaders & VFX (game engines)78Author shaders, materials, and full-screen effects across Godot, Unity, and Unreal. One9mental model — the GPU pipeline — mapped onto each engine's authoring surface, plus recipes10for the effects people actually ask for.1112## Version contract — read first1314Target the current stable line of each engine and never emit its retired APIs. If unsure a15symbol is current, say so rather than guess.1617| Engine | Target | Never emit → use instead |18| --- | --- | --- |19| **Godot** | 4.x (4.4/4.5 stable) | `SCREEN_TEXTURE`/`DEPTH_TEXTURE`/`NORMAL_TEXTURE` built-ins → declare a `uniform sampler2D … : hint_screen_texture` / `hint_depth_texture` / `hint_normal_roughness_texture`. `hint_color`/`hint_albedo` → `source_color`. `hint_white`/`hint_black` → `hint_default_white`/`hint_default_black`. GLES2-era guides. |20| **Unity** | 6 (6000.x LTS), URP/HDRP | Surface shaders (`#pragma surface`) — Built-in-RP only, they do **not** compile under URP/HDRP. `CGPROGRAM`+`UnityCG.cginc`, `UnityObjectToClipPos`, `mul(UNITY_MATRIX_MVP, v)` → `HLSLPROGRAM` + URP `Core.hlsl` and `TransformObjectToHClip(posOS)`. `OnRenderImage`/`Graphics.Blit` post FX → URP Renderer Feature / Fullscreen Shader Graph. |21| **Unreal** | 5.x (5.4+) | `SceneTexture:PostProcessInput0` outside a Post Process material; Opacity without a translucent blend mode; Opacity Mask without a Masked blend mode. Prefer graph nodes; drop to a Custom HLSL node only for logic nodes can't express. |2223`shader_type` tokens in Godot 4 are exact: `spatial`, `canvas_item` (underscore), `particles`24(plural), `sky`, `fog`.2526## Shader fundamentals (the pipeline)2728Every engine compiles your material into the same GPU stages. Two you write:2930- **Vertex stage** — runs **once per vertex**. Transforms position into clip space and passes31 interpolated data (UVs, normals, custom `varying`s) down. Cheap; scales with mesh vertex count.32- **Rasterizer** (fixed) turns triangles into fragments and *interpolates* the vertex outputs.33- **Fragment / pixel stage** — runs **once per covered pixel** (× overdraw). Samples textures,34 does lighting, writes the final color. Expensive; scales with screen coverage.3536**Per-vertex vs per-pixel is the core performance lever:** compute anything that interpolates37linearly (position offsets, un-normalized directions, scalar masks) in the vertex stage and pass38it as a `varying`; keep only what must be exact per-pixel (normalizing interpolated normals,39texture sampling, lighting, fresnel) in the fragment stage.4041**UVs** are per-vertex 2D texture coordinates (0–1), interpolated across the face — you sample42textures and drive scrolling/tiling/masks with them. **Normals** are surface directions used for43lighting and rim; interpolated normals must be re-normalized per pixel. **Normal maps** store44directions in **tangent space** (unpack with `×2−1`); respect handedness.4546**Coordinate spaces** — know which space each value is in before you do math on it:4748| Space | Meaning | Typical use |49| --- | --- | --- |50| Object / model | mesh-local, origin at the pivot | authoring positions/normals start here |51| World | scene-global | world-space effects, triplanar, lighting |52| View / camera | relative to the camera | Godot spatial `NORMAL`/`VIEW` live here |53| Clip / NDC | post-projection homogeneous coords | the vertex stage's required output |54| Tangent | per-fragment surface basis (T,B,N) | normal maps are decoded here |55| Screen / UV | 0–1 across the framebuffer | post-process sampling (`SCREEN_UV`) |5657## Per-engine authoring5859### Godot 4.x6061Godot ships its own GLSL-like language (`.gdshader`). Pick a `shader_type`, declare `uniform`s62(exposed as material parameters), pass data with `varying`, write `vertex()`/`fragment()`63(+`light()`). A **ShaderMaterial** binds the shader to a node and holds uniform values; set them64from code with `material.set_shader_parameter("name", value)`. Uniform hints: `source_color`65(sRGB→linear color pickers), `hint_range(a,b)`, `hint_default_white`, `hint_screen_texture`,66plus texture filters/repeats (`filter_linear_mipmap`, `repeat_enable`).6768Small **canvas_item** (2D) shader — scroll and tint a texture:6970```glsl71shader_type canvas_item;72uniform sampler2D noise : repeat_enable;73uniform vec4 tint : source_color = vec4(1.0);74uniform float speed = 0.1;7576void fragment() {77 vec2 uv = UV + vec2(TIME * speed, 0.0); // UV is the node's texcoord78 COLOR = texture(noise, uv) * tint * COLOR; // in COLOR = vertex/modulate color79}80```8182Small **spatial** (3D) shader — a fresnel rim glow (`NORMAL` and `VIEW` are **view-space** here):8384```glsl85shader_type spatial;86render_mode blend_add, cull_back;87uniform vec3 rim_color : source_color = vec3(0.2, 0.6, 1.0);88uniform float power : hint_range(0.0, 8.0) = 3.0;8990varying vec3 v_normal;91void vertex() { v_normal = NORMAL; } // pass to fragment via varying92void fragment() {93 float f = pow(1.0 - dot(normalize(v_normal), normalize(VIEW)), power);94 EMISSION = rim_color * f;95 ALPHA = f;96}97```9899Deep dive (built-in variables per type, render_modes, particles/sky/fog, screen/depth reads) →100`references/godot-shading-language.md`.101102### Unity 6103104Two authoring paths, both on the Scriptable Render Pipeline (**URP** for most projects, **HDRP**105for high-end):106107- **Shader Graph** — visual node graph feeding a *master stack* (Vertex + Fragment blocks).108 Artist-friendly, URP/HDRP only, compiles to HLSL. Default choice for surface looks and VFX.109- **Hand-written HLSL** — a `Shader "…" { … }` (ShaderLab) wrapping `Properties` and `Pass`110 blocks; the program goes in an `HLSLPROGRAM … ENDHLSL` block that `#include`s URP's `Core.hlsl`111 / `Lighting.hlsl`. Use for full control, custom lighting, or compute-driven effects.112113Surface shaders are not an option under URP/HDRP (see the Version contract) — a lit look is an114HLSL pass or a Shader Graph. Set parameters at runtime through a `MaterialPropertyBlock` or115`Material.SetFloat/SetColor/SetTexture`. Full URP unlit + lit HLSL pass and a Shader Graph116mapping → `references/unity-and-unreal-shaders.md`.117118### Unreal 5.x119120A **Material** is a node graph the engine compiles to HLSL. You wire outputs on the main result121node — Base Color, Metallic, Roughness, Emissive Color, Normal, Opacity / Opacity Mask, World122Position Offset. Key knobs on the material:123124- **Material Domain** — what the material drives: *Surface* (default meshes), *Deferred Decal*,125 *Light Function*, *Volume*, *Post Process* (full-screen), *User Interface*.126- **Blend Mode** (Opaque/Masked/Translucent/Additive…) and **Shading Model** (Default Lit,127 Unlit, Subsurface, …). Opacity needs Translucent; Opacity Mask needs Masked.128- **Material Instances** expose parameters (scalar/vector/texture/switch) for cheap variants and129 runtime tweaks via a Dynamic Material Instance (`SetScalarParameterValue`, …).130- **Custom node** — a raw HLSL escape hatch: set Output Type, add named Inputs, `return …;`.131 Reach for it only when the node set can't express the logic (loops, bitops). Reuse via Material132 Functions. UE5.5+ adds *Substrate* as an opt-in shading system; the standard material is still default.133134Custom-node HLSL, domains, and a Godot↔Unreal recipe mapping → `references/unity-and-unreal-shaders.md`.135136## Common effect recipes (concepts)137138Each is a *technique*, engine-agnostic — the reference has full per-engine code.139140| Effect | Core idea |141| --- | --- |142| **Dissolve** | Threshold a noise texture against an animated cutoff; `discard`/clip below it; add an emissive band at the edge. |143| **Rim / outline** | Rim = fresnel `pow(1 − N·V, p)`. Outline = inverted-hull pass (scale along normals, flip culling) **or** a post-process depth/normal edge detect. |144| **Toon / cel** | Quantize diffuse `N·L` into bands (`step`/`smoothstep` or a ramp texture); hard-stepped specular. |145| **Water / flow** | Scroll two normal maps at different speeds (or advect a flow-map's RG); refract the screen texture; depth-difference foam at shorelines. |146| **Force field** | Fresnel + scrolling hex/pattern texture + intersection glow from a scene-depth difference; additive. |147| **Hologram** | Scanlines `sin(worldY·f + TIME)` + fresnel + flicker + slight RGB channel offset; additive/translucent. |148149**Worked example — dissolve (Godot spatial):**150151```glsl152shader_type spatial;153render_mode cull_disabled;154uniform sampler2D dissolve_noise : hint_default_white;155uniform float threshold : hint_range(0.0, 1.0) = 0.0; // animate 0 → 1156uniform float edge = 0.05;157uniform vec3 edge_color : source_color = vec3(1.0, 0.4, 0.0);158159void fragment() {160 float n = texture(dissolve_noise, UV).r;161 if (n < threshold) discard; // cut the hole162 float e = smoothstep(threshold, threshold + edge, n);163 EMISSION = edge_color * (1.0 - e); // glowing burn ring164 ALBEDO = vec3(0.6);165}166```167168Drive `threshold` from an `AnimationPlayer` or `set_shader_parameter`. The same math ports to169Unity (`clip(n - threshold)`) and Unreal (Opacity Mask + a threshold parameter). All six recipes,170per engine → `references/effect-recipes.md`.171172## Post-processing / full-screen effects173174- **Godot** — a `canvas_item` shader on a full-rect `ColorRect` reading `hint_screen_texture`, or175 a spatial unshaded full-screen quad reading `hint_screen_texture`/`hint_depth_texture`; or a176 `CompositorEffect` (4.3+) for a custom render pass. Environment already covers glow/tonemap/SSAO.177- **Unity (URP)** — a **Full Screen Pass Renderer Feature** driving a *Fullscreen* Shader Graph178 (or a Blit pass). HDRP uses Custom Pass / Fullscreen. Legacy `OnRenderImage` is Built-in-RP only.179- **Unreal** — a **Post Process Material** (Material Domain = Post Process) on a Post Process180 Volume; read the frame with **SceneTexture** nodes (SceneColor, SceneDepth, custom stencil).181 Blendable Location orders it against tonemapping.182183## Performance184185- **Overdraw** is the top cost: transparent/additive layers each re-shade the same pixels. Prefer186 opaque, sort and minimize overlap, keep particle fill low. `discard`/`clip` **disables early-Z**187 — don't use it as a cheap "invisible".188- **Texture sampling** = a memory fetch + filter each call; *dependent* reads (UV derived from a189 prior sample) stall the pipeline. Pack masks into channels, atlas, and cache samples in locals.190- **Branching**: a divergent `if` across a GPU warp can execute *both* sides. Prefer191 `step`/`mix`/`clamp`; branches on a **uniform** (same value for all pixels) are cheap; static192 branches compile out.193- **LOD & precision**: use mipmaps, shader LOD variants, and `mediump`/half precision on mobile;194 full `float` only where banding shows. Move linear work to the vertex stage.195- **Mobile / tile GPUs**: bandwidth-bound — keep render targets small, avoid mid-pass framebuffer196 reads, and note that `discard` and large full-screen passes break tile hidden-surface removal.197198## Anti-patterns199200| Anti-pattern | Do instead |201| --- | --- |202| Porting a tutorial verbatim from Godot 3, Built-in RP, or pre-5.0 UE | Translate it through the Version contract table first — retired symbols still compile in old guides, not in your project. |203| Reading a texture or writing a color without minding linear vs sRGB | Author color uniforms as `source_color` (Godot) / sRGB-marked properties and check the space at every read and output — the #1 "looks washed out / too dark" bug. |204| Using interpolated normals raw, or a normal map straight from the sample | Re-normalize per pixel; unpack with `×2−1` and mind tangent handedness. |205| Writing the shader before choosing the target surface | Pick `shader_type` / render pipeline / material domain first (2D vs 3D, URP vs HDRP, Surface vs Post Process) — it decides which built-ins and blend modes exist. |206| Fresnel, normalization, or lighting math in the vertex stage | Only linearly-interpolating work goes per-vertex; exact math stays per-pixel. |207| `discard`/`clip` as a cheap "make it invisible" | Cull it or scale to zero — `discard` disables early-Z and breaks tile hidden-surface removal on mobile. |208| Stacking additive/translucent layers until the look works | Count the overdraw: each layer re-shades the same pixels. Prefer opaque, minimize overlap, keep particle fill low. |209| Full-screen effects via `OnRenderImage`/`Graphics.Blit`, or `SceneTexture` outside the Post Process domain | Use the engine's supported path — URP Renderer Feature / Fullscreen Shader Graph, UE Post Process material, Godot `ColorRect` + `hint_screen_texture` or `CompositorEffect`. |210211## Related skills212213- [`godot`](../godot/SKILL.md) / [`unity`](../unity/SKILL.md) / [`unreal`](../unreal/SKILL.md) — gameplay214 code, nodes/components, input, scene wiring; this skill owns the *shading*, not the C#/GDScript/Blueprint around it.215- [`gamedev-physics`](../gamedev-physics/SKILL.md) — simulation, collision, rigid bodies, character216 controllers (a shader that *fakes* refraction is here; simulating fluid dynamics is not).217- [`gamedev-shipping`](../gamedev-shipping/SKILL.md) — platform export and shader-variant stripping in218 the build (this skill keeps the per-shader performance work).219220## Checklist221222- [ ] Correct engine + version idiom (no banned API from the Version contract table).223- [ ] Right `shader_type` / render pipeline / material domain for the target (2D vs 3D, URP vs HDRP, Surface vs Post Process).224- [ ] Work placed in the right stage: linear math per-vertex via `varying`, exact math per-pixel.225- [ ] Colors authored in the correct space (`source_color` / sRGB handling); normals normalized and unpacked.226- [ ] Uniforms/parameters exposed and driven from code or an animation track — not hard-coded.227- [ ] Performance sanity: overdraw, sample count, and branching considered; mobile precision set if targeted.228- [ ] Post-process uses the engine's supported full-screen path (not a retired Built-in-RP mechanism).