@学习
咱们来看一下如何在Unity中实现透明度测试的效果。在上面咱们已经知道了透明度测试的原理。
透明度测试:只要一个片元的透明度不知足条件(一般是小于某个阈值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响;不然就会按照普通的不透明物体的处理方式来处理它。
一般,咱们会在片元着色器中使用clip函数来进行透明度测试。clip是Cg中的一个函数,它的定义以下:
函数:void clip(float4 x);void clip(float3 x);void clip(float2 x);void clip(float1 x);void clip(float x);
参数:裁剪时使用的标量或矢量条件。
描述:若是给定参数的任何一个份量是负数,就会舍弃当前像素的输出颜色。它等同于下面的代码:测试
void clip(float4 x) { if(any(x<0)) discard; }
在本节中,咱们使用图中的半透明纹理来实现透明度测试。该透明纹理在不一样区域的透明度也不一样,咱们经过它来查看透明度测试的效果。
在学习完本节后,咱们能够获得相似下图的效果。
步骤:
(1)为了在材质面板中控制透明度测试时使用的阈值,咱们在Properties语义块中声明一个范围在[0,1]之间的属性_Cutoff:spa
Properties{ _Color("Main Tint",Color)=(1,1,1,1) _MainTex("Main Tex",2D)="white"{} _Cutoff("Alpha Cutoff",Range(0,1))=0.5 }
_Cutoff参数用于决定咱们调用clip进行透明度测试时使用的判断条件。它的范围是[0,1],这是由于纹理像素的透明度就是在此范围内。
(2)而后,咱们在SubShader语义块中定义了一个Pass语义块:3d
SubShader{ Tags{"Queue"="AlphaTest""IgnoreProjector"="True" "RenderType"="TransparentCutout"} Pass{ Tags{"LightMode"="ForwardBase"} } }
咱们在前面已经知道了渲染的重要性,而且知道在Unity中透明度使用的渲染队列是名为AlphaTest的队列,所以咱们须要把Queue标签设置为AlphaTest。而RenderType标签可让Unity把这个shader纳入到提早定义的组(这里就是TransparentCutout组)中,以指明该shader是一个使用了透明度测试的shader。RenderType标签一般被用于着色器替换功能。咱们还把IgnoreProjector设置为True,这意味着这个Shader不会受到投影器(Projectors)的影响。一般,使用了透明度测试的Shader都应该在SubShader中设置这三个标签。最后,LightMode的标签是Pass标签中的一种,它用于定义该Pass在Unity的光照流水线中的角色。
(3)为了和Properties语义块中声明的属性创建联系,咱们须要定义和各个属性类型相匹配的变量:code
fixed4 _Color; sampler2D _Maintex; float4 _MainTex_ST; fixed _Cutoff;
因为_Cutoff的范围在[0,1],所以咱们可使用fixed精度来存储它。
(4)而后咱们定义顶点着色器输入和输出结构体,接着定义顶点着色器:orm
struct a2v{ float4 vertex:POSITION; float3 normal:NORMAL; float4 texcoord:TEXCOORD0; }; struct v2f{ float4 pos:SV_POSITION; float3 worldNormal:TEXCOORD0; float3 worldPos:TEXCOORD1; float2 uv:TEXCOORD2; }; v2f vert(a2v v){ v2f o; o.pos=mul(UNITY_MATRIX_MVP,v.vertex); o.worldNormal=UnityObjectToWorldNormal(v.normal); o.worldPos=mul(_Object2World,v.vertex).xyz; o.uv=TRANSFORM_TEX(v.texcoord,_MainTex); return o; }
上面的代码咱们已经见过不少次了,咱们在顶点着色器计算出世界空间的法线方向和顶点位置以及变换后的纹理坐标,再把他们传递给片元着色器。
(5)最重要的透明度测试的代码写在片元着色器中:blog
fixed4 frag(v2f i):SV_Target{ fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos)); fixed4 texColor=tex2D(_MainTex,i.uv); //Alpha text clip(TeXColor.a-_Cutoff); //Equal to //if((Texcolor.a-_Cutoff)<0.0){discard}; fixed3 albedo=texColor.rgb*_Color.rgb; fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo; fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(worldNormal,worldLightDir)); return fixed(ambient+diffuse,1.0); }
(6)最后,咱们须要为这个UnityShader设置合适的Fallback:排序
Fallback"Transparent/Cutout/VertexLit"
和以前使用的Diffuse和Specular不一样,此次咱们使用内置的Transparent/Cutout/VertexLit来做为回调Shader。这不只可以保证咱们编写的SubShader没法在当前显卡上工做时能够有合适的替代Shader,还能够保证使用透明度测试的物体能够正确的向其余物体投射阴影,具体原理咱们后面讲到。
材质面板中的Alpha cutoff参数用于调整透明度测试时使用的阈值,当纹理像素的透明度小于该值时,对应的片元就会被舍弃。当咱们逐渐调大该值时,立方体上的网格就会消失,以下图所示:队列
从上图能够看出,透明度测试获得的透明效果很极端——要么彻底透明,要么彻底不透明,它的效果每每像在一个不透明的物体上挖了一个洞。并且获得的透明效果在边缘处每每良莠不齐,有锯齿,这是由于在边界处纹理的透明度的变化精度问题。为了获得更加柔滑的透明效果,就可使用透明度混合。
透明度混合的实现要比透明度测试复杂一些,这是由于咱们在处理透明度测试时,实际上跟对待普通的不透明的物体几乎是同样的,只是在片元着色器中增长了对透明度的判断并裁剪片元的代码。而想要实现透明度混合就没有这么简单了。咱们回顾以前提到的透明度混合的原理:
透明度混合:这种方法能够获得真正的半透明效果。它会使用当前片元的透明度做为混合因子,与已经存储在颜色缓冲区中的颜色值进行混合,获得新的颜色。可是,透明度混合须要关闭深度写入,设使得咱们要很是当心物体的渲染顺序。
为了进行混合,咱们须要使用Unity提供的混合命令——Blend。Blend是Unity提供的设置混合模式的命令。想要实现半透明的效果就须要把当前自身的颜色和已经存在于颜色缓冲中的颜色值进行混合,混合时使用的函数就是由该指令决定的。下表给出了Blend命令的语义:
在本节里,咱们会使用第二种语义,即Blend SrcFactor DstFactor来进行混合。须要注意的是,这个命令在设置混合因子的同时也开启了混合模式。这是由于只有开启了混合以后,设置片元的透明通道才有意义,而Unity在咱们使用Blend命令的时候就自动帮咱们打开了。不少初学者老是抱怨为何本身的模型没有任何透明的效果,这每每是由于他们没有在pass中使用Blend命令,一方面是没有设置混合因子,但更重要的是,根本没有打开混合模式。咱们会把源颜色的混合因子SrcFactor设为SrcAlpha,而目标颜色的混合因子DstFactor设置为OneMinusSrcAlpha。这意味着混合后新的颜色是:
一般透明度的混合使用的就是这样的混合命令,在后面,咱们会看到更多混合语义用法。
使用和上小结一样的纹理,咱们能够获得下面的效果:
步骤:
(1)修改Properties语义块:
Properties{ _Color("Main Tint",Color)={1,1,1,1} _MainTex("Main Tex", 2D)="white"{} _AlphaScale("Alpha Scale",Range(0,1))=1 }
咱们使用一个新的属性_AlphaScale来代替原先的_Cutoff属性。_AlphaScale用于在透明纹理的基础上控制总体的透明度。相应的,咱们也须要在Pass中修改和属性对应的变量:
fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _AlphaScale;
(2)修改SubShader使用的标签:
SubShader{ Tags{"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} }
(3)与透明度测试不一样的是,咱们还须要在Pass中为透明度混合进行合适的混合状态设置:
Pass{ Tags{"LightMode"="ForwardBase"} ZWrite Off Blend SrcAlpha OneMinusSrcAlpha }
Pass的标签仍和以前的同样,这是为了让Unity可以按向前渲染路径的方式为咱们正确提供各个光照变量。除此以外,咱们还该把该Pass的深度写入(ZWrite)设置为关闭状态(Off),咱们在以前已经讲过为何要这样作了。这是很是重要的。而后咱们开启并设置了该Pass的混合模式。如在本节开头所讲的,咱们将源颜色(该片元着色器产生的颜色)的混合因子设为SrcAlpha,把目标颜色(已经存在于颜色缓冲区中的颜色)的混合因子设为OneMinusSrcAlpha,以获得合适的半透明效果。
(4)修改片元着色器:
fixed4 frag(v2f i):SV_Target{ fixed3 worldNormal=normalize(i.worldNormal); fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos)); fixed4 texColor=tex2D(_MainTex,i.uv); fixed3 albedo = texColor.rgb*_Color.rgb; fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo; fixed3 diffuse = _LightColor0.rgb*albedo*max(0,dot(worldNormal,worldLightDir)); return fixed(ambient+diffuse,texColor.a*_AlphaScale); }
上述代码和之前的几乎同样,只是移除了透明度测试的代码,并设置了该片元着色器返回值中的透明通道,它是纹理像素的透明通道和材质参数_AlphaScale的乘积。正如本节一开始所说,只有使用Blend命令打开混合后,咱们在这里设置的透明通道才有意义,不然这些透明度并不会对片元的透明效果有任何影响。
(5)最后,修改UnityShader的Fallback:
Fallback"Transparent/VertexLit"
咱们能够调节材质面板上的AlphaScale参数,以控制总体透明度。下图给出了不一样AlphaScale参数下的半透明效果。
咱们在之前解释了因为关闭深度写入带来的各类问题。当模型自己有复杂的遮挡关系或是包含了复杂的非凸格网格时候,就会有各类各样由于排序错误而产生的错误的透明效果。下图给出了使用UnityShader渲染Knot模型时获得的效果。
这都是因为咱们关闭了深度写入形成的,由于这样咱们就没法对模型进行像素级别的深度排序。在上面咱们提出了一种解决方法就是分割网格,从而能够获得一个“质量优等”的网格。可是不少状况下这每每时不切实际的。这是咱们能够想办法从新利用深度写入,让模型能够像半透明物体同样进行淡入淡出。这就是咱们的下一节内容。