This shader uses a pass to create a slightly bigger mesh behind the original one.
This is a good solution (at least in Unity), but only for convex/non transparent object. The fragments of the outline will indeed appear behind the mesh:
We can remove the fragments behind the mesh modifying the depth buffer with a duplicated object.
The original object writes to the z-buffer, so the duplicated object (i.e. the one that act as an outline) will be partially culled by the original one.
In order to obtain this, we can use these shaders:
Shader"Outline/Outline"{Properties{_OutlineColor("Outline Color",Color)=(0,0,0,1)_Outline("Outline width",Range(.002,0.03))=.005}CGINCLUDE#include "UnityCG.cginc"structappdata{float4vertex:POSITION;float3normal:NORMAL;};structv2f{float4pos:POSITION;float4color:COLOR;};uniformfloat_Outline;uniformfloat4_OutlineColor;v2fvert(appdatav){// just make a copy of incoming vertex data but scaled according to normal directionv2fo;o.pos=mul(UNITY_MATRIX_MVP,v.vertex);float3norm=mul((float3x3)UNITY_MATRIX_IT_MV,v.normal);float2offset=TransformViewToProjection(norm.xy);o.pos.xy+=offset*o.pos.z*_Outline;o.color=_OutlineColor;returno;}ENDCGSubShader{Tags{"Queue"="Overlay"}Pass{Name"OUTLINE"Tags{"LightMode"="Always"}CullFrontZWriteOnZTestLessBlendSrcAlphaOneMinusSrcAlphaColorMaskRGBOffset15,15CGPROGRAM#pragma vertex vert#pragma fragment fraghalf4frag(v2fi):COLOR{returni.color;}ENDCG}}SubShader{Tags{"Queue"="Overlay"}CGPROGRAM#pragma surface surf Lambertsampler2D_MainTex;fixed4_Color;structInput{float2uv_MainTex;};voidsurf(InputIN,inoutSurfaceOutputo){fixed4c=tex2D(_MainTex,IN.uv_MainTex)*_Color;o.Albedo=c.rgb;o.Alpha=c.a;}ENDCGPass{Name"OUTLINE"Tags{"LightMode"="Always"}CullFrontZWriteOnColorMaskRGBBlendSrcAlphaOneMinusSrcAlphaCGPROGRAM#pragma vertex vert#pragma exclude_renderers gles xbox360 ps3ENDCGSetTexture[_MainTex]{combineprimary}}}Fallback"Diffuse"}
The result is pretty good:
Finally, here it is a Unity script that automatically creates the outline effect when applied to an object:
usingUnityEngine;usingSystem.Collections;publicclassOutliner:MonoBehaviour{publicColormeshColor=newColor(1f,1f,1f,0.5f);publicColoroutlineColor=newColor(1f,1f,0f,1f);// Use this for initializationpublicvoidStart(){// Set the transparent material to this objectMeshRenderermeshRenderer=GetComponent<meshrenderer>();Material[]materials=meshRenderer.materials;intmaterialsNum=materials.Length;for(inti=0;i<materialsNum;i++){materials[i].shader=Shader.Find("Outline/Transparent");materials[i].SetColor("_color",meshColor);}// Create copy of this object, this will have the shader that makes the real outlineGameObjectoutlineObj=newGameObject();outlineObj.transform.position=transform.position;outlineObj.transform.rotation=transform.rotation;outlineObj.AddComponent<meshfilter>();outlineObj.AddComponent<meshrenderer>();Meshmesh;mesh=(Mesh)Instantiate(GetComponent<meshfilter>().mesh);outlineObj.GetComponent<meshfilter>().mesh=mesh;outlineObj.transform.parent=this.transform;materials=newMaterial[materialsNum];for(inti=0;i<materialsNum;i++){materials[i]=newMaterial(Shader.Find("Outline/Outline"));materials[i].SetColor("_OutlineColor",outlineColor);}outlineObj.GetComponent<meshrenderer>().materials=materials;}}