Shader Graph
A shader graph is a visual node graph that compiles to the same Material and WGSL hooks a hand-written shader produces, so you can author surfaces by wiring nodes instead of typing shader code.
How it fits
A shader graph is an authoring form of a Material asset. You wire value, texture, and math nodes into a single Surface output node, and the compiler walks that graph into the same four WGSL hooks (vertexPosition, surfaceNormal, surfaceColor) and parameter list a written shader would produce. The compiled result is a Material, indistinguishable at render time from one authored as raw WGSL.
The panel, the create flow (+ New Shader Graph), the live mirroring (no Save button), the preview, and the Params table all live on the Shader Editor page. This page documents the node canvas itself: the Surface output, how you work in the graph, every node in the registry, subgraphs, and how the graph compiles.
The node type keys are generic (multiply, sampleTexture, lerp), never a source engine's node names. One registry drives hand-authored graphs and graphs imported from Unity, Unreal, or Godot alike.
The Surface output
The Surface node is permanent: it always exists, cannot be deleted, and every graph ends at it. Wire a node's output into one of its input ports to drive that channel. A port left unwired keeps the engine default for that channel. If nothing is wired to any port, the graph fails to compile with the message nothing wired to the master.
| Port | Drives | Stage | Notes |
|---|---|---|---|
baseColor | Albedo (albedo = rgb) | Fragment | The lit surface colour. |
emission | Emissive (emissive = rgb) | Fragment | Added light. With emission wired but no baseColor, albedo is forced to black. |
alpha | Output alpha (outAlpha) | Fragment | Read only when the surface blend is alpha or additive; ignored on opaque surfaces. |
normal | Shading normal (N = normalize(...)) | Fragment | Replaces the interpolated normal used for lighting. |
metallic | Metalness, clamped to 0-1 | Fragment | Feeds the PBR model. See PBR lighting. |
roughness | Roughness, clamped to 0.04-1 | Fragment | Lower bound 0.04 avoids a zero-roughness singularity. |
occlusion | Ambient occlusion, clamped to 0-1 | Fragment | Multiplies indirect lighting. |
vertexOffset | World displacement (world.xyz += offset) | Vertex | Added to the world position in the vertex stage - the one port evaluated in the vertex stage. |
Because vertexOffset runs in the vertex stage, its subtree can only use nodes that are valid there. Fragment-only nodes (see the catalog below) cannot feed vertexOffset.
The surface's blend mode (opaque / alpha / additive), unlit flag, and opacity are surface settings, edited alongside the graph. Transparent surfaces render unlit by default; a graph opts a transparent surface back into lighting with the surface lit flag (for water or glass specular). Opaque surfaces are always lit.
Working in the graph
The canvas is editable when you own the shader and read-only when you are inspecting an imported one. Editing controls:
- Palette - the + Add Node ▾ button opens a searchable menu grouped by the registry
category. Type to filter by label or type key; press Enter to add the first match. Any subgraphs in the project appear in a Subgraphs group at the bottom. - Wire - drag from a node's output port (right side) to an input port (left side, or a Surface port). Each input holds one wire; wiring a port that already has a wire replaces it.
- Delete - click a wire to cut it. Select a node and press Delete / Backspace, or click the ✕ on the node, to remove it. The Surface node is permanent and never selected for deletion.
- Multi-select - shift-click to toggle nodes into the selection; shift-drag on empty canvas to box-select. ⌘A / Ctrl A selects every node.
- Copy / paste / duplicate - ⌘C / ⌘V copy and paste the selection (with the wires internal to it); ⌘D duplicates in place. The clipboard survives switching between shaders in one session.
- Group → Subgraph - with a selection, the ⧉ Group → Subgraph button collapses those nodes into a reusable subgraph (see Subgraphs).
- Inline edits - a Constant node has a number field; a Property node has a dropdown of the graph's properties; a Split node has a channel dropdown (
.x/.y/.z/.w); Blend, Comparison, Static Switch, and Custom WGSL each expose their own inline control. Any unwiredfloatinput port also shows a small number box, so a one-off constant needs no separate Constant node.
The header above the canvas carries Tidy, Fit, and a zoom slider - documented on the Shader Editor page. Tidy re-lays the whole graph left to right (sources on the left, Surface on the right) as one undo step.
📸 Screenshot - save as img/editor-shader-graph-canvas.png
The node canvas with a Sample Texture 2D feeding a Multiply into the Surface baseColor port, the + Add Node palette open and filtered, and one node's inline number box visible.
Tracing a Surface output
Click a wired Surface port to back-trace what produces it: the node feeding that port and every ancestor up the chain light amber, and every node and wire that does not contribute dims. It answers "which nodes actually affect this channel" on a dense graph without reading every wire by hand. Click the same port again to toggle it off, or press the ✕ Clear highlight button (top-left) or Esc. Tracing changes nothing in the graph and works in a read-only imported shader too.
📸 Screenshot - save as img/editor-shader-graph-trace.png
The baseColor Surface port clicked: its contributing nodes and wires highlighted amber, the unrelated nodes dimmed, and the ✕ Clear highlight button showing top-left.
Variadic math ports
Add, Subtract, Multiply, and Divide start with two input ports (A, B) and grow. Once both base ports are used, a trailing spare port appears (C, then D, up to H), and the node folds every filled port left to right (A op B op C ...). A spare port shows its no-op identity (1 for Multiply/Divide, 0 for Add/Subtract) so an untouched extra reads as ignored. This collapses a chain of two-input operations into one node; capped at ports C-H.
Node catalog
Every entry below is one node in the NODE_REGISTRY. Inputs list the port name and type; the output is a single value. Notes:
- † fragment-only - the node emits WGSL that only exists in the fragment stage (a texture sample,
N/V, or the scene-depth prepass). Wiring it into thevertexOffsetchain (or a Vertex Interpolator's vertex-stage input) fails to compile with a clear message. - ‡ variadic - grows extra input ports C-H (see above).
- Several Input and Geometry nodes are stage-aware: they read a pre-interpolation vertex variable in a vertex chain and the interpolated fragment value otherwise.
Input
| Node | Inputs | Output | Description |
|---|---|---|---|
| Constant | (none) | float | A literal number typed inline (params.value). |
| Vector 2 | X (float), Y (float) | vec2 | Packs two floats into a vec2. |
| Vector 3 | X (float), Y (float), Z (float) | vec3 | Packs three floats into a vec3. |
| Vector 4 | X (float), Y (float), Z (float), W (float) | vec4 | Packs four floats into a vec4. |
| Property | (none) | matches property | Reads an exposed graph property (params.<name>); rank follows the property type. |
| UV | (none) | vec2 | Mesh UV coordinates. Stage-aware (uv in vertex, in.uv in fragment). |
| Vertex Color | (none) | vec3 | Per-vertex colour attribute. Stage-aware. |
| Time | (none) | float | Animated time, globals.time * globals.anim. |
| Sample Texture 2D † | Texture (texture), UV (vec2) | vec4 | Samples a texture. The Texture port binds a Texture2D property (or the node's own embedded texture id); UV defaults to the mesh UV. |
Geometry
| Node | Inputs | Output | Description |
|---|---|---|---|
| Position | (none) | vec3 | World-space position (world.xyz in vertex, in.worldPos in fragment). |
| Object Position | (none) | vec3 | The object origin (pivot) in world space. Vertex-only; degrades to zero in the fragment stage. |
| Normal Vector | (none) | vec3 | World-space surface normal (model-transformed in vertex, N in fragment). |
| View Direction | (none) | vec3 | View direction; V in fragment, a forward constant in vertex. |
Math
| Node | Inputs | Output | Description |
|---|---|---|---|
| Add ‡ | A (float), B (float) | float | A + B (folds C-H). |
| Subtract ‡ | A (float), B (float) | float | A - B (folds C-H). |
| Multiply ‡ | A (float), B (float) | float | A * B (folds C-H). |
| Divide ‡ | A (float=1), B (float=1) | float | A / B (folds C-H). |
| Power | A (float), B (float=1) | float | pow(A, B). |
| One Minus | In (float) | float | 1 - In. |
| Absolute | In (float) | float | abs(In). |
| Negate | In (float) | float | -In. |
| Normalize | In (vec3) | vec3 | normalize(In). |
| Dot Product | A (vec3), B (vec3) | float | dot(A, B). |
| Saturate | In (float) | float | clamp(In, 0, 1). |
| Clamp | In (float), Min (float=0), Max (float=1) | float | clamp(In, Min, Max). |
| Lerp | A (float), B (float), T (float) | float | mix(A, B, T). |
| Fresnel Effect † | Power (float=1) | float | pow(1 - max(dot(N, V), 0), Power). |
| Sine | In (float) | float | sin(In). |
| Cosine | In (float) | float | cos(In). |
| Length | In (vec3) | float | length(In). |
| Distance | A (vec3), B (vec3) | float | distance(A, B). |
| Reflect | In (vec3), Normal (vec3) | vec3 | reflect(In, Normal). |
| Smoothstep | Edge0 (float=0), Edge1 (float=1), In (float) | float | smoothstep(Edge0, Edge1, In). |
| Fraction | In (float) | float | fract(In). |
| Floor | In (float) | float | floor(In). |
| Ceiling | In (float) | float | ceil(In). |
| Round | In (float) | float | round(In). |
| Square Root | In (float) | float | sqrt(In). |
| Exponential | In (float) | float | exp(In). |
| Log | In (float=1) | float | log(In). |
| Sign | In (float) | float | sign(In). |
| Step | Edge (float), In (float) | float | step(Edge, In). |
| Modulo | A (float), B (float=1) | float | A - B * floor(A / B). |
| Minimum | A (float), B (float) | float | min(A, B). |
| Maximum | A (float), B (float) | float | max(A, B). |
| Arctangent2 | A (float), B (float=1) | float | atan2(A, B). |
| Cross Product | A (vec3), B (vec3) | vec3 | cross(A, B). |
| Remap | In (float), InMin (float=-1), InMax (float=1), OutMin (float=0), OutMax (float=1) | float | Rescales In from the In range to the Out range. |
| Rotate About Axis | In (vec3), Axis (vec3=0,1,0), Angle (float) | vec3 | Rotates a vector about an axis by an angle. |
UV
| Node | Inputs | Output | Description |
|---|---|---|---|
| Tiling And Offset | UV (vec2), Tiling (vec2=1,1), Offset (vec2=0,0) | vec2 | UV * Tiling + Offset. |
| Panner | UV (vec2), Speed (vec2=0.1,0) | vec2 | Scrolls UVs over time, UV + time * Speed. |
| Rotator | UV (vec2), Center (vec2=0.5,0.5), Rotation (float) | vec2 | Rotates UVs about a centre point. |
| Polar Coordinates | UV (vec2), Center (vec2=0.5,0.5), RadialScale (float=1), LengthScale (float=1) | vec2 | Converts UVs to polar (radius, angle) coordinates. |
| Flipbook | UV (vec2), Width (float=1), Height (float=1), Tile (float=0) | vec2 | Remaps UVs to one cell of a sprite sheet. |
Procedural
| Node | Inputs | Output | Description |
|---|---|---|---|
| Hash | Seed (vec3) | float | A stable 0-1 hash of a position (same input, same value). |
| Gradient Noise | UV (vec2), Scale (float=1) | float | Perlin-style gradient noise. |
| Voronoi | UV (vec2), AngleOffset (float=2), CellDensity (float=5) | float | Voronoi cell noise. |
| Checkerboard | UV (vec2), Frequency (float=4) | float | A 0/1 checker pattern at the given frequency. |
Channel
| Node | Inputs | Output | Description |
|---|---|---|---|
| Split | In (vec4) | float | Extracts one channel (.x/.y/.z/.w, chosen inline). |
Environment
| Node | Inputs | Output | Description |
|---|---|---|---|
| Sky Color | Dir (vec3, default view dir) | vec3 | Samples the procedural sky colour in a direction. Wire a reflected view direction for a reflection. |
| Scene Depth † | (none) | float | Distance from this fragment to the opaque geometry behind it, from the scene-position prepass; a large sentinel when no prepass ran. |
Artistic
| Node | Inputs | Output | Description |
|---|---|---|---|
| Hue | In (vec3), Offset (float) | vec3 | Shifts hue by the offset. |
| Saturation | In (vec3), Saturation (float=1) | vec3 | Scales colour saturation. |
| Contrast | In (vec3), Contrast (float=1) | vec3 | Scales contrast about 0.5. |
| Invert Colors | In (vec3) | vec3 | 1 - In. |
| Blend | A (vec3), B (vec3), Opacity (float=1) | vec3 | Photoshop-style blend (multiply, screen, overlay, add, lighten, darken - chosen inline) mixed by Opacity. |
Logic
| Node | Inputs | Output | Description |
|---|---|---|---|
| Comparison | A (float), B (float) | float | Returns 1 or 0 for A op B (op chosen inline: >, <, >=, <=, ==, !=). |
| Branch | Predicate (float), True (float), False (float) | float | Runtime select: Predicate > 0.5 ? True : False. Both branches evaluate. |
| Static Switch | On (float), Off (float) | float | Compile-time select: only the chosen branch is compiled; the other subtree emits no WGSL. |
Vertex
| Node | Inputs | Output | Description |
|---|---|---|---|
| Vertex Interpolator | In (vec4) | vec4 | Computes its input once per vertex and reads it back interpolated in the fragment stage. Up to 8 per graph. In a vertex chain it is a pass-through. |
Utility
| Node | Inputs | Output | Description |
|---|---|---|---|
| Custom WGSL | (none) | float (rank set inline) | An escape hatch: a raw WGSL expression you type (referencing in.*, params.*, N, V), with its output type chosen inline (f / v2 / v3 / v4). |
Subgraphs
A subgraph is a reusable, single-output graph fragment - the same idea as a Unity Sub Graph or an Unreal Material Function. A parent graph references it through a subgraph node whose input ports mirror the subgraph's declared inputs; inside the subgraph, an input node stands for a value the parent wires in.
Create one with ⧉ Group → Subgraph: select a set of nodes and the editor lifts them into a new subgraph, replacing them with a single subgraph node. Wires crossing into the selection become the subgraph's inputs; the one wire (or master port) leaving the selection becomes its single output. The selection must have exactly one output boundary - zero or more than one is rejected, since subgraphs are single-output.
The same subgraph can be dropped into many graphs from the palette's Subgraphs group. Before compilation, expandSubgraphs inlines every subgraph node into the parent graph - prefixing internal ids, rewiring each input node to the parent's wired source, and splicing the subgraph's output in place of the node. Nested subgraphs expand recursively (guarded against a subgraph that references itself). The compiler itself stays subgraph-unaware, so a flattened graph compiles identically whether or not subgraphs were used.
How it compiles
Compilation is a single-static-assignment (SSA) topological walk that starts at the wired Surface ports and pulls their subtrees backward. Only nodes reachable from a wired Surface port emit code; anything disconnected is skipped entirely.
Details:
- Per-port walk. Each Surface port is walked into its hook buffer at its stage:
normalintosurfaceNormal(fragment, walked first), thenbaseColor/emission/metallic/roughness/occlusionand (for transparent surfaces)alphaintosurfaceColor(fragment), thenvertexOffsetintovertexPosition(vertex). - Each node's
emitproduces a WGSL value expression using the shared value helpers (the samesplat/bin/asVec3helpers the Unity transpiler uses), so a hand-built graph and an imported one compile to identical WGSL. - Memoisation and CSE. Node outputs are memoised per stage, and two nodes that emit the same WGSL collapse to one
let(common-subexpression elimination), keyed per stage. A value's expression is written once as an SSA line and referenced by id. - Stage awareness. Position, UV, Normal, and similar nodes are keyed by stage, because they read a different variable in the vertex hook than in the fragment hook. A Vertex Interpolator computes its input in the vertex stage, writes it to a
awakenVsgvarying (max 8), and reads the interpolated value in the fragment stage. - Errors throw
SGUnsupportedwith a readable message, and the editor keeps the last good compile. Cases: an unknown node type, a cycle (cycle at node X), an edge to a missing node, afragmentOnlynode reached in the vertex stage, a value read of a texture or bool property, more than four textures (MAX_MATERIAL_TEXTURES), or nothing wired to the Surface. - Engine-agnostic. One registry compiles hand-authored graphs and graphs imported from Unity ShaderGraph, Unreal, or Godot - every source maps its nodes onto these generic types, and every path shares this compiler.
The output is a Material (hooks, packed params, and resolved texture bindings) ready to register with the renderer - the same artifact a written WGSL shader produces.