理解本篇必备技能: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的现象即可)就可以得到这样的效果了
最后调好的值:
三、优缺点
优点
实现简单,效率高,比stencil方式高,为什么这么说呢,看看一下这个图
(这是从unity官方文档上下下来的一张unity渲染流水线图 https://docs.unity3d.com/Manual/SL-Blend.html)
可见,stencil方式比ZTest方式至少多走了 FragmentShader (片着色器阶段),效率孰优孰劣自见分明了。
缺点
除了模型外拓带来的共有缺点外,就是ZFighting问题突出,差不多每个模型都需要单独调节一套offset参数才能避免ZFighting问题。而且多个模型叠加时,一样有概率出现描边断裂现象
导致这个问题就是offset的值调的太大了,外面模型的描边的ZTest失败了,不渲染了,解决方式就是适当的调小offset的参数值
描边是出来了,但是别的地方也出来了
有时候都找不到能同时解决这两个问题的值,也是烦。。