unity 描边之 ZTest 篇

理解本篇必备技能:unity深度测试

一、原理

本篇的原理简单粗暴。
1、老套路,先用一个pass膨胀模型,之后利用offset指令让其深度整体远离摄像机若干距离,片元着色器输出描边颜色。
2、再用一个pass进行正常渲染(纹理采样,高光、漫反等等),由于上一个pass的深度值比本pass的深度值大,而默认的ZTest的比较函数是LEqual,所以上一个pass只有膨胀出去的部分可以通过深度测试,即才会被渲染,被渲染部分就是我们看到的描边了。

二、案例

Shader "Unlit/ZTestOutline"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_OutlineColor("outline Color",Color) = (0,0,0,0)
		_OutlineWidth("outline Width",Range(0,0.05)) = 0.01
		_OffsetFactor("offset slope",Float) = 1
		_OffsetUnits("offset units",Float) = 1
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100
		pass
		{
			Offset [_OffsetFactor],[_OffsetUnits]
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			// make fog work
			#pragma multi_compile_fog
			
			#include "UnityCG.cginc"

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

			struct v2f
			{
				float4 vertex : SV_POSITION;
			};

			fixed4 _OutlineColor;
			float _OutlineWidth;

			v2f vert (appdata v)
			{
				v2f o;
				//在模型空间里直接膨胀模型,也可以在别的空间里做
				//需要注意法线的变换,还要注意在别的空间里膨胀的话,顶点变换到裁剪空间时不是MVP了
				// v.vertex.xyz += v.normal * _OutlineWidth;
				o.vertex = UnityObjectToClipPos(v.vertex);

				// 在裁剪空间里膨胀,可以解决描边近大远小的问题
				// 先把法线从模型空间转到相机空间
				// 这里直接使用unity给我们提供的宏,等价于 vNormal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal))
				// 为什么是(float3x3)UNITY_MATRIX_IT_MV,而不是 (float3x3)UNITY_MATRIX_MV,冯乐乐美女的4.7节是有这个解释的
				fixed3 vNormal = COMPUTE_VIEW_NORMAL;
				// 再把 vNormal 的xy转化到裁剪空间里去,z不要了
				fixed2 pNormal = TransformViewToProjection(vNormal.xy);
				// 开始膨胀模型
				o.vertex.xy += pNormal * _OutlineWidth;

				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				return _OutlineColor;
			}
			ENDCG
		}
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			// make fog work
			#pragma multi_compile_fog
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				UNITY_TRANSFER_FOG(o,o.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				// sample the texture
				fixed4 col = tex2D(_MainTex, i.uv);
				// apply fog
				UNITY_APPLY_FOG(i.fogCoord, col);
				return col;
			}
			ENDCG
		}
		
	}
}

本代码膨胀模型是放在裁剪空间里的,因为我发现如果在模型空间里膨胀的话,ZFighting特别严重,模型看起来blingbling的。估计是在模型空间里膨胀的话,因为修改了Z值,加剧了渲染共面的问题才会导致这样。如下图:

适当调节 offset factor 和 offset units 的值(offset units往正的调哦,因为这样才是加深深度值),如果调节好了(怎么算调节好了呢,就是在scene视图里,按住Alt + 鼠标左键变换视角,没有blingbling的现象即可)就可以得到这样的效果了

最后调好的值:
unity 描边之 ZTest 篇_第1张图片

三、优缺点

优点

实现简单,效率高,比stencil方式高,为什么这么说呢,看看一下这个图
unity 描边之 ZTest 篇_第2张图片
(这是从unity官方文档上下下来的一张unity渲染流水线图 https://docs.unity3d.com/Manual/SL-Blend.html)
可见,stencil方式比ZTest方式至少多走了 FragmentShader (片着色器阶段),效率孰优孰劣自见分明了。

缺点

除了模型外拓带来的共有缺点外,就是ZFighting问题突出,差不多每个模型都需要单独调节一套offset参数才能避免ZFighting问题。而且多个模型叠加时,一样有概率出现描边断裂现象
unity 描边之 ZTest 篇_第3张图片
导致这个问题就是offset的值调的太大了,外面模型的描边的ZTest失败了,不渲染了,解决方式就是适当的调小offset的参数值
unity 描边之 ZTest 篇_第4张图片

描边是出来了,但是别的地方也出来了
unity 描边之 ZTest 篇_第5张图片
有时候都找不到能同时解决这两个问题的值,也是烦。。

你可能感兴趣的:(unity之着色器,unity,描边)