这篇文章主要说一下Shader里面的透明效果,透明效果在UnityShader中有两种方式,透明度测试和透明度混合,透明度测试是比较霸道的透明算法,它只有透明和不透明两种情况,也就是说这个UnityShader能实现的不是透明就是不透明,不会存在半透明现象,所以它的值就只有0和1两种。那么要想实现半透明效果的话就需要用到透明度混合了,透明度混合的值不像透明度测试一样只用0和1,它是可以存在中间值的,所以实现半透明效果用透明度混合,而不需要实现半透明的情况下直接用透明度测试就OK。
ps:本篇文章采自《UnityShader入门精要》一书。
透明度测试
透明度测试的Shader有几个新的知识点出现,因为要控制这个Shader是否透明,所以需要一个阈值来控制,上面也说到了,透明度测试的值只有0和1,所以阈值的范围就是0,1,即:_Cutoff("AlphaCutoff",Range(0,1)) = 0.5
另外,做透明效果需要在SubShader中标明渲染队列和渲染顺序以及渲染方式,之前的没有标记是因为非透明效果的是会自动的进行深度测试以及渲染顺序,而非透明效果要是不进行标记的话,会出现各种各样意想不到的问题,因为UnityShader内部会进行自动的渲染设置,为了避免这些问题,需要我们手动进行设置,即:
Tags{“Queue” = “AlphaTest” “IgnorProjector” = “True” “RenderType” = “TransparentCutout”}
Queue 设定渲染队列 AlphaTest表示是透明度测试。
IgnorProjector 设定Shader不受投影器影响 为True。
RenderTypr 设定Shader归组为TransParentCutout透明度测试。
这些设置和设定在之后会有一个文章单独的记录。
这里我是写在了SubShader中表示只有这个SubShader是进行这个操作的,如果写在Pass中就表示只有那个Pass是进行这个操作,如果写在全局就表示整个Shader都会进行这个操作。
设置好之后需要进行计算了,透明度计算公式:
Clip(fixed4)
if(any(x<0))
discard;
换算到Unity之后为:
clip(alphaColor.a - _Cutoff)
if( ( alphaColor.a - _Cutoff )<0 )
discard;
discard是放弃的意思相当于如果这个纹理的a值也就是透明度值减去阈值以后是小于0的,那么我们就放弃这次的操作。
完整代码如下:
Shader "Unlit/AlphaShader" { 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" "IgnorProjector"="True" "RenderType"="TransparentCutout" } Pass { Tags{"LightMode" = "ForwardBase"} CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; fixed3 worldNormal : TEXCOORD0; fixed3 worldLightDir : TEXCOORD1; fixed3 worldPos : TEXCOORD2; float2 uv : TEXCOORD3; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _Color; fixed _Cutoff; v2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); o.worldPos = UnityObjectToWorldDir(o.pos).xyz; o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldLightDir = UnityWorldSpaceLightDir(o.worldPos); return o; } fixed4 frag (v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldPos = normalize(i.worldPos); fixed3 worldLightDir = normalize(i.worldLightDir); //透明度计算 fixed4 alphaColor = tex2D(_MainTex, i.uv); clip(alphaColor.a - _Cutoff); if ((alphaColor.a - _Cutoff)<0.0) { discard; } fixed3 albedo = alphaColor.rgb * _Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal, worldLightDir)); fixed3 color = ambient + diffuse; return fixed4(color,1.0); } ENDCG } } }
从上面的脚本可以看出,除了增加了上面说的那些知识点以外,都是之前一样的东西。
透明度混合(关闭深度写入)
透明度混合开始说了它能实现半透明效果,透明度混合分两种,关闭深度写入和开启深度写入,深度写入这个概念简单一点来理解就是UnityShader会自动判断物体的距离以及遮挡,被遮挡的就不渲染,距离远的渲染也是有影响的,但是半透明效果的话,透明物体后面的物体要显示出来,所以我们要手动控制这个深度写入。
首先阈值与透明度测试一样,范围也是0到1。
渲染设置,渲染队列更改为Transparent,其余与透明度测试一致。
最重要的一点在Pass中要关闭深度写入,即:ZWrite Off
设置源颜色的混合因子和目标颜色的混合因子,因为半透明是存在颜色混合的,所以:BlendSrcAlpha OneMinusSrcAlpha
最后只需要在返回整体颜色值的时候将透明度的颜色的a值乘以阈值返回即可
完整代码如下:(关闭深度测试)
Shader "Unlit/AlphaBlendingShader" { Properties { _MainTex ("Texture", 2D) = "white" {} _Color("Color Tint",Color) = (1,1,1,1) _AlphaScale("Alpha Scale",Range(0,1)) = 0.5 } SubShader { Tags { "Queue"="Transparent" "IgoneProjector"="True" "RenderType"="TransparentCutout" } //Cull Off Pass { Tags{"LightMode" = "ForwardBase"} ZWrite off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" struct a2v { float4 vertex : POSITION; float3 normal:NORMAL; float2 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; float3 worldLightDir : TEXCOORD2; float2 uv : TEXCOORD3; }; sampler2D _MainTex; float4 _MainTex_ST; fixed _AlphaScale; fixed3 _Color; v2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = UnityObjectToWorldDir(o.pos).xyz; o.worldLightDir = UnityWorldSpaceLightDir(o.worldPos); return o; } fixed4 frag (v2f i) : SV_Target { fixed3 worldPos = normalize(i.worldPos); fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = normalize(i.worldLightDir); fixed4 alphaColor = tex2D(_MainTex, i.uv); fixed3 albedo = alphaColor.rgb*_Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; fixed3 diffuse = _LightColor0.rgb * albedo*saturate(dot(worldNormal, worldLightDir)); fixed3 color = ambient + diffuse; return fixed4(color,alphaColor.a * _AlphaScale); } ENDCG } } }
上面的代码中有个Call Off 禁用了,打开以后会发现物体内部的显示也会出现,如果不写的话物体内部就会被剔除。透明度测试也是一样的。
透明度混合(开启深度写入)
关闭深度写入实现的半透明会出现重叠或者重影的现象,为了防止同为透明物体出现遮挡或渲染顺序出现问题,用开启深度写入的透明度混合实现,也就是用两个Pass来处理渲染模型,一个用来开启深度写入一个用来做正常的透明度混合,在开启深度写入的Pass中写:
ZWrite On
ColorMask0
ColorMask 0 意味着不写入任何颜色通道,不会输出任何颜色
完整代码如下:
Shader "Unlit/AlphaBlendingShader_Self" { Properties { _MainTex ("Texture", 2D) = "white" {} _Color("Color Tint",Color) = (1,1,1,1) _AlphaScale("Alpha Scale",Range(0,1)) = 0.5 } SubShader { Tags { "Queue"="Transparent" "IgnorProject"="True" "RenderType"="TransparentCutout"} Pass{ Tags{ "LightMode" = "ForwardBase" } Cull Front ZWrite On ColorMask 0 } Pass { Cull Back Tags{"LightMode" = "ForwardBase"} ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; float3 worldLightDir : TEXCOORD3; float2 uv : TEXCOORD0; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _Color; fixed _AlphaScale; v2f vert (a2v v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = UnityObjectToWorldDir(o.pos).xyz; o.worldLightDir = UnityWorldSpaceLightDir(o.worldPos); return o; } fixed4 frag (v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldPos = normalize(i.worldPos); fixed3 worldLightDir = normalize(i.worldLightDir); fixed4 alphaColor = tex2D(_MainTex, i.uv); fixed3 albedo = alphaColor.rgb * _Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal, worldLightDir)); fixed3 color = ambient + diffuse; return fixed4(color, alphaColor.a * _AlphaScale); } ENDCG } } }
这里出现两个东西一个Cull Front 一个Cull Back
这个是实现双面渲染的透明效果,没有这个操作的话透明效果只是正面透明且背面透明是看不到的,这是因为在渲染过程中内部和背面被剔除了渲染图元,为了实现背面和内部的透明渲染,用Cull指令来控制要剔除哪个面的渲染图元
Cull Back 背面不渲染
Cull Front 正面不渲染
Cull Off 关闭剔除功能