π³ CosterGraphics.Systems.OutlineSystem

A full-screen, URP-based outline effect for 3D GameObjects using a ScriptableRendererFeature, ScriptableRenderPasses, and the Outline3D component.
This system allows you to draw sharp, controllable outlines around specific objects without requiring custom shaders on the objects themselves and without using 'inverted hull' mesh copies. Outline texture masks are generated in dedicated mask passes and then calculated and composited in screen-space using configurable materials and full-screen Shader Graph shaders.
π Overview
The OutlineSystem consists of three core components:
Outline3D : MonoBehaviour
A MonoBehaviour added to any GameObject you want to outline.
It marks the object for inclusion into the outline mask pass.Outline3DRendererFeature : ScriptableRendererFeature
A custom URP Renderer Feature that:- collects all objects with the
Outline3Dcomponent - renders them to an off-screen mask
- applies a full-screen composite pass to draw the outline
- collects all objects with the
Outline Materials / Shader Graph Shaders
A ShaderGraph (e.g.,SG_OutlineComposite) and material used by the renderer feature to generate the final outline.
Common parameters include:- Outline thickness
- Outline color
- Blur/soften
- Edge detection or dilation strength
π§© Outline System Components
Outline3D (MonoBehaviour)
Marks a GameObject as outline-eligible.
Responsibilities:
- Registers itself with the renderer feature
- Provides access to its Renderers
- Stores per-object outline settings (color, thickness, etc.)
Outline3DURPRendererFeature (ScriptableRendererFeature)
Handles all inspection, rendering and compositing work.
Typical passes:
1. Mask Pass
- Draws all
Outline3Dobjects into a dedicated mask texture - Produces a binary white silhouette
- Optional: also capture depth or normals for improved accuracy
2. Composite Pass
- Full-screen pass
- Samples mask
- Dilates / colors / blends
- Writes result onto camera color buffer
π¨ ShaderGraph β Composite Shader Tips
For best results:
- Use a dilation kernel (e.g., sampling 8 neighbors)
- Multiply dilation by
OutlineThickness - Multiply output color by
OutlineColor - Add result to camera color input
For smoother outlines:
- Add a tiny blur after dilation
- Or apply inner + outer double-pass dilation
π Example Usage
public class Enemy : MonoBehaviour
{
private Outline3D outline;
void Start()
{
outline = gameObject.AddComponent<Outline3D>();
outline.Color = Color.red;
outline.Thickness = 2f;
}
}
π Additional Notes
- Outlines are purely a full-screen post-process, so they wonβt modify materials or meshes.
- The system supports any number of outlined objects.
- Performance is excellent because the mask renders only simple unlit silhouettes.
- You can extend this system with multiple outline layers, glow effects, or animated outline thickness.
π§© Depth Bias in the Mask Pass
When generating the mask texture for the outline system, the mask geometry is rendered in a dedicated pass. This pass typically uses LessEqual depth testing so the mask correctly follows the exact visible surfaces of the scene geometry.
However, rendering with LessEqual introduces a subtle but important problem:
β Floating-Point Precision (ULP) Issues
Depth buffers store values using quantized floating-point precision. Two fragments that should have the same depth often differ by a few ULPs (Units in the Last Place).
This tiny difference means:
- Pixels that should pass
LessEqualsometimes fail the test - Mask edges show holes, flickering, or thin broken borders
- Outlines become unstable or contain noise
Using Less is not an option because the mask geometry needs to render when depths are exactly equal, not only when strictly closer.
π― The Solution: Apply a Small Depth Bias
During the Mask Pass, the renderer applies a tiny depth bias, which nudges the mask geometry slightly toward the camera. This guarantees that its depth values are always marginally smaller than the underlying scene depth, eliminating ULP inconsistencies.
The effect:
- β The mask always passes depth testing
- β Edges become stable and clean
- β No gaps or flickering
- β No dependency on floating-point precision behavior
βοΈ How the Bias Is Applied
URP allows setting a temporary global depth bias:
cmd.SetGlobalDepthBias(slopeBias, depthBias);
A typical configuration:
slopeBias = 0depthBias = -1f(very small offset toward the camera)
After the mask pass completes, the bias is reset:
cmd.SetGlobalDepthBias(0, 0);
π Why This Is Safe
- The bias is scoped only to the mask pass.
- Resetting immediately ensures no effect on:
- Unityβs built-in rendering
- Other renderer features
- Shadows, lighting, or post-processing
- The outline system gains stable, predictable mask rendering with no side effects.
π§ Summary
Using a tiny depth bias is a deliberate and necessary fix to overcome floating-point limitations in depth testing. It ensures the mask is rendered reliably and produces clean, artifact-free outlines without altering the main rendering pipeline.
Basic, Advanced & Pro Feature Comparison
Outline System Basic Features:
- Outline3D MonoBehaviour Component (
Outline3D) - Color Mask Based Silhouette Outlines
- Depth & Normals Based Contour outlines
- Hybrid Silhouette & Contour Outlines
Outline System Advanced Features:
- Advanced Outline3D MonoBehavour Component (
Outline3DAdv) - Advanced Outline3D Renderer Features (
Outline3DAdvRendererFeature) - Renderer Feature IDs (Render different objects with different types of outlines easily)
- ObjectID Based Outlines
- Advanced Edge Detection Kernel Based Outlines (Cross, Gaussian, Laplacian, Sobel, Scharr)
- Comes with Shader Graph Sub-Graphs for each of the kernels to create your own Composite shader with
- Advanced depth masked outline shaders using depth tested and non depth tested masks
Outline System Pro Features:
- OutlineFeature Profile Scriptable Objects System (Replaces Feature IDs?)
- Ray Marched Distance Field Based Outlines
- Other pro stuff