🖍️ The easiest way to render an outline in Unity
A simple method of rendering outlines in Unity.
5 minute read
Jump to heading Introduction
When using the Unity game engine, most developers will at some point want to render an outline. I wanted to share the easiest way to render an outline (that I know of) that produces decent results.
This method I will share requires no knowledge about custom render passes. There is no custom C# code, just existing Unity features and a single outline shader.
🖍️ Looking for an advanced outline rendering toolkit for your game? Check out Linework which I have lovingly developed!
Jump to heading How does it work?
The outline will work like this:
- Render the object (that we want to outline) using a stencil mask
- Render the object again but extruded (made bigger), and draw it only where the stencil mask is not present
- No step 3, you are done!
You can do this in Unity right now, without writing any code other than the simple outline shader.
Jump to heading Step by step
To render our outline we will use 2 Render Objects passes which are pre-made render passes made by Unity which you can simply add to your Renderer Features list on your Render Pipeline Asset.
Jump to heading 1. Outline Shader + Material
First, we will create an outline shader + material. Use the following shader and make an outline material from it.
This shader simply extrudes the vertices of a mesh in the vertex stage of the shader. How far the vertices are extruded controls the width of the outline.
The fragment stage returns a single flat color (the outline color).
Shader "Unlit/Fast Outline"
{
Properties
{
_OutlineColor ("Outline Color", Color) = (0,1,0,1)
_OutlineWidth ("Outline Width", Range (0, 10)) = 0.5
}
SubShader
{
Tags
{
"RenderType" = "Opaque"
"RenderPipeline" = "UniversalPipeline"
}
ZWrite Off
Cull Off
ZTest LEqual
Blend SrcAlpha OneMinusSrcAlpha
HLSLINCLUDE
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
half4 _OutlineColor;
half _OutlineWidth;
CBUFFER_END
struct Attributes
{
float4 positionOS : POSITION;
half3 normalOS : NORMAL;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
UNITY_VERTEX_OUTPUT_STEREO
};
half4 frag(Varyings IN) : SV_Target
{
return _OutlineColor;
}
ENDHLSL
Pass
{
Name "NORMAL VECTOR (CLIP SPACE)"
HLSLPROGRAM
#pragma vertex vert
Varyings vert(Attributes IN)
{
Varyings OUT;
UNITY_SETUP_INSTANCE_ID(IN);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
half width = _OutlineWidth;
OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
float3 normalHCS = mul((float3x3)UNITY_MATRIX_VP, mul((float3x3)UNITY_MATRIX_M, IN.normalOS));
OUT.positionHCS.xy += normalize(normalHCS.xy) / _ScreenParams.xy * OUT.positionHCS.w * width * 2;
return OUT;
}
ENDHLSL
}
}
}
If you would like more information on how this outline shader works exactly, you can read 5 ways to draw an outline.
Jump to heading 2. Stencil Pass
We will use a first pass to render the object with a stencil mask. This pass has the following settings.
- The Layer Mask set to Everything
- The Stencil Options set to Ref 1, Comp Always, Pass Replace
Simply said, this renders the objects with a stencil mask.
💡 I am using the Universal Render Pipeline. You'll need to use this for the Render Objects pass to be available.
Jump to heading 3. Outline Pass
We will use a second pass to render the outline. This pass has the following settings.
- The Layer Mask set to Everything (matching with the first pass)
- The Override Material set to our outline material we created in step 1
- The Depth Test set to Less Equal
- The Stencil Options set to Ref 1, Comp Not Equal, Pass Keep
Simply said, this renders the extruded version of the objects except where the stencil mask is present. Doing this produces the outline.
Jump to heading What now
You have an outline! Congratulations!
Some considerations:
Jump to heading Layer Masks
You can use the Layer Mask option to configure which objects receive an outline. You can at runtime change the layer of an object to add/remove outlines.
Jump to heading Other Outline Techniques
This outline is rendered using the vertex extrusion technique. There are many other possible techniques, each with their own drawbacks and advantages. Read 5 ways to draw an outline for an overview.
Jump to heading Custom Renderer Feature
You could make a custom Renderer Feature that is specifically made for rendering outlines like this. This would work similarly to the Render Objects pass (made by Unity), but you will only need to add a single pass in this case. See this documentation on how to start with this.
Jump to heading Rendering Layers
The Render Objects pass has a Layer Mask option. This can be used to only apply outlines selectively to specific objects. However, Unity also has Rendering Layers which are better suited for this. If you make a custom pass, you can add support for these.
Jump to heading Single Pass
Instead of using a stencil mask, you could remove the first pass and change your shader from Cull Off
to Cull Front
. This will cull the front-facing triangles of your extruded mesh and might be acceptable for your use case.