🖍️ 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:

  1. Render the object (that we want to outline) using a stencil mask
  2. Render the object again but extruded (made bigger), and draw it only where the stencil mask is not present
  3. 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.

Simply said, this renders the objects with a stencil mask.

Stencil pass.

💡 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.

Simply said, this renders the extruded version of the objects except where the stencil mask is present. Doing this produces the outline.

Outline pass.

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.

Single outline pass.

Published