Sprite Outline

Sprite Outline

In this "Unity Sprite Outline" tutorial we'll discover how to create both a "Sprite Innerline" and a "Outer Sprite Outline". The shader works by sampling the 4 neighboring pixels (left, right, up, bottom) around each texel - 1 texel distance, controlled via _MainTex_TexelSize. The Inner Sprite Outline is drawn "inside" the sprite, while "Outer" is the opposite. Both variants share the same 4-sample logic and differ by just 1 line of code.

Read more about "Texture's Texel Size" here.

0:00
/0:01

HLSL

Both variants follow the same steps:

  1. Sample the original pixel color (col) and its alpha.
  2. Sample the 4 neighboring pixels (left, right, up, bottom) using _MainTex_TexelSize.
  3. Calculate the outline value from those 4 samples.
  4. Return the blended color using lerp(col, _OutlineColor, outline).

Inner Sprite Outline HLSL

Shader "Unlit/InnerSpriteOutline HLSL"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_OutlineColor("Outline Color", Color) = (1,1,1,1)
    }
    SubShader
    {
		Tags
		{
			"RenderType" = "Transparent"
		}
 
		Blend SrcAlpha OneMinusSrcAlpha
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
 
            #include "UnityCG.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
 
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };
 
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;
 
			fixed4 _OutlineColor;
 
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
 
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
 
				fixed leftPixel = tex2D(_MainTex, i.uv + float2(-_MainTex_TexelSize.x, 0)).a;
				fixed upPixel = tex2D(_MainTex, i.uv + float2(0, _MainTex_TexelSize.y)).a;
				fixed rightPixel = tex2D(_MainTex, i.uv + float2(_MainTex_TexelSize.x, 0)).a;
				fixed bottomPixel = tex2D(_MainTex, i.uv + float2(0, -_MainTex_TexelSize.y)).a;
 
				fixed outline = (1 - leftPixel * upPixel * rightPixel * bottomPixel) * col.a;
 
                return lerp(col, _OutlineColor, outline);
            }
            ENDCG
        }
    }
}

Outer Sprite Outline HLSL

To achieve an "Outline" instead of an "Innerline", just change the "outline" line (in the previous code snippet) into this:

fixed outline = max(max(leftPixel, upPixel), max(rightPixel, bottomPixel)) - col.a;

Amplify Shader

Inner Sprite Outline ASE

Outer Sprite Outline ASE

Frequently asked questions

What is the difference between inner and outer sprite outline?

The inner outline is drawn inside the sprite boundary (replacing border pixels), while the outer outline appears outside it by sampling pixels that are transparent but near the sprite edge.

Does this shader work with Unity's sprite atlas?

Yes, as long as you make sure the sprite has enough padding in the atlas so that neighboring sprites don't bleed into the outline samples.

Can I control the outline thickness?

This shader uses a fixed 1-texel width. To increase thickness you'd need to sample more neighbors or scale _MainTex_TexelSize by a multiplier property.

Does this work in the Universal Render Pipeline (URP)?

The HLSL variant targets the built-in pipeline. For URP you would need to convert the shader to an HLSL pass inside a URP ShaderLab structure or use Shader Graph instead.

Comments

Want game-ready tools or need specific solutions?

Work with us