Unity shaders (technical artist, shading)

SkillMedia

Helps your agent write and debug Unity shaders like water, toon, dissolve, outlines, and render effects.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Unity shaders (technical artist, shading) skill

About this skill

Use when a Unity 6.3 URP task involves shaders: hand-written HLSL, Shader Graph water, toon, dissolve or outline, Custom Function nodes, a renderer feature or full-screen effect on Render Graph, compute shaders, shader variants, keywords, stripping, Shader Build Settings, PSO warm-up, or shader cost

What this skill tells your AI

The instructions your AI receives, as published by scenario-labs/skills in skills/game-engines/unity/scenario-unity-shaders/SKILL.md and read by ahel’s review.

Expert shading in Unity 6.3 is a shader you can prove: it compiles for the target graphics API, every pass agrees, its variants are counted after stripping, its first use is shown warm, its cost is measured in GPU milliseconds, and its look is checked in a capture with numbers. An agent without a mouse gets there through text: hand-written URP HLSL is the primary route, Shader Graph is used through existing graphs driven by exposed properties plus agent-owned .hlsl behind Custom Function nodes, and new graph wiring is a GUI step (references/gui-paths.md). Target: 6000.3.21f1, URP 17.3, Shader Graph 17.3, Metal on Apple Silicon. If a sibling skill named here is missing from your available skills, ask the user to install it (npx skills add scenario-labs/skills --skill <name>); unattended, proceed from tool schemas and flag the gap.

REQUIRED BACKGROUND: scenario-unity-expert (channels, review loop, 6.3 traps). Its toolkit (ut_env, ut_run, ut_review, AgentKit) is imported, never copied.

Status (2026-09-24, after the blind-grade refactor): every procedure ran in Unity 6000.3.21f1 here: tests/code/unity-shaders/run_all.sh --live (73 live checks including a development-player PSO proof, 32 offline checks, all passing; evidence in archive/tests/unity-shaders/).

Stance (the expert delta)

  1. Text first, graphs by template. Shader Graph has no loops and almost no lighting nodes, so lights and loops live in HLSL (Daniel Ilett, F8bAI6dIrto [00:07:13]); its one advantage is generated depth and shadow passes (bH--RU6qyTw [00:38:24]), which AgentPasses.hlsl gives hand-written shaders too. With graphs, duplicate one, set its exposed references, put changing logic in a File-mode .hlsl. Graph Settings bind every pass: Alpha Clipping without Allow Material Override writes #define _ALPHATEST_ON 1 into all 8 generated passes, and no Boolean keyword removes it (installed UniversalTarget.cs, observed).
  2. Every pass must agree. Displacement and clip() run in ShadowCaster, DepthOnly and DepthNormals too (6.3 Manual; Ilett [00:37:52]). A missing DepthOnly pass makes an opaque shader invisible with Depth Priming (observed). DepthNormals writes the raw world normal: the Manual's normal * 0.5 + 0.5 example reads back wrong in SSAO and outlines (observed).
  3. Read the installed pipeline, not the tutorial. Copy keywords from the installed Lit.shader (Ilett [00:14:51]; ut_shaders.urp_lit_keywords()): 6.3 has _CLUSTER_LIGHT_LOOP and four soft shadow variants. LIGHT_LOOP_BEGIN skips directional lights, so a custom loop needs a directional pre-loop under Forward+; Unity's own Custom Lighting sample lacks it (observed).
  4. Variants are a budget counted after stripping. shader_feature for material toggles, multi_compile only for runtime C# toggles, dynamic_branch tested with if, never #if (6.3 Manual). Strict Shader Variant Matching before stripping, then the funnel in the build log (Carotenuto, Unity blog 2024). A shader_feature (or _local) variant ships only if a material in the build enables it (observed 0 vs 6): never toggle one from runtime C#. Unity 6 Shader Build Settings exclude or retype keywords with zero code, but a dynamic_branch override is silently ignored for #if-tested keywords, URP's soft shadow and SSAO sets included (observed).
  5. First use is proven warm. Trace one GraphicsStateCollection per graphics API and platform in a development player, warm it behind the loading screen, and show gameplay frames with zero Shader.CreateGPUProgram samples and zero logged uploads (6.3 Manual; observed 14 to 0 on Metal).
  6. Cost is GPU time, not instruction count. Loops count at max trip, float4 costs about 4x float, transcendentals cost many cycles (Ben Cloward, E82XxlXMJs4 [00:15:44]). Measured here: 64 sin on a float4 about 3x a float, 10x or more a multiply-add loop. Half pays only where a device profiler shows ALU-bound (Freya Holmér, kfM-yu0iQBk [01:20:26]): desktop PlatformDefault compiles half as float.
  7. Render Graph shape. RecordRenderGraph declares, never records; blit once into a new texture then resourceData.cameraColor = destination; gate in AddRenderPasses; refuse the back buffer (6.3 docs; Unity dither sample, U8PygjYAF7A [00:09:06] [00:10:10]).
  8. One scalar field, one threshold, two colors; look and measure. Toon, water, dissolve, outline and dither threshold a 0..1 value (visual digest); the water's depth mask also calms its shore normals (gRq-IdShxpU [00:06:12]). Offset an emissive edge from the clip test (Brackeys, taMp1g1pBeE [00:05:31]). Output intermediates as colors (Freya [01:57:25]), then assert pixels at projected probes.

Establish first

  • Platforms and graphics APIs: Metal, Vulkan, DX12, GLES 3.x, WebGL 2 or WebGPU. Compute runs on Metal, Vulkan, DX11/12 and GLES 3.1+ (4 buffers guaranteed on GLES 3.1), not on GLES 3.0 or WebGL 2; the Web gets it through WebGPU (experimental in 6.3). The GPU Resident Drawer and STP exclude GLES entirely.
  • Rendering path per renderer (Forward, Forward+, Deferred+): decides the light-loop code; Shader Graph custom lighting works in Forward and Forward+ only.
  • URP assets per quality level: Depth and Opaque Texture, soft shadows, SSAO. The template's Mobile_RPAsset has Depth Texture off: depth-faded water needs it on or a baked shore mask.
  • Style and authors: toon or PBR; artists editing graphs (keep a graph, agent owns the .hlsl) or code only.
  • Per-object values: material instance, MeshRenderer/SkinnedMeshRenderer.SetShaderUserValue (6.3, SRP Batcher friendly), or MaterialPropertyBlock (breaks the SRP Batcher).
  • Budgets: GPU ms for the effect, variants per shader, overdraw, first-use hitches. Defaults when silent: URP 17.3 Forward+, PC and mobile tiers from the template, HLSL, shaders under Assets/AgentShaders/.

Workflow

  1. Install and inventory. ut_shaders.install(P); RendererFeatureJobs.ListFeatures (every quality level, asset, renderer, feature); lint_shader_file, shadergraph_summary (targets, properties, Graph Settings findings), lint_project_keywords. GATE: pipeline facts in the plan; lint errors listed.
  2. Author. Start from scripts/Shaders/: AgentLit, AgentToon, AgentWater, AgentDissolve, AgentEdgeOutline, AgentGrayscale, AgentCompute, shared AgentPasses.hlsl (AGENT_CLIP/AGENT_DISPLACE) and AgentNoise.hlsl. Graph route: ShaderGraphJobs.ImportPackageSample, DuplicateGraph, shadergraph_set_target_settings, GeneratedCode, AgentCustomLighting.hlsl. GATE: lint zero errors.
  3. Compile for the target. ShaderJobs.ValidateShaders (ShaderUtil.GetShaderMessages plus CompileVariant per pass for Metal). GATE: zero errors, the four opaque passes present.
  4. Bench and look. SetupMaterials, ShaderLab.BuildScene, ShaderLab.Shots (many shots per boot: material, renderer, feature and URP changes; probes projected to pixels). GATE: ut_review flags clean, contact sheet opened, probe numbers (shadow darker than open ground, dissolve shadow gone at 1, extra lights change the lit side).
  5. Full-screen effects. RendererFeatureJobs.AddFeature with AgentFullscreenFeature or the zero-code FullScreenPassRendererFeature. GATE: on/off captures differ as intended; no back-buffer error.
  6. Variants. VariantJobs.StrippingSettings (strict matching on), BuildShaderBundle for the shipping materials, parse_variant_funnel, variant_budget_check, strip rules or KeywordOverrides (Shader Build Settings), PostProcessingAudit (every VolumeProfile under Assets/ keeps its post variants). GATE: shipped count per shader under budget; logger total equals Unity's "After scriptable stripping"; overrides cleared or committed on purpose.
  7. First-use stutter. PsoJobs.BuildPsoScene, ut_run.build(development=True), ut_shaders.run_pso_player trace, cold, warm; PsoJobs.InspectCollection; ship StreamingAssets/PSO/<platform>_<api>.graphicsstate read by AgentPsoProbe. GATE: pso_gate clean (cold shows uploads, warm shows none by marker and by log).
  8. Cost. GpuCostJobs.MeasureFullscreenCost A/B, interleaved, async compilation off (precision_model for half tests). GATE: faster in ms beyond noise; device numbers from a development player (scenario-unity-performance).
  9. Deliver. Shaders, materials, feature assets, .graphicsstate per API, and the evidence packet (Handoffs).

Numbers

ValueRelative toSource
7,688 full; 4,046 per stage; 62 after settings; 13 shippedAgentKit/Lit in a macOS AssetBundle build with the template's URP assetsobserved
13 to 11AgentLit shipped variants after excluding the soft shadow set in Shader Build Settingsobserved
433 to 577UberPost variants when one unused Bloom HQ + dirt profile sits under Assets/observed
14 to 0; 1.8 to 2.4 msfirst-use GPU program uploads in gameplay, cold vs warm; warm-up of 26 to 27 PSOs in one frame, macOS playerobserved
0.007 to 0.033 / 0.29 to 0.42 / 0.89 to 1.66 msone 1080p full-screen draw: 64 multiply-adds / 64 sin / 64 float4 sin, M5 Max; compare within one runobserved
0.92 to 1.00 / 0.77 to 0.89half4 over float4 sin time, PlatformDefault / Unified precision model, M5 Max (noise about 10 %)observed
more than 128 keywords, 4 reservedper shader: compile slows6.3 Manual
2000 / 2450 / 3000Geometry / AlphaTest / Transparent queuesCyanilux
64x1x1, 8x8x1compute thread groups; ceil-divided dispatch plus bounds check6.3 Manual, [added]
(1.2, 0.35, 0.03) vs (4, 1.1, 0.15)HDR dissolve edge that stays orange vs washes to cream under Neutral tonemappingobserved
0.2126, 0.7152, 0.0722Rec. 709 luminance on linear color6.3 Manual

Quality gates

  • Measurable: lint and lint_project_keywords zero errors; ValidateShaders zero errors for the target API; SRP Batcher compatible (read after a render); probes (shadow luma under 0.75x open ground; dissolve shadow restored at 1; extra lights change the lit side in Forward and Forward+; water normals calmer at the shore than deep); feature on/off difference; shipped variants under budget and stable; no stray VolumeProfile; pso_gate clean per API; GPU ms A/B; compute GPU equals CPU (max error below 1e-5).
  • Visual: contact sheet opened; toon bands sharp; water hue shift, calm shore, foam, motion; dissolve edge keeps its hue; outline on silhouettes only; no magenta.

Common mistakes

MistakeWhat it looks likeFix
no DepthOnly passobject vanishes with Depth Priming ForcedAgentDepthOnlyVertex/Fragment
clip only in the forward passdissolved object still casts a full shadowAGENT_CLIP shared by all passes
DepthNormals returns n * 0.5 + 0.5wrong SSAO and outlinesreturn the raw normal
LIGHT_LOOP_BEGIN without a directional pre-loopa second directional light lights nothing under Forward+loop URP_FP_DIRECTIONAL_LIGHTS_COUNT first
_FORWARD_PLUS, SHADOWS_SCREEN, ComputeScreenPos (deprecated since URP 11)compile warning, dead shadow branch_CLUSTER_LIGHT_LOOP, GetMainLight(shadowCoord), GetNormalizedScreenSpaceUV(positionCS)
shader_feature_local toggled from runtime C# (dissolve "on hit")variant missing in the player: pink or closest matchmaterial swap, or multi_compile_local; lint_project_keywords
a Boolean keyword to skip Shader Graph Alpha Clippingwhole object alpha-tested in every pass, all the timeAllow Material Override (_ALPHATEST_ON keyword) or a second opaque material
dynamic_branch Type Override on an #if-tested keywordvariant count unchangedtest with if(), declare without a stage suffix
if (KW) on a _fragment-suffixed keyword"a known keyword but not declared in this pass"declare it for all stages
stray VolumeProfile assets (samples, tests)post variants for effects no scene usesmove them out of Assets/; PostProcessingAudit
one PSO collection for every API, or none provenfirst-use hitches on deviceper-API trace, cold vs warm runs
Depth Texture off on the URP assetwater turns to solid foam; hidden while SSAO still requests depthenable on every URP asset that renders it; test with SSAO off
feature missing from one renderer"the effect does nothing" on one quality levelListFeatures across quality levels
After Rendering injectionnothing drawn, back-buffer errorAfter Rendering Post Processing
HDR edge color 4 and upemissive edge washes to cream1 to 1.5, Bloom carries the glow
judging cost by instruction count, or half on desktop"optimized" shader no fasterMeasureFullscreenCost; half on the device
properties outside UnityPerMaterialSRP Batcher off, values bleedone CBUFFER in HLSLINCLUDE

Handoffs

  • Receives from scenario-unity-rendering-lighting: URP assets per quality level, rendering path, Depth/Opaque Texture, post stack, tonemapping, the Default Volume Profile. From scenario-unity-vfx: particle shader needs (blend mode, soft particles, flipbooks); scenario-unity-vfx owns VFX Graph, the Particle System and effect budgets. From scenario-unity-world-building: water and terrain surfaces. From scenario-unity-architecture: gameplay code that drives materials.
  • Delivers to scenario-unity-vfx and scenario-unity-world-building: shaders, materials with documented references, feature assets. To scenario-unity-performance: GPU ms tables, variant funnels, .graphicsstate files with the cold/warm proof for device captures. To scenario-unity-pipeline-automation: the variant budget and PSO gates for CI. To scenario-unity-mobile and scenario-unity-web: API limits (compute from GLES 3.1, none on GLES 3.0 or WebGL 2), half precision to profile on device, mobile Depth Texture cost, alpha-test cost on tile GPUs.
  • Packet: paths, ValidateShaders JSON, contact sheet, probes, funnel, PSO runs, cost table, Verified and Assumed.

Unity 6.3 notes

  • Render Graph only: Compatibility Mode removed (URP_COMPATIBILITY_MODE only to port, gone in 6.4); AddBlitPass returns a builder (returnBuilder: true); After Rendering always follows the final blit (6.2+).
  • _CLUSTER_LIGHT_LOOP replaced _FORWARD_PLUS (6.1); soft shadows have _LOW/_MEDIUM/_HIGH variants; fog is declared dynamic_branch in URP's Fog.hlsl.
  • Shader Build Settings: EditorGraphicsSettings.Get/SetShaderBuildSettings (the active set; a build profile can override). GraphicsStateCollection is experimental; tracing needs a development player; on Metal CreateGraphicsGraphicsPipelineImpl does not exist. Log Shader Compilation prints Uploaded shader variant to the GPU driver: ....
  • URP re-adds every override to the Default Volume Profile before a build, so most post-processing variant families always ship.
  • SetShaderUserValue(uint) exists on MeshRenderer and SkinnedMeshRenderer, not Renderer; the shader reads unity_RendererUserValue.
  • Shader Graph 17.3: templates, custom lighting via Unlit plus Keep Lighting Variants; reflected HLSL nodes need 17.5, Preprocessor Directives arrive in 6.6. WebGPU is supported from 6.6; Built-in RP is deprecated from 6.5.

References

Signals

GitHub stars
681
Forks
82
Last commit
Sep 2026
Advanced
Item type
skill
Key
scenario-unity-shaders
Source
github.com/scenario-labs/skills