Introduction
An enemy burns away. The edge where a shield meets the ground lights up. The scenery behind a heat source shimmers.
Effects like these start less with complex textures and more with deciding which input drives the effect, and which part of the object it changes.
In this article, we'll build six visually and technically different effects using URP and Shader Graph in Unity 6.3. The sample project is public and includes editable graphs and reusable prefabs. Here's a video showing all six effects:
| Effect | What it looks like | Core idea |
|---|---|---|
| 01 Emissive dissolve | An object disappears in patches, leaving a glowing edge | Threshold a noise pattern |
| 02 Intersection shield | A glowing band appears where a shield intersects the ground or an obstacle | Use screen-space depth differences |
| 03 Heat haze | The background behind a heat source shimmers | Offset the coordinates used to sample the background |
| 04 Triplanar snow | Texture a rock without UV unwrapping and cover its upward-facing surfaces with snow | Use projection coordinates and surface orientation |
| 05 Interactive grass | Grass bends in the wind and around an approaching character while its roots stay fixed | Displace vertices according to their height |
| 06 Scan pulse | A glowing band travels continuously across floors, walls, and buildings | Use distance in world space |
The graphs, materials, required meshes and textures, and control scripts are grouped under Runtime. Each effect has also been tested when imported into a separate project.
Open the sample project
Public repository / latest version · Pinned commit used for this article · Gallery scene · Validation record
Use Unity Hub to open the repository folder with Unity 6000.3.22f1. After package resolution and compilation finish, open Assets/ShaderGraphTechniques/Demo/Scenes/00_Gallery.unity and enter Play mode. Implementation links throughout this article point to the pinned commit.
The graphs, materials, prefabs, and control scripts for all six effects are under Assets/ShaderGraphTechniques/Runtime/. Each effect's GETTING_STARTED_JA.md lists its dependencies and setup requirements. Open the included .shadergraph in the Graph Editor to inspect the actual connections.
The video-selection and publication-status notes in the pinned commit are outdated. For the video, use the link at the beginning of this article.
Shared setup
Target environment
Unity 6.3 belongs to the 6000.3 release family. Its official manual links to the Shader Graph 17.3 package documentation for this generation of features. This article targets the tested combination of 6000.3.22f1 and URP / Shader Graph 17.3.0. This does not mean that all six effects became possible for the first time in Unity 6.3.1
| Setting | Assumption in this article |
|---|---|
| Editor | Unity 6.3 LTS / 6000.3.22f1
|
| Render pipeline | URP 17.3.0 / Shader Graph 17.3.0
|
| Renderer | Universal Renderer / Forward / standard Render Graph path |
| Color space | Linear |
| Camera | A single Base Camera; start with Perspective |
| Graph precision | Single as the baseline |
| Target renderer | A regular MeshRenderer; HDRP, Built-in, and the 2D Renderer are outside this article's scope |
| Capture environment | Windows 64-bit, NVIDIA GeForce RTX 3060 Laptop GPU, Direct3D 12 |
The sample includes ProjectSettings/ProjectVersion.txt, Packages/manifest.json, and Packages/packages-lock.json. Open it with this combination first rather than swapping in a different generation of URP or Shader Graph. When moving an effect into your own project, check the versions resolved by Package Manager there as well.
Distances and displacements in the sample scenes are tuned for 1 Unity unit = 1 m. Adjust distance-related properties when moving an effect into a game with a different scale.
These examples do not require a custom Renderer Feature or a hand-written shader implementation. Shader Graph handles the visuals; the runtime C# code handles parameter control, material ownership, and grass bounds.
Depth Texture and Opaque Texture are different settings
In the URP Asset, Depth Texture enables depth sampling, while Opaque Texture provides the background color after opaque objects have been rendered. The camera can override these settings.2
| Effect | Depth Texture | Opaque Texture |
|---|---|---|
| Intersection shield | Required | Not required |
| Heat haze | Not required for the basic version | Required |
| Other effects | Not required for the effect's calculations | Not required |
The sample enables both, but its project settings are not included in the exported packages. Enable only the settings needed by the effect in the destination project. Check not just Graphics settings, but also which URP Asset and Renderer the active Quality level and camera actually use.
For the heat-haze comparison, start with Opaque Downsampling = None. A downsampled background texture changes sharpness independently of the distortion itself.2
Adjust bright colors and Bloom separately
To create an emissive-looking area, feed bright values into Emission in a Lit Graph or Base Color in an Unlit Graph. Bloom is what makes that brightness spread into the surrounding image.345
When using Bloom, check the URP and camera HDR settings, the camera's Post Processing setting, the Global Volume and its Bloom override, and the relationship between the camera's Volume Mask and the Volume's Layer. Simply adding a Volume is not enough.67
Make each effect readable without Bloom first, then add a subtle amount. The minimal comparison scenes use no Fog and do not require TAA, motion blur, or dynamic resolution.
Read the formulas as node connections
The text blocks below are compact descriptions of node connections, not HLSL you can paste and execute.
Saturate clamps a value to the 0–1 range. Step(edge, x) returns 0 when x < edge, and 1 otherwise. Smoothstep(a, b, x) smoothly transitions from 0 to 1 as x moves from a to b. Throughout this article, we keep a < b.89
Pws means Position / World, and PosOS means Position / Object. Notation such as .xz means extracting the required components with Split and Combine, or with Swizzle. We use a consistent world-space coordinate system within URP.1011
For each public property, set a clear display name and a Reference name matching the identifier used here, such as _Progress. In Shader Graph 17.3, the setting that exposes a property in the material Inspector is named Show In Inspector.12
In effects 01–04 and 06 at the pinned commit, Position / Object → Vertex / Position is explicitly connected even though those graphs do not deform vertices. The Windows StrictMode build passed with that connection in place, so use the same connection when building the graphs manually. The validation record explains the Motion Vector pass fix behind it.
Give animated effects a time input you can pause
In effects 03 and 05, t normally comes from the Time output of the Time node. The sample puts the following expression in Runtime/Common/ManualTime.shadersubgraph, allowing an effect to be frozen at a chosen time for capture or comparison.13
t = Lerp(Time.Time, _ManualTime, Saturate(_UseManualTime))
_UseManualTime is a Float in the 0–1 range, with a default of 0. _ManualTime is a Float measured in seconds, also defaulting to 0. The shared Sub Graph has explicit inputs and does not depend on a capture-only global variable. The property tables below are starting values for building the graphs manually; they are not values held constant throughout every frame of the video.
01 — Dissolve with a glowing edge
Watch this effect (0:03) · Implementation folder at the pinned commit
The graph is SG_01_Dissolve.shadergraph. Pass a value from 0 to 1 to the prefab's DissolveController.SetProgress(value) to control it. To use it on your own MeshRenderer with UV0, add MaterialInstanceOwner and DissolveController to the same GameObject.
This works well for enemy deaths, summoning, and item appearances. Instead of lowering the opacity of the entire object, it removes parts of the surface according to a noise pattern.
Graph settings and properties
Create a URP Lit Graph with Surface Type = Opaque and Alpha Clipping = On. Alpha Clipping discards pixels whose Alpha is below a threshold.3
| Reference | Type and starting value | Purpose |
|---|---|---|
_Progress |
Float, 0–1; default 0 | Fully visible at 0, fully removed at 1 |
_NoiseScale |
Float, 8 | Noise pattern density |
_EdgeWidth |
Float, 0.06 | Width of the glowing band |
_BaseColor |
Color; your choice of surface color | Normal surface color |
_EdgeColor |
HDR Color; orange | Glow color |
_EdgeIntensity |
Float, 3 | Glow intensity |
Start with a Sphere or Capsule that has UV0. Because this version uses UVs, a mesh without UVs—or with heavily overlapping UVs—will not produce the intended pattern.
Core connections
Feed UV0's XY components and _NoiseScale into Simple Noise. Pass its output through Saturate and call the result m.14
p = Saturate(_Progress)
w = Max(_EdgeWidth, 0.0001)
m = Saturate(SimpleNoise(UV0.xy, _NoiseScale))
cut = Lerp(-w - 0.0001, 1.0001, p)
visible = Step(cut, m)
edge = visible * (1 - Smoothstep(cut, cut + w, m))
Connect cut to the Edge input of Step, and m to In. The glowing band occupies a width of w on the surviving side of the boundary.
Simple Noise ── Saturate ──┬─ Step ─────────────── Alpha
│
└─ Smoothstep → One Minus
× visible
× HDR Color
× Intensity ── Emission
Connect the Fragment outputs as follows:
Base Color = _BaseColor.rgb
Emission = _EdgeColor.rgb * _EdgeIntensity * edge
Alpha = visible
Alpha Clip Threshold = 0.5
Metallic = 0
Smoothness = 0.35
Why extend the threshold slightly beyond the endpoints?
Using only Step(_Progress, m) leaves awkward endpoint behavior. In particular, a point where m = 1 can remain visible even at _Progress = 1.
Here, the threshold starts at -w - ε and ends at 1 + ε. With m constrained to 0–1, this guarantees no clipping or glowing edge at the start, and complete removal at the end. Those endpoints follow directly from the formula above. Progress is not, however, the percentage of surface area that has disappeared.
Integration notes
Clipping in this graph is a visual operation. Control Colliders and gameplay state separately. Check shadows with your actual lights and a Player build so that the object's shadow does not remain after its surface has disappeared.
If the glow clips to white, lower _EdgeIntensity first rather than changing Bloom, and inspect the shape of the boundary. This example is intended to work with Opaque + Alpha Clipping, not by switching to transparent blending to hide problems.
02 — A shield that glows where it intersects the environment
Watch this effect (0:13) · Implementation folder at the pinned commit
The graph is SG_02_IntersectionShield.shadergraph. It needs no dedicated runtime controller; adjust its exposed material properties. The video hides the two static blue comparison objects and shows the shield and moving pillar. Those comparison objects remain in the technical test scene, so its visible object count differs from the video's.
Place a transparent sphere so that the floor or a pillar intersects it. A glowing band around that intersection makes it look more like an energy field than a plain transparent sphere.
Graph settings and properties
Use a URP Unlit Graph with the following settings. Transparent blending, rendered faces, and depth writing are configured in the Graph Inspector.4
Surface Type = Transparent
Blending Mode = Alpha
Render Face = Front
Depth Write = Force Disabled
Depth Test = L Equal
Cast Shadows = Off
Start with the camera outside the sphere. The basic version avoids double-sided rendering because it adds overlapping transparent front and back surfaces.
| Reference | Type and starting value | Purpose |
|---|---|---|
_ContactWidth |
Float, 0.15 | Depth-difference range that produces the glow |
_RimPower |
Float, 4 | Rim sharpness |
_RimIntensity |
Float, 2 | Rim brightness |
_ContactIntensity |
Float, 5 | Intersection-band brightness |
_ShieldColor |
HDR Color; blue-green | Shield color |
_Opacity |
Float, 0–1; default 1 | Visibility |
Use the dedicated depth-difference node
Add Scene Depth Difference and set its Sampling Mode to Eye. Connect Screen Position / Default to Scene UV, and Position / World to Position WS.
This node returns the difference between the sampled scene depth and the depth of the supplied world-space position. Eye mode uses meters, and the result is negative when the sampled surface is closer to the camera.15
d = SceneDepthDifference(Eye)
w = Max(_ContactWidth, 0.0001)
contact = Step(0, d) * (1 - Saturate(d / w))
contact approaches 1 as the depths get closer and falls to 0 as they separate. This measures depth along the camera's viewing direction: it is not the shortest distance between objects or a Collider contact test. Changing the view changes the appearance of the band.
Add a Fresnel Effect node as well. Keep Normal and View Direction in the same World space, and pass _RimPower to Power. Call the output rim. Fresnel Effect emphasizes surfaces seen at a grazing angle.16
Base Color = _ShieldColor.rgb
* (0.2 + rim * _RimIntensity
+ contact * _ContactIntensity)
Alpha = Saturate((0.04 + 0.25 * rim + 0.8 * contact) * _Opacity)
What to check when the intersection does not glow
First, make sure Depth Texture is enabled for the active URP Asset and camera. Then give the intersecting floor or pillar an ordinary opaque material, such as URP/Lit.
This technique can only read surfaces present in the depth texture at that point in rendering. It does not detect intersections with every transparent object. Do not treat enabling Depth Write on the shield itself as a fix.
Also place an opaque box in front of the camera and check that it hides the shield behind it as usual. Changing the depth test to Always would turn this into a different, see-through effect.
03 — Heat haze that distorts the background
Watch this effect (0:23) · Implementation folder at the pinned commit
The graph is SG_03_HeatHaze.shadergraph. The prefab's HeatHazeController provides SetManualTime(seconds), UseRealtime(), and SetOpacity(0) for a frozen comparison, normal time, and hiding the effect.
This heat shimmer can be used above flames, exhaust vents, magic circles, or hot ground.
Rather than overlaying a semitransparent color, it resamples the background image at slightly offset coordinates to make the air appear to shimmer.
Graph settings and properties
Use a URP Unlit Graph with Transparent / Alpha, Depth Write = Force Disabled, Depth Test = L Equal, and Cast Shadows = Off. Start by applying it to a Quad whose front face points toward the camera.
| Reference | Type and starting value | Purpose |
|---|---|---|
_NoiseScale |
Float, 6 | Distortion pattern density |
_Distortion |
Float, 0.01 | Distortion amount in normalized screen UVs |
_Opacity |
Float, 0–1; default 1 | Blend amount for the distorted background |
_FlowA |
Vector2, (0.12, 0.20) | Motion of the first noise pattern |
_FlowB |
Vector2, (-0.18, 0.10) | Motion of the second noise pattern |
_ScreenEdgeFade |
Float, 0.04 | Width over which distortion fades near screen edges |
Also add the shared _UseManualTime and _ManualTime properties.
Use the Quad's UVs and screen UVs for different jobs
Build the distortion pattern from the Quad's UV0, but use the XY components of Screen Position / Default to sample the background. Default provides normalized 0–1 screen coordinates; it is not the same as Raw.17
u = UV0.xy
s = ScreenPosition(Default).xy
n1 = SimpleNoise(u * _NoiseScale + t * _FlowA, Scale = 1)
n2 = SimpleNoise(u * _NoiseScale + t * _FlowB + (19.7, 3.1), Scale = 1)
v = 2 * Vector2(n1, n2) - Vector2(1, 1)
The two noise values form v, which supplies the two components of the background sampling offset.
Next, build a mask that softens the Quad's perimeter:
r = Length(u * 2 - 1)
shape = 1 - Smoothstep(0.65, 1, r)
edgeDist = Min(Min(s.x, 1 - s.x), Min(s.y, 1 - s.y))
screen = Smoothstep(0, Max(_ScreenEdgeFade, 0.0001), edgeDist)
sampleUV = Clamp(s + v * _Distortion * shape * screen,
(0.001, 0.001), (0.999, 0.999))
shape hides the Quad's square boundary, and screen reduces distortion near the screen edges. The Clamp limits are fixed margins for this basic version, not an exact half-texel correction.
Pass sampleUV into the XY components of the Scene Color UV input, and use the output directly as the color. If you construct a Vector4, set the unused ZW components to 0.
Base Color = SceneColor(sampleUV)
Alpha = shape * Saturate(_Opacity)
Do not multiply RGB by Alpha beforehand; let Alpha blending perform the compositing. This example also does not multiply Scene Color by an HDR intensity to make it glow.
There are limits to the background this method can read
In URP, Scene Color reads the Opaque Texture copied before transparent objects are rendered. Use it in the Fragment stage. It is not a way to freely distort the entire rendered frame, including glass and particles.18
As a result, overlapping transparent objects or multiple heat-haze surfaces can look as though they overwrite transparent effects drawn earlier. Changing draw order cannot recover information absent from the background copy.
Because this only offsets screen-space sample positions, it can also pull in colors from other objects near foreground silhouettes. Screen-edge fading and Clamp do not solve every silhouette artifact.
Evaluate the basic version without Fog, against an opaque background, using a single Quad. To compare with the effect disabled, use _Opacity = 0 or disable the Renderer. Setting distortion to 0 does not stop the shader from drawing the background copy.
04 — Triplanar rocks with snow on upward-facing surfaces
Watch this effect (0:33) · Implementation folder at the pinned commit
The graph is SG_04_TriplanarSnow.shadergraph. It needs no dedicated runtime controller. Adjust _SnowAmount on the material, and try replacing the included rock texture with your own.
This effect textures rocks and cliffs without well-organized UVs, then spreads snow across them. Changing the snow color to green or dark brown also makes it useful for moss or dirt coverage.
Graph settings and properties
Use a URP Lit Graph with Surface Type = Opaque and Alpha Clipping = Off.
| Reference | Type and starting value | Purpose |
|---|---|---|
_BaseMap |
Texture2D | A tileable rock color texture |
_BaseTint |
Color; white | Rock color tint |
_TextureScale |
Float, 1 | Texture density in world space |
_SnowAmount |
Float, 0–1; default 0.5 | Snow coverage |
_SnowColor |
Color; slightly blue-tinted white | Snow color |
_SnowNoiseScale |
Float, 1.5 | Coverage pattern density |
_SnowSoftness |
Float, 0.12 | Softness of the snow boundary |
Import _BaseMap as a color texture with sRGB enabled, Wrap Mode set to Repeat, and Mip Maps enabled.19 The sample includes a custom, pre-generated texture named TEX_SeamlessRock.png.
Texture the rock with Triplanar
Set the Triplanar node's Type to Default and Input Space to World. Connect _BaseMap to Texture and _TextureScale to Tile, and start with Blend at 4. Keep Position and Normal in World space as well.
Triplanar projects a texture from three directions and blends the results according to surface orientation. It samples the input texture three times, so its cost is not the same as a single ordinary texture lookup. Use this node in the Fragment stage.20
rock = Triplanar(_BaseMap,
Position = Pws,
Normal = Normalize(NormalVector(World)),
Tile = _TextureScale,
Blend = 4).rgb * _BaseTint.rgb
Multiply upward-facing slope by coverage
Use Normal Vector / World for surface orientation. Take its Dot Product with (0, 1, 0) to measure how much it faces upward.21
up = Dot(Normalize(NormalVector(World)), (0, 1, 0))
slope = Smoothstep(0.35, 0.85, up)
n = Saturate(SimpleNoise(Pws.xz, _SnowNoiseScale))
a = Saturate(_SnowAmount)
s = Max(_SnowSoftness, 0.0001)
cut = Lerp(1 + s + 0.0001, -s - 0.0001, a)
cover = Smoothstep(cut - s, cut + s, n)
snow = slope * cover
slope limits which surfaces can receive snow, and the noise-based cover fills those surfaces. With this formula, Snow Amount 0 means no snow; at 1, coverage reaches the limit allowed by slope. Even at 1, vertical walls do not turn completely white.
Base Color = Lerp(rock, _SnowColor.rgb, snow)
Smoothness = Lerp(0.2, 0.35, snow)
Metallic = 0
This is the appearance of snow, not a volume simulation
The basic version changes color and surface appearance only. It does not thicken the silhouette with snow or detect areas sheltered by a roof. An upward-facing surface can receive snow even indoors.
Because the projection is in world space, moving the object changes its position relative to the pattern. This makes the setup suitable for stationary rocks and buildings. For a moving prop that needs the pattern to stay attached, redesign the projection coordinates in Object space. The normal test that restricts snow to upward-facing surfaces can still remain in World space.
“No UVs required” does not mean “no inputs or conditions required.” The texture's Repeat setting, the normals, and the projection space are part of this effect's input requirements.
05 — Grass that bends around an approaching character
Watch this effect (0:43) · Implementation folder at the pinned commit
The graph is SG_05_InteractiveGrass.shadergraph. Assign the target Transform to the prefab's InteractiveGrassController.Interactor. Use SetBend(radius, strength) to adjust interaction bending, SetManualTime(seconds) for a frozen comparison, and UseRealtime() to return to normal time.
This adds bending around an approaching character to grass that already sways in the wind.
The central idea is to keep root vertices fixed and increase displacement toward each blade's tip. This changes the Vertex stage rather than just the surface color.
Define the mesh requirements first
In the basic version, narrow polygons form the grass silhouette. No grass image is used for Alpha Clipping.
Make each blade a tapered strip with roughly six subdivisions along its height. Set UV0's Y component to 0 at the root and 1 at the tip. Even when multiple blades share one mesh, each blade needs its own 0–1 range in UV0.Y.
A single unsubdivided Quad cannot form a curve along its length. The sample's MESH_GrassCluster.asset is already generated, with six vertical subdivisions per blade. You do not need to run a generation script in the destination project.
Graph settings and properties
Use a URP Unlit Graph with Opaque, Alpha Clipping = Off, and Render Face = Both. Because this basic version does not calculate lighting, we can leave reconstructing deformed normals out of the example.4
| Reference | Type and starting value | Purpose |
|---|---|---|
_InteractorPositionWS |
Vector3, (0, 0, 0) | World position of the object pushing the grass aside |
_InteractorEnabled |
Float, 0–1; default 0 | Stops proximity-based bending at 0 |
_BendRadius |
Float, 1 | Horizontal interaction radius |
_BendStrength |
Float, 0.6 | Interaction displacement |
_WindDirectionXZ |
Vector2, (1, 0) | Wind direction |
_WindStrength |
Float, 0.08 | Wind displacement |
_WindSpeed |
Float, 1.5 | Wind animation speed |
_RootColor |
Color; dark green | Root color |
_TipColor |
Color; light green | Tip color |
Also add the shared manual-time properties. Distances and displacements are tuned for the sample scenes' 1 Unity unit = 1 m scale.
Build the vertex displacement
Here, Pws is the undeformed Position / World, evaluated in the Vertex stage.
h = Saturate(UV0.y)
rootMask = h * h
q = Pws.xz - _InteractorPositionWS.xz
d = Length(q)
outward = q / Max(d, 0.0001)
radius = Max(_BendRadius, 0.0001)
weight = (1 - Smoothstep(0, radius, d)) * Saturate(_InteractorEnabled)
windDir = _WindDirectionXZ / Max(Length(_WindDirectionXZ), 0.0001)
phase = Pws.x * 0.7 + Pws.z * 0.9 + t * _WindSpeed
wind = windDir * Sin(phase) * _WindStrength
moveXZ = (outward * weight * _BendStrength + wind) * rootMask
movedWS = Pws + Vector3(moveXZ.x, 0, moveXZ.y)
At the root, h * h is 0, so the vertices stay fixed. Displacement increases toward the tip. The formula avoids division by zero even when a vertex lies exactly at the interaction center and d = 0. At that point, the outward direction is zero and only wind contributes to displacement.
Finally, pass movedWS through Transform / World → Object / Type = Position and connect the result to the Vertex Position input.
Position(World) → Add displacement
→ Transform(World → Object, Position)
→ Vertex / Position
The Vertex Position input expects Object space. Do not connect a World-space position directly. The Transform node's Position type includes translation, unlike Direction.311
For the Fragment color, start with a simple root-to-tip gradient:
Base Color = Lerp(_RootColor.rgb, _TipColor.rgb, Saturate(UV0.y))
The game supplies the interactor's position
On the C# side, pass the target Transform's world position to _InteractorPositionWS, and set _InteractorEnabled = 1 only while the target is valid. If it is unassigned or has been destroyed, return the value to 0 so that the grass does not react to a nonexistent character at the origin.
This basic version reacts only to horizontal distance. A character on a bridge at a different height can therefore affect grass below. For multilevel environments, add a height condition or separate the affected grass into groups. This is not physical collision, persistent footprints, or a simulation that preserves blade length.
Without correct bounds, grass can disappear at the screen edges
When a shader moves vertices, the bounds used for culling must contain the deformed geometry. Unity's Renderer.localBounds can be overridden for this purpose. However, that override is not saved into the Scene or Prefab, so it must be applied again at runtime.22
The sample's InteractiveGrassController reapplies bounds when enabled and when SetBend is called. Its margin comes from boundsPadding—default 0.9—not from automatically estimating the deformation. Review that margin when increasing bend or wind strength, and reapply the bounds if scale changes at runtime. The bounds must cover the maximum displacement; the Collider is not updated.
Do not make Static Batching or Dynamic Batching a prerequisite for the first test. In particular, vertex processing that converts back to Object space needs separate checks to ensure batching does not change its coordinate assumptions. Both Static and Dynamic Batching involve combining vertices in world space.23
06 — A scan pulse that travels across buildings and terrain
Watch this effect (0:53) · Implementation folder at the pinned commit
The graph is SG_06_ScanPulse.shadergraph. The prefab's ScanPulseController provides SetCenter(transform) and SetState(active, radius). When no center is assigned, it uses the component's own position.
This works for exploration scans, sonar, or magical detection. The same wave should travel across walls and steps as well as the floor.
The basic version is not a post-processing effect: the scan pulse is drawn inside the affected materials.
Graph settings and properties
Use a URP Lit Graph with Opaque and Alpha Clipping = Off.
| Reference | Type and starting value | Purpose |
|---|---|---|
_ScanCenterWS |
Vector3, (0, 0, 0) | Wave center |
_ScanRadius |
Float, 0 | Current radius |
_ScanWidth |
Float, 0.25 | Half-width of the glowing band |
_ScanActive |
Float, 0–1; default 0 | Whether the wave is visible |
_ScanColor |
HDR Color; blue-green | Wave color |
_ScanIntensity |
Float, 3 | Wave brightness |
_BaseColor |
Color; gray | Normal surface color |
Light up the area close to a spherical shell
In the Fragment stage, calculate the distance between Position / World and _ScanCenterWS.10
distanceToCenter = Distance(Pws, _ScanCenterWS)
radius = Max(_ScanRadius, 0)
width = Max(_ScanWidth, 0.0001)
distanceToShell = Abs(distanceToCenter - radius)
ring = (1 - Smoothstep(0, width, distanceToShell))
* Saturate(_ScanActive)
The glow is brightest on the sphere where distanceToCenter = radius, then fades inward and outward. Because _ScanWidth applies on both sides of the sphere, the full band with a nonzero value is approximately twice that width.
Base Color = _BaseColor.rgb
Emission = _ScanColor.rgb * _ScanIntensity * ring
Metallic = 0
Smoothness = 0.25
Increasing _ScanRadius moves the glowing band through the intersections between that sphere and the floor, walls, and buildings. This uses XYZ distance, not just XZ distance, so it is not the same calculation as projecting a flat circle from the floor onto a wall.
Send the same wave parameters to multiple objects
Give every participating Renderer the same center, radius, and width. If the wave lines up where it crosses from the floor to a wall, the coordinate spaces are consistent.
For a repeating pulse, set _ScanActive = 0 before resetting the radius to 0 to avoid an unwanted flash at the center. To run two independent waves simultaneously, separate the ownership of the materials they control as well.
Do not describe this as affecting the entire scene
Materials that do not use this graph will not show the pulse. To add it to an existing, more complex Lit Graph, move the distance-to-ring calculation into a Sub Graph and add its result to the existing Emission.
The effect also uses normal depth testing, so it does not reveal objects behind a wall. A full-screen scan or detection display through occluders requires a different rendering design.
Moving the effects into another project
Keep Runtime separate from Demo
The dependency direction is Demo → Runtime. Runtime prefabs contain no Camera, Light, Volume, Canvas, or Demo Manager, and they do not require the Gallery. That does not mean the destination scene needs no camera or lighting; it means you do not have to bring along the demonstration setup.
The reusable parts are the graph, its referenced Sub Graphs, materials, required textures and meshes, and runtime controllers only where needed. The prefabs bundle these into configured objects. Each package also includes a setup guide and a LICENSE file inside Assets.
Do not copy .shadergraph or .mat files arbitrarily through a file explorer. Transfer them in a way that preserves references and .meta files.24 The sample's Package Exporter gathers dependencies, excludes other effects, Demo, Tests, Editor, and ProjectSettings, and passes the resulting set to Unity's Export Package API. Including the required dependencies is not the same as exporting the entire project.25
| Effect | What to bring for the visual effect | Destination requirements |
|---|---|---|
| Dissolve | Graph and material; control code for automated playback | A mesh with UV0 |
| Intersection shield | Graph, material, and a mesh such as a sphere | Depth Texture and an opaque intersection target |
| Heat haze | Graph, time Sub Graph, material, and Quad | Opaque Texture; an opaque background for the basic version |
| Snow | Graph, material, and rock texture | Valid normals, projection coordinates, and Repeat settings |
| Grass | Graph, time Sub Graph, material, dedicated mesh, and position/bounds control | Root mask in UV0.Y and a target Transform |
| Scan pulse | Graph and material; control code for automated playback | Consistent parameters on every participating object |
Generate packages locally, then import them
The six individual packages and ShaderGraphTechniques-Runtime-All.unitypackage have already been generated, inspected, and tested through isolated imports. However, the generated archives are excluded from Git. Open the original sample project and run Tools > Shader Graph Techniques > Export Runtime Packages to generate them locally under Artifacts/Packages/. The distribution and migration record lists package names and inspection results.
Prepare a new URP project in Unity 6000.3.22f1 and confirm that URP / Shader Graph resolve to 17.3.0. Use Unity's Assets > Import Package > Custom Package... to import just the .unitypackage for the effect you need.26 Enable Depth Texture for effect 02 or Opaque Texture for effect 03 in the destination project, then place the corresponding PF_*.prefab in the scene or assign MAT_*.mat to your Renderer.
Individual packages include the Common files they need. When you import another effect later, shared assets retain the same original GUIDs. You do not need to copy the entire Runtime folder separately.
Share materials deliberately
When two prefabs are in a scene, changing the Progress of one should not unexpectedly make the other disappear.
In effects 01, 03, 05, and 06, MaterialInstanceOwner creates an instance for each Renderer material slot and reuses it while initialized. On disable, it restores the original references and destroys only the instances it created. It does not modify shared material assets or global shader values.
For effects 02 and 04, adjusting a material in its Inspector affects every object sharing that material. To control these independently per prefab, use separate material assets or instances with explicit ownership and cleanup. Distinguish changing a shared material asset from changing one placed object.
MaterialPropertyBlock is not an automatic optimization. Unity's API documentation explicitly states that it is incompatible with the SRP Batcher. Evaluate ease of per-object control separately from CPU rendering cost.27
Isolated imports were tested in seven independent projects
The six individual packages and Runtime-All were imported separately into seven independent, newly created URP projects, without copying the original project's Assets or Library directories. All seven imports passed (7/7).
Without the Gallery or demo Manager, the tests checked prefabs, graphs, supported shaders, and actual Unity renders of states A/B controlled through the APIs or public material values, image differences, zero missing scripts, and no magenta fallback. In the Runtime-All project, all six effects were rendered individually. The package archives themselves were also checked against the manifest for every pathname, GUID, and asset SHA-256, with 7/7 content checks passing. These results are documented in the validation record and distribution and migration record.
Separately, a fresh clone was reimported without a Library directory. Package resolution for all 57 packages and script compilation succeeded, followed by EditMode 9/9 and GPU-rendered PlayMode 4/4. Attempts that failed to render with Null Graphics are not counted as successful results.
What remains untested
The tested GPU and Graphics API are the RTX 3060 Laptop GPU and Direct3D 12 listed earlier. XR, mobile, macOS / Linux, other GPUs, Direct3D 11 / Vulkan / Metal, and real-time 60 fps performance remain untested. Multiple cameras, Camera Stacks, Fog, Heat Haze compositing with transparent objects, and long-running integration into a real game are also untested. Zero, negative, and mirrored scales are outside the supported scope. Successful isolated imports do not guarantee support across all those environments.
Measure rendering cost with the target GPU, resolution, Graphics API, and number of visible instances held consistent. Do not call an effect lightweight just because it has few nodes. Background copies, depth sampling, overlapping transparent surfaces, and Triplanar's multiple texture samples are different sources of cost.
Conclusion
Rather than remembering these as six unrelated tricks, focus on the inputs they use.
Dissolve and snow turn values into regions to reveal or cover. The shield and heat haze read screen information from the camera. Grass and the scan pulse use positions and distances to drive their changes.
In the fixed environment, the sample passed EditMode 9/9, PlayMode 4/4, a Windows StrictMode build with 0 errors / 0 warnings, and package-content and isolated-import checks at 7/7 each. Both test suites also passed on a fresh clone. Use the pinned commit to follow along with the video and implementation.
Can you explain the inputs and assumptions when the effect moves to a different mesh, position, or project? Getting that right is what makes a Shader Graph easier to bring into your own game. Just do not confuse a recording at 60 fps with real-time performance—measure in your game's actual target environment.
-
Unity documentation: New in Unity 6.3. Revisited September 14, 2026. ↩
-
Unity documentation: Universal Render Pipeline asset reference for URP. Depth Texture, Opaque Texture, Opaque Downsampling, and related settings. ↩
-
Unity documentation: Lit shader graph reference for URP. Vertex/Fragment inputs and graph settings. ↩
-
Unity documentation: Unlit shader graph reference for URP. Transparent blending, Render Face, Depth Write/Test, and related settings. ↩
-
Unity documentation: Bloom in URP. ↩
-
Unity documentation: Camera component reference for URP. Camera Post Processing and Volume-related settings. ↩
-
Unity documentation: Post-processing in URP. ↩
-
Unity documentation: Step Node / Shader Graph 17.3. ↩
-
Unity documentation: Smoothstep Node / Shader Graph 17.3. ↩
-
Unity documentation: Position Node / Shader Graph 17.3. ↩
-
Unity documentation: Transform Node / Shader Graph 17.3. Coordinate spaces and the differences between Position, Direction, and Normal. ↩
-
Unity documentation: Property Types / Shader Graph 17.3. Reference, Show In Inspector, and related settings. ↩
-
Unity documentation: Time Node / Shader Graph 17.3. ↩
-
Unity documentation: Simple Noise Node / Shader Graph 17.3. ↩
-
Unity documentation: Scene Depth Difference node / Shader Graph 17.3. Eye mode, input coordinates, and the sign of the difference. ↩
-
Unity documentation: Fresnel Effect Node / Shader Graph 17.3. ↩
-
Unity documentation: Screen Position Node / Shader Graph 17.3. ↩
-
Unity documentation: Scene Color node / Shader Graph 17.3. Opaque Texture, shader stage, and draw-order requirements. ↩
-
Unity documentation: Default texture type reference. sRGB, Mip Maps, and Wrap Mode. ↩
-
Unity documentation: Triplanar Node / Shader Graph 17.3. Three-direction projection, three texture samples, and Input Space. ↩
-
Unity documentation: Normal Vector Node / Shader Graph 17.3. ↩
-
Unity documentation: Renderer.localBounds / Unity 6.3. Bounds for shader deformation and persistence limitations. ↩
-
Unity documentation: Introduction to batching meshes. Static/Dynamic Batching and world space. ↩
-
Unity documentation: Asset metadata. Asset identifiers, import settings, and preserving
.metafiles. ↩ -
Unity documentation: Create asset packages and AssetDatabase.ExportPackage. ↩
-
Unity documentation: Import local asset packages. Importing through
Assets > Import Package > Custom Package. ↩ -
Unity documentation: MaterialPropertyBlock / Unity 6.3. The SRP Batcher compatibility warning. ↩
Top comments (0)