Edge Outline Full-Screen Shader for Unity URP
A URP full-screen outline shader for Unity 6 that draws lines on every silhouette and crease from the depth and normals textures, with an ink-on-paper mode. No per-object setup.
Also called: post-process outline, screen-space outline, edge detection, sketch shader, comic outline.
Full-screen effects URP, Unity 6000.0+ intermediate MIT licensed
Use it for: Comic, cel-shaded, architectural or blueprint looks where every object should be outlined without giving each one an outline material.
The preview runs a WebGL copy of the effect over a small test scene. In Unity it applies to everything your camera renders.
What it does
- Outlines silhouettes from depth differences and creases from normal differences
- Depth sensitivity relative to distance, so far objects are not drowned in lines
- Thickness in screen pixels
- Fade out with distance
- Outline Only mode: replaces the image with a paper colour for an ink-sketch look
- Works with any material that writes depth and normals, which includes URP's Lit and Unlit shaders and the opaque shaders on this site
How it works
For every pixel the shader samples the depth and normals textures at four diagonal neighbours (a Roberts cross). A large jump in depth, measured relative to the pixel's own distance, marks a silhouette; a large change in normal direction marks a crease or corner. Either one paints the outline colour over the image.
The Depth and Normal requirements on the renderer feature make URP render the depth and normals textures this shader reads; the Color requirement provides the image itself in _BlitTexture.
Setup
- Save the file as EdgeOutline.shader under Assets and create a material that uses Scripts For Unity > Full Screen > Edge Outline.
- On your URP Renderer asset, click Add Renderer Feature > Full Screen Pass Renderer Feature and assign the material to Pass Material.
- Set Injection Point to Before Rendering Post Processing, so Bloom and tonemapping apply to the lines too.
- Set Requirements to Depth, Normal and Color (or Everything).
- Lower Depth Threshold and Normal Threshold for more lines, raise them for fewer.
Common mistakes
- No lines, or lines only on some objects: objects drawn with a shader that has no DepthNormals pass are missing from the normals texture. Most custom shaders from older tutorials lack one.
- Lines across flat floors: the Depth Threshold is too low for steep viewing angles. Raise it a little, or rely on normals for creases.
- Transparent objects have no outline: they do not write depth or normals, so the effect cannot see them. This is a limit of screen-space outlines.
- Wrong edges with the Deferred renderer: the shader is written for the Forward and Forward+ renderers.
Questions
- Why do some objects have no outline?
- The effect reads URP's depth and normals textures. Objects drawn with a shader that has no DepthNormals pass, or that are transparent, are missing from them.
- Can each object have its own outline colour?
- No. A screen-space outline uses one colour for the whole image. For a per-object colour or a highlight on one object, use the Outline Highlight shader.
- How expensive is it?
- It is one full-screen pass with ten texture samples per pixel, plus the depth and normals prepass URP renders when the feature asks for them. That prepass draws your opaque geometry once more, which costs more in scenes with many objects.
Source
EdgeOutline.shader · 89 lines · ShaderLab and HLSL for the Universal Render Pipeline 17 (Unity 6)
// Edge Outline: draws lines where depth or surface direction changes, for
// a comic, technical or "ink on paper" look. Applied with URP's Full Screen
// Pass Renderer Feature with Depth, Normal and Color requirements.
// URP 17 (Unity 6). Scripts For Unity, MIT licensed.
Shader "Scripts For Unity/Full Screen/Edge Outline"
{
Properties
{
_OutlineColor ("Outline Color", Color) = (0.05, 0.05, 0.07, 1)
_Thickness ("Thickness (pixels)", Range(0.5, 4)) = 1
_DepthThreshold ("Depth Threshold", Range(0.005, 0.5)) = 0.08
_NormalThreshold ("Normal Threshold", Range(0.05, 2)) = 0.4
_FadeDistance ("Fade Out Distance", Range(1, 500)) = 60
[Toggle(_OUTLINE_ONLY)] _OutlineOnly ("Outline Only (Paper)", Float) = 0
_PaperColor ("Paper Color", Color) = (0.96, 0.94, 0.88, 1)
}
SubShader
{
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
ZWrite Off
ZTest Always
Cull Off
Pass
{
Name "Edge Outline"
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
#pragma shader_feature_local_fragment _OUTLINE_ONLY
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl"
CBUFFER_START(UnityPerMaterial)
half4 _OutlineColor;
float _Thickness;
float _DepthThreshold;
float _NormalThreshold;
float _FadeDistance;
float _OutlineOnly;
half4 _PaperColor;
CBUFFER_END
float SfuEyeDepth(float2 uv)
{
return LinearEyeDepth(SampleSceneDepth(uv), _ZBufferParams);
}
half4 Frag(Varyings input) : SV_Target
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
float2 uv = input.texcoord;
float2 texel = _Thickness / _ScreenParams.xy;
// Roberts cross: compare the two diagonals around this pixel
float2 a = uv + texel;
float2 b = uv - texel;
float2 c = uv + float2(texel.x, -texel.y);
float2 d = uv + float2(-texel.x, texel.y);
// Depth edges, relative to distance so far objects are not over-outlined
float centerDepth = SfuEyeDepth(uv);
float depthAB = SfuEyeDepth(a) - SfuEyeDepth(b);
float depthCD = SfuEyeDepth(c) - SfuEyeDepth(d);
float depthEdge = step(_DepthThreshold, sqrt(depthAB * depthAB + depthCD * depthCD) / max(centerDepth, 0.0001));
// Normal edges: creases and corners where depth barely changes
float3 normalAB = SampleSceneNormals(a) - SampleSceneNormals(b);
float3 normalCD = SampleSceneNormals(c) - SampleSceneNormals(d);
float normalEdge = step(_NormalThreshold, sqrt(dot(normalAB, normalAB) + dot(normalCD, normalCD)));
float edge = max(depthEdge, normalEdge) * (1.0 - saturate(centerDepth / _FadeDistance));
half4 color = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv);
#if defined(_OUTLINE_ONLY)
color.rgb = _PaperColor.rgb;
#endif
color.rgb = lerp(color.rgb, _OutlineColor.rgb, edge * _OutlineColor.a);
return color;
}
ENDHLSL
}
}
}
More shaders
- Monochrome Full-Screen ShaderA URP full-screen monochrome shader for Unity 6: duotone grey or sepia, an optional 1-bit threshold, and a keep-one-colour mode. Applied with the Full Screen Pass Renderer Feature.
- Pixelate Full-Screen ShaderA URP full-screen pixel art shader for Unity 6: large pixels, a limited number of colour steps and ordered dithering for a retro look, as one Full Screen Pass Renderer Feature material.
- Toon Lit Shader with OutlineA URP toon shader for Unity 6: two-band cel shading that receives shadows, a hard specular highlight, a rim light and an inverted-hull outline, in one .shader file.
- Dissolve ShaderA URP dissolve shader for Unity 6 that burns a mesh away along procedural noise with a glowing HDR edge. No noise texture needed, and its shadow dissolves with it.