Trying to make a silhouette outline shader

I’m trying to make an outline shader that only shows outline from the model’s silhouette, to be used as indication that the character has been selected.

The current problem with this one is the outline doesn’t show in the feet, because of the offset, it gets hidden by the ground. The offset is there to get rid of outline lines that are not in the silhouette (i.e. protrusions facing the camera)

I want the outline to be above the terrain, but below the character. The queue tag doesn’t seem to work.

Shader "Outlined/Diffuse" {
	Properties {
		_Color ("Main Color", Color) = (.5,.5,.5,1)
		_OutlineColor ("Outline Color", Color) = (0,0,0,1)
		_Outline ("Outline width", Range (.002, 0.03)) = .005
		_MainTex ("Base (RGB)", 2D) = "white" { }
	}
	
CGINCLUDE
#include "UnityCG.cginc"

struct appdata {
	float4 vertex : POSITION;
	float3 normal : NORMAL;
};

struct v2f {
	float4 pos : POSITION;
	float4 color : COLOR;
};

uniform float _Outline;
uniform float4 _OutlineColor;

v2f vert(appdata v) {
	// just make a copy of incoming vertex data but scaled according to normal direction
	v2f o;
	o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

	float3 norm   = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
	float2 offset = TransformViewToProjection(norm.xy);

	o.pos.xy += offset * o.pos.z * _Outline;
	o.color = _OutlineColor;
	return o;
}
ENDCG

	SubShader {
		Tags {"Queue" = "Overlay" }
CGPROGRAM
#pragma surface surf Lambert

sampler2D _MainTex;
fixed4 _Color;

struct Input {
	float2 uv_MainTex;
};

void surf (Input IN, inout SurfaceOutput o) {
	fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
	o.Albedo = c.rgb;
	o.Alpha = c.a;
}
ENDCG

		// note that a vertex shader is specified here but its using the one above
		Pass {
			Name "OUTLINE"
			Tags { "LightMode" = "Always" }
			Cull Front
			ZWrite On
			ZTest LEqual
			ColorMask RGB
			Blend SrcAlpha OneMinusSrcAlpha
			Offset 15,15

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			half4 frag(v2f i) :COLOR { return i.color; }
			ENDCG
		}
	}
	
	SubShader {
		Tags {"Queue" = "Overlay" }
CGPROGRAM
#pragma surface surf Lambert

sampler2D _MainTex;
fixed4 _Color;

struct Input {
	float2 uv_MainTex;
};

void surf (Input IN, inout SurfaceOutput o) {
	fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
	o.Albedo = c.rgb;
	o.Alpha = c.a;
}
ENDCG

		Pass {
			Name "OUTLINE"
			Tags { "LightMode" = "Always" }
			Cull Front
			ZWrite On
			ColorMask RGB
			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM
			#pragma vertex vert
			#pragma exclude_renderers gles xbox360 ps3
			ENDCG
			SetTexture [_MainTex] { combine primary }
		}
	}
	
	Fallback "Diffuse"
}

A good friend in IRC said the problem is that the outline is reading the depth buffer, and that’s what causes it to clip against other geometry.

Somehow I want the outline to stop considering other geometry other than the model its supposed to outline.

Use “ZTest Always” and “ZWrite Off” to render the outline in the Transparent queue and then render the character on top of it in the Overlay queue. (With or without “ZTest Always” depending on what you prefer.)

Also another problem with my current set up is you’ll have to tweak the depth offset values depending on the size of your object. You’ll want it to be below the model its outlining, and above objects that the model is positionally behind; it has to be in-between.

P.S. positionally is not a word

Here it is with zwrite off and ztest always:

Here it is with zwrite off only:

I’m not sure if you can specify render queue per pass. I added it in but I didn’t see any difference.

Here is what I want to happen, done using a two camera trick:

Unfortunately, that method does this:

EDIT: I realize the picture doesn’t show it, but the character is supposed to be behind that slope. What happened was the character effectively shows on top always, no matter what is supposed to obstruct your view of it.

I’m fine if the outline has to show “always on top no matter what”[1] but certainly the character itself shouldn’t be “always on top”.


1: Though I would prefer the outline to be “always on top of only things that are farther than the character, but also be on top of the portion of the ground he’s standing in”.

On the other hand, showing outline always on top no matter what can also be useful to show a selected character when he’s behind walls, or when he’s crawling under ducts for example. So I’d like to explore both options.

Use the Offset command in shaders to push it in front of the geometry. It will only need to offset by a small ish amount to get his feet over. Do the same for the above model too. That could work. It will cause problems clipping with small rocks etc though.

Please read the thread entirely first.

Here is what it looks like with depth offset of positive values, which is almost what I want, except the feet don’t show outline (this is also the first pic I posted):

Here it is with depth offset with negative values, notice it has lines on the belt, nose, and other areas which I do not want:

Here is what it looks like without any offset:

An interesting experiment, I was able to make the shader show the outline only, but still suffers from the problem that the feet doesn’t show outline.

well if speed is not an issue you can solve it with render to texture.

The problem is I don’t have Pro.

Oh boy, I feel like an idiot, I forgot you can reorder passes. Here’s how it looks like now:

Wiki article: http://www.unifycommunity.com/wiki/index.php?title=Silhouette-Outlined_Diffuse

Thanks for your patience, everyone.

Anyone care to make a variant that can make use of normal maps?

Hi there,

Nice shader thanks for sharing!

I wanted to ask you if it is possible to show the outline of the character when he is behind objects instead of rendering a solid color for the whole obstructed part of the character.

Thanks!

+1

Also, if it could be possible to make it such that it doesnt render the outline over renderers with this shader, for example, if a tank has hull, turret, gunbarrel, then it will render outlines (and worst, solid filled color) for the back of the gunbarrel that is inside the turret and for the base of the turret that is slighty inside the tank.

hej, this shader is really nice, just what we need.

when we’re trying to use it on an Android device (or with OpenGL ES2 emulation enabled in the editor), it doesn’t render correctly though. we’re using the Silhouette Only variant:

disabled ES2 emulation:

enabled ES2 emulation:

My knowledge of shader things is limited, could this have something to do with the precision of depth testing, or some feature used by this shader that’s not available in ES? Maybe someone can hint towards a way to fix this. thanks!

This is really cool! How did you make the shader just render the outlines?

Never saw that before. One guess is it could be z-fighting or whatever they call it. The shader has an Offset -8, -8 that’s commented out. Try enabling it and play around with the values.

Check the wiki link I gave (http://wiki.unity3d.com/index.php?title=Silhouette-Outlined_Diffuse) it’s there.

Awesome, Thanks so much!

Hi guys, great work.

I am currently exploring ways to create a Silhouette outline of a gameObject that is not a Post Effect, and have successfully got it working in the unity editor, but we are getting z-sorting artifacts on the mobile device.

I am approaching this problem by duplicating the gameObjects in question, extruding the faces out using the vertex normals, then applying a simple colour shader to this expanded geometry.
For the mask we are using the original non-extruded gameObjects and applying our mask shader to mask the extruded gameObject yielding an outline effect.

The problem is that when viewed from certain camera angles, the outline game objects faces inside the mask object are not being masked.

Initially we solved this problem by using Offset -1, -1000 which works in the Unity editor, how ever, Offset fails on iOS.

to illustrate:
Wireframe of object setup, inner object is being used as mask object, outer object is our extruded outline object

Screenshot of results inside unity editor:

Screen shot of results on iOS:

Here are our shaders:

Shader "Mask"  {
   
    SubShader {
        Tags {"Queue" = "Geometry-10" }       
        
        Pass {
       		Lighting Off
	       Offset -1000, 0
	        ColorMask 0
       		ZWrite On
        }
    }
}
Shader "Back Colour Only"{
Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
    }
    SubShader {
        Tags {"Queue" = "Geometry" }
       	
            Pass {
			Cull Front
			Color [_Color]
			ZWrite On
			Offset 1000, 0
			Lighting Off
	    }
        
    } 
}

Have also tried the above solutions, which has the same issues I’m getting here.
My shader knowledge is rather limited, and if someone has any insight into what I’m doing wrong, would be greatly appreciated.

Cheers all

Hi guys

solved my problem another way.

Cheers all.