实现原理
1.先用一个Pass将原来的物体沿着该物体的顶点法线外移,使它变得比之前大
2.使用Outline的颜色填充整个物体,并且剔除正面的渲染,得到一个放大的背面
3.用第二个Pass正常渲染渲染,第二次正常渲染会挡住第一个渲染的背面,但是第一个Pass变大导致部分挡不住,则挡不住的那部分就变成了该物体的外描边
代码图如:
//法线外扩实现法 Shader "Custom/Outline" { Properties { _MainTex("MainTex", 2D) = "white" {} _Outline("Outline",float) = 0.1 _OutlineColor("OutlineColor",Color) = (0,0,0,1) } SubShader { Tags { "RenderType" = "Transparent" } //Transparent可以在opaque之后画,否则会被天空盒遮住 LOD 100 Pass { Name "Outline_Fst"//第一个Pass,法线外扩+正面剔除 Cull Front ZWrite Off CGPROGRAM #pragma vertex vert #pragma fragment frag struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 vertex : SV_POSITION; }; float _Outline; float4 _OutlineColor; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); //把法线转换到视图(相机)空间,UNITY_MATRIX_IT_MV为模型视图矩阵的逆转置矩阵 float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal); //把法线转换到投影(剪裁)空间 float2 clip_normal_xy = mul((float2x2)UNITY_MATRIX_P,vnormal.xy); //向法线方向延伸 o.vertex.xy = o.vertex.xy + clip_normal_xy * _Outline; return o; } fixed4 frag(v2f i) : SV_Target { return _OutlineColor; } ENDCG } Pass { Name "Outline_Sec" //第二个Pass,正常渲染 CGPROGRAM #pragma vertex vert #pragma fragment frag struct appdata { float4 vertex : POSITION; float2 uv :TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; float2 uv:TEXCOORD0; }; sampler2D _MainTex; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag(v2f i) : SV_Target { return tex2D(_MainTex,i.uv); } ENDCG } } }
核心点:
1.渲染类型改为Transparent目的是在opaque之后画,否则会被天空盒(opaque)遮住
2.法线从模型空间转到视图(相机)空间,使用的是UNITY_MATRIX_IT_MV而不是UNITY_MATRIX_I_MV,原因是非等比缩放情况下,使用和顶点变换一样的UNITY_MATRIX_I_MV会导致法线异常,我在非等比缩放模型法线转换一文中有写过详细推导,此处只给结论:法线的变换矩阵为模型变换矩阵的逆转置矩阵
效果图:
优点:不需要写深度缓冲
缺点:棱角分明的物体(顶点非连续)的物体会穿帮
效果图:
总结,项目中的模型一般来说不会出现棱角分明的情况,所以此方法是项目中最常使用的方法,真正的问题在于此方法是硬描边,无法达到类似光(带模糊效果)的描边效果
文章评论