Unity Shader (7)
1、Unity实现透明效果有两种方法:使用透明测试(Alpha Test),这种方法无法得到真正的半透明效果;使用透明混合(Alpha Blend)
1)透明测试:给定一个透明通道的阈值,在一定条件下(如大于这个阈值)时抛弃这个片元,一刀切得意思
2)混合:根据指定的混合公式使用源信息与目标信息进行混合,需要注意的是混合时深度写入是关闭只读的,这是为什么不透明物体能够遮挡住透明物体的原因,但是渲染顺序有一些要求,因为透明物体不写入深度信息,那么如果先写入透明物体那么第一次写入不透明物体时深度信息未被写入,不管是在已渲染物体的前面还是后面都将直接覆盖颜色缓冲
2、ShaderLab提供了Queue标签来指定物体的渲染队列来控制物体渲染顺序,渲染队列数值越小物体越早被渲染
3、Alpha Test shader,关键就在于丢弃片元的那一处
1 Shader "Unity Shaders Book/Chapter 8/Alpha Test" { 2 Properties { 3 _Color ("Color Tint", Color) = (1, 1, 1, 1) 4 _MainTex ("Main Tex", 2D) = "white" {} 5 _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5//用于Alpha Test的阈值,小于这个值时丢弃片元 6 } 7 SubShader {
//指定渲染队列;IgnoreProjector是设置忽略投影器的影响;RenderType用于设置shader分组 8 Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"} 9 Pass 10 { 11 Tags { "LightMode"="ForwardBase" } 12 13 CGPROGRAM 14 #pragma vertex vert 15 #pragma fragment frag 16 #include "Lighting.cginc" 17 18 fixed4 _Color; 19 sampler2D _MainTex; 20 float4 _MainTex_ST; 21 fixed _Cutoff; 22 23 struct a2v { 24 float4 vertex : POSITION; 25 float3 normal : NORMAL; 26 float4 texcoord : TEXCOORD0; 27 }; 28 struct v2f { 29 float4 pos : SV_POSITION; 30 float3 worldNormal : TEXCOORD0; 31 float3 worldPos : TEXCOORD1; 32 float2 uv : TEXCOORD2; 33 }; 34 35 v2f vert(a2v v) { 36 v2f o; 37 o.pos = mul(UNITY_MATRIX_MVP, v.vertex); 38 o.worldNormal = UnityObjectToWorldNormal(v.normal); 39 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; 40 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); 41 return o; 42 } 43 fixed4 frag(v2f i) : SV_Target { 44 fixed3 worldNormal = normalize(i.worldNormal); 45 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 46 fixed4 texColor = tex2D(_MainTex, i.uv); 47 // Alpha test 这里是关键,下面注释的代码与clip效果相同 48 clip (texColor.a - _Cutoff); 49 // if ((texColor.a - _Cutoff) < 0.0) { 50 // discard; 51 // } 52 fixed3 albedo = texColor.rgb * _Color.rgb; 53 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; 54 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir)); 55 return fixed4(ambient + diffuse, 1.0); 56 } 57 ENDCG 58 } 59 } 60 FallBack "Transparent/Cutout/VertexLit" 61 }
4、Alpha Blend 关键在于深度写入的关闭与混合公式的指定,混合比例因子不是必须的,因为本身的Alpha就是一个因子,添加一个比例因子是为了更好的控制混合效果(当前片元是源信息,已经写入颜色缓冲的是目标信息)
Shader "Unity Shaders Book/Chapter 8/Alpha Blend" { Properties { _Color ("Color Tint", Color) = (1, 1, 1, 1) _MainTex ("Main Tex", 2D) = "white" {} _AlphaScale ("Alpha Scale", Range(0, 1)) = 1 //这个是本片元的混合比例因子 } SubShader { Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} Pass { Tags { "LightMode"="ForwardBase" } ZWrite Off //关闭深度写入 Blend SrcAlpha OneMinusSrcAlpha //指定混合公式:color_src * srcalpha + (1 - srcalpha) * color_dest CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; fixed _AlphaScale; 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(unity_ObjectToWorld, v.vertex).xyz; o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); 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); 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, texColor.a * _AlphaScale); //注意混合比例因子的使用方式 } ENDCG } } FallBack "Transparent/VertexLit" }
5、因为半透明片元深度信息是不写入的,所以可能出现背面片元出现在正面片的上面的穿帮情况,因此存在开启深度信息写入的混合方式,具体实现方式为在上面的shaderlab中添加一个Pass屏蔽颜色缓冲的写入只写入深度信息但代价是运算量的上升以及会破坏批处理的使用规则
Shader "Unity Shaders Book/Chapter 8/Alpha Blending With ZWrite" { Properties { _Color ("Color Tint", Color) = (1, 1, 1, 1) _MainTex ("Main Tex", 2D) = "white" {} _AlphaScale ("Alpha Scale", Range(0, 1)) = 1 } SubShader { Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} // Extra pass that renders to depth buffer only Pass { ZWrite On ColorMask 0 } Pass { Tags { "LightMode"="ForwardBase" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; fixed _AlphaScale; 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(unity_ObjectToWorld, v.vertex).xyz; o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); 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); 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, texColor.a * _AlphaScale); } ENDCG } } FallBack "Transparent/VertexLit" }
6、混合公式在计算上RGB与Alpha是分开的,可以分别制定它们的计算方式
混合的方式非常的多
默认的混合算术运算符是加,可以通过BlendOp指定算术符
7、常见的混合效果
8、Unity 中的ShaderLab背面剔除是默认开启的,所以即使是半透明物体也看不见背面的东东,因此对于Alpha Test只需要关闭面剔除即可,但是对于Alpha Blend是不够的,不写入深度信息带来额外的副作用,应该使用两个Pass,第一个画背面,第二个画正面
Shader "Alpha_Blend_Both_Side" { Properties { ...... } SubShader { Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} Pass { Tags { "LightMode"="ForwardBase" } Cull Front ......//blend code ENDCG }
Pass { Tags { "LightMode"="ForwardBase" } Cull Back ......//blend code ENDCG }
} FallBack "Transparent/VertexLit" }