本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。
https://blog.csdn.net/lzhq1982/article/details/82808525
上一篇我们学习了透明度测试的理论知识,这一篇我们看看实现。
通常,我们会在片元着色器中用clip函数来进行透明测试。
它的定义如下:
函数:void clip(float4 x); void clip(float3 x); void clip(float2 x); void clip(float x);
参数:裁剪时使用的标量或矢量条件。
描述:如果给定参数的任何一个分量是负数,就会舍弃当前像素的输出颜色。它等同于如下代码:
void clip(float4 x)
{
if (any x < 0)
discard;
}
下面我们要用一张透明纹理,实现透明度测试效果,先看透明纹理:
最终效果:
直接上代码:
Shader "CustomShader/Transparent/AlphaTest"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}
SubShader
{
Tags {"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
Pass
{
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
fixed _Cutoff;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD1;
float3 worldPos : TEXCOORD2;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
// if (texColor.a - _Cutoff < 0.0) {
// discard;
// }
clip(texColor.a - _Cutoff);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
return fixed4 (ambient + diffuse, 1.0);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"
}
上面的代码其实很简单,抛去纹理处理和光照的部分,重点我们看这几个地方:
在Properties里面,我们定义了一个裁剪系数_Cutoff:
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
这个裁剪系数我们可以在unity里调大小,来使图片不同的alpha部分变得透明。
Tags里,我们定义了三个标签,就像上一篇文章说的,我们要定义渲染顺序,
"Queue" = "AlphaTest"
它的顺序位于不透明物体和半透的值之间。
RenderType标签可以让Unity把这个Shader归入提前定义的组(TranparentCutout)中,以指明该Shader是一个使用了透明度测试的Shader。RanderType标签通常被用于着色器替换功能。IgnoreProjector设置为True,该Shader不会受投影器(Projectors)的影响。
其余大量代码都是处理纹理映射和光照的,我们不说,有关透明度测试的就下面一行:
clip(texColor.a - _Cutoff);
就像上面对clip的说明一样,参数小于0就会被舍弃,所以这句话的意思是当该片元的alpha值小于_Cutoff时,就会直接舍弃,不会被渲染,则该处就是透明的,反之就会被渲染,不透明。注意被注掉的代码,替换clip函数效果是一样的。
if (texColor.a - _Cutoff < 0.0) {
discard;
}
我们控制_Cutoff的值,可以得到不同的效果:
说到最后要提个比较重要的问题,透明度测试的效率是比较低的。
不要以为被裁掉不渲染了效率就高了,在该书中渲染流水线那章中有提过:
如果按照正常的流水线,深度测试是在片元着色器后面的,那么对于不透明物体,GPU在片元着色器部分花了很大力气终于计算出片元的颜色后,在深度测试中却没过,被舍弃了,那之前的工作都白费了,所以对于大多数GPU,它们会尽可能在片元着色器之前就先进行测试了,这种技术被称为Early-Z技术。这样就可以提前舍弃掉没有通过测试的片元而不进入片元着色器做复杂的运算。但如果我们用了透明度测试,要调用clip在片元着色器中手动舍弃片元,就导致Gpu无法提前执行各种测试。这样就还是要把所有片元都处理一遍再进入各种测试,效率就下降了。同时我觉得clip执行了if条件判断,这也是比较耗性能的,那么它是否比透明度混合更费呢,如果是,那透明度混合同样能达到透明度测试的效果,这个有待进一步测试。