Inverted Normal Studio

Sharp Stencil Shadows

Documentation for Sharp Stencil Shadows, a URP renderer feature for hard, pixel-exact stencil shadow volumes.

Shadow Casters

Home › Guides

What casts a shadow

A renderer casts a stencil shadow when it is:

  • an active, enabled Mesh Renderer or Skinned Mesh Renderer with a mesh
  • set to Cast Shadows On, Two Sided, or Shadows Only
  • on a layer in Caster Layers
  • in the level that casts, if it belongs to an LOD Group (see LOD groups)

No components are needed. To exclude an object, set Cast Shadows to Off or move it off the caster layers.

Caster discovery

The feature finds casters by scanning the loaded scenes:

  • every Discovery Refresh Interval seconds (default 1)
  • when a scene loads or unloads
  • when you call ShadowCasterDiscovery.RequestRefresh()

Disabling, destroying, or moving a caster off the caster layers takes effect immediately. New or re-enabled casters, and mesh swaps, are picked up at the next scan. Call RequestRefresh() after spawning objects if they must cast on the next frame:

Instantiate(prefab);
ShadowCasterDiscovery.RequestRefresh();

LOD groups

Shadow LOD (in General) chooses which level of an LOD Group casts:

  • Match Camera (default): each camera's shadows come from the level it draws, using the LOD Group's own transitions, LOD Bias, and Maximum LOD Level. Distant groups cast from their simpler levels, and a culled group casts nothing.
  • LOD0: LOD0 always casts, whichever level is on screen.

In both modes the level on screen gets self-shadow cancellation. During a cross-fade the shadow comes from one level, so it switches level without fading.

A disabled LOD Group draws and casts every level. Renderers under an LOD Group but in none of its levels cast as ordinary casters. LODGroup.ForceLOD isn't followed, and billboard levels cast nothing.

The periodic scan rereads a group's levels only when it is new or its number of levels changed. Call RequestRefresh() after changing an LOD Group's levels, size, or fade mode at runtime.

Mesh LOD

On Unity 6.2 and later, a mesh with Mesh LOD levels (for example from Generate Mesh LODs in the model importer) follows Shadow LOD the same way: Match Camera casts the level each camera draws, using the mesh's LOD selection curve, the Mesh LOD Threshold quality setting, and the renderer's LOD Selection Bias and LOD Override; LOD0 casts level 0.

  • Build preprocessing caches every level of each mesh in the build. A level missing from the cache casts level 0's shape, and the Console names the mesh.
  • The level on screen gets self-shadow cancellation, except on Skinned Mesh Renderers drawn above level 0, and on meshes drawn above level 0 that have a submesh which can't cancel (see Shadows on the casters themselves).
  • In Textured mode, alpha-clipped and transparent casters draw level 0 into the caster atlas.
  • Topology registered with ShadowVolumeTopologyRegistration applies to level 0 only.

Moving and scaled casters

Moving, rotating, non-uniformly scaled, mirrored (negative scale), and physics-driven casters are all supported. A caster's volume is regenerated only when its transform or its light changes.

Skinned mesh casters

Skinned Mesh Renderers cast their current animated pose with no setup.

CPU backend GPU backend
Source BakeMesh, once per frame The renderer's deformed vertex buffer
Requirement Float32 XYZ positions and normals, Float32 XYZW tangents Float32 XYZ positions in vertex stream 0
Unsupported layout Skipped, with a diagnostic Falls back to the CPU backend

On the GPU backend, Force Skinned Updates (default on) enables Update When Offscreen while the caster is on the GPU, so off-screen skinned casters still cast their current pose. Every renderer setting the GPU backend changes is restored when the caster leaves the GPU or the feature is disposed.

GPU-deformed mesh casters

A Mesh Renderer whose mesh is deformed by a compute shader (grass, cloth, water, and so on) casts its deformed shape on the GPU backend. It needs:

  • Raw vertex buffer access. The mesh's vertexBufferTarget must include GraphicsBuffer.Target.Raw, which compute writes need anyway.
  • 0 XYZ positions with 4-byte-aligned stride and offset, in any stream.
  • Positions written before the camera renders, for example in Update or LateUpdate. Later writes show one frame late.
  • Bounds covering the deformed shape. Set Mesh.bounds accordingly, since shadow culling and scissors use them.
mesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw;
using GraphicsBuffer vertices = mesh.GetVertexBuffer(0);
deformShader.SetBuffer(kernel, "_Vertices", vertices);
deformShader.Dispatch(kernel, groups, 1, 1);

Meshes rewritten from the CPU each frame, for example with Mesh.SetVertices, are read the same way when their vertex buffer has the Raw target. Seams that the deformation pulls apart are split on the GPU so the volume stays closed. Deformation done only in a material's vertex shader is not visible to shadow volumes.

Cost: like a skinned caster, a deformed caster regenerates its volume every frame, for each light.

Turning it off: clear GPU Deformed Meshes, or call SetGpuDeformedMeshes(false). Deformed meshes then cast their rest pose, as they always do on the CPU backend.