深度缓冲(Depth Buffer)html
透明度混合时应关闭深度写入(ZWrite Off)缓存
若是不关闭深度写入,一个半透明表面背后的表面本就是透过它被咱们看到的,但因为深度测试时判断结果是该半透明表面)距离摄像机更近,致使后面的表面会被剔除掉,也就没法经过半透明面观察到后面的物体。app
另外注意关闭深度写入后要考虑物体的渲染顺序。以下图,一个半透明物体A和一个不透明物体B,B在A后方,若是先渲染A再渲染B会出现A被B遮挡的状况,这是错误的。所以渲染顺序在关闭深度写入的状况下极为重要。函数
为了保证渲染顺序正确,渲染引擎通常会对物体进行排序,再渲染,经常使用的方法是测试
但这种方法没法解决物体重叠的状况,所以须要额外的解决方案,好比分割网格等。可是也能够试着让透明通道更柔和,是穿插重叠看起来不那么明显。spa
Unity的渲染顺序3d
Unity经过一组Queue标签来决定模型归于哪一个渲染队列,队列由整数索引表示,索引号越小越先被渲染。orm
渲染队列 | 渲染队列描述 | 渲染队列值 |
Background | 这个队列被最早渲染。它被用于skyboxes等。 | 1000 |
Geometry | 这是默认的渲染队列。它被用于绝大多数对象。不透明几何体使用该队列。 | 2000 |
AlphaTest | 通道检查的几何体使用该队列。它和Geometry队列不一样,对于在全部立体物体绘制后渲染的通道检查的对象,它更有效。 | 2450 |
Transparent | 该渲染队列在Geometry和AlphaTest队列后被渲染。任何通道混合的(也就是说,那些不写入深度缓存的Shaders)对象使用该队列,例如玻璃和粒子效果。 | 3000 |
Overlay | 该渲染队列是为叠加效果服务的。任何最后被渲染的对象使用该队列,例如镜头光晕。 | 4000 |
透明度测试htm
只要有一个片元的透明度不知足条件(一般是小于某个阈值),那么它对应的片元便会被舍弃,不作任何处理。对象
在Unity中是用以下函数来进行透明度测试:
clip(texColor.a - _Cutoff);
texColor.a为纹理的alpha值,_Cutoff为阈值,该函数等价于
if(texColor.a - _Cutoff < 0) discard;
完整的代码
Shader "Unity Shader Book/Chapter 8/AlphaTest" { Properties { _Color("Color Tint",Color) = (1,1,1,1) _MainTex ("Texture", 2D) = "white" {} _Cutoff("Alpha Cutoff",Range(0,1)) = 0.5 } SubShader { Tags { "RenderType"="TransparentCutout" "Queue"="AlphaTest" "IgnoreProjector"="True" "LightMode"="ForwardBase"} LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" struct appdata { float4 vertex : POSITION; float3 normal:NORMAL; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal:TEXCOORD0; float3 worldPos:TEXCOORD1; float2 uv : TEXCOORD2; }; fixed4 _Color; fixed _Cutoff; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _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 = UnityWorldSpaceLightDir(i.worldPos); fixed4 texColor = tex2D(_MainTex,i.uv); //Alpha Test clip(texColor.a - _Cutoff); //equal to //if(texColor.a - _Cutoff < 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 fixed4(diffuse + ambient,1.0); } ENDCG } } }
效果以下,能够看到只是单纯的颜色剔除,并不算正常的透明效果。
为了获得正确的透明效果,能够使用透明度混合。
透明度混合
透明度混合即将透明物体的源颜色与其表面后方的目标颜色混合,其基本公式为:
DstColor-new(混合后的颜色) = SrcAlpha * SrcColor + (1- SrcAlpha) * DstColor。(SrcAlpha为混合因子)
该公式能够在Unity的ShaderLab语义中表示为
Blend SrcAlpha OneMinusSrcAlpha
更多语义能够参考官方文档
完整代码实现
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' Shader "Unity/Chapter 8/AlphaBlend" { Properties { _Color("Color Tint",Color) = (1,1,1,1) _MainTex ("Texture", 2D) = "white" {} _AlphaScale("Alpha Scale",Range(0,1)) = 0.5 } SubShader { Tags { "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent" } LOD 100 Blend SrcAlpha OneMinusSrcAlpha ZWrite On Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" struct appdata { float3 normal:NORMAL; float4 vertex : POSITION; float4 texcoord : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD2; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; float4 pos : SV_POSITION; }; fixed4 _Color; fixed _AlphaScale; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.worldNormal = UnityObjectToWorldNormal(v.normal); o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); 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); 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" }
该代码获得的效果以下
嗯,这才是正常的透明效果
开启深度写入的半透明效果
上述的透明混合并无开启深度写入(ZWrite On),所以若是物体有重叠,那么会产生错误的效果,以下
这时候须要在上文的透明度混合代码中的Pass代码块以前,插入一个新的Pass代码块
Pass { ZWrite On ColorMask 0 //用于设置颜色通道的写掩码,0表示不写入任何通道 }
这样就会获得正确的效果
双面渲染的透明效果
仔细观察上文的透明效果能够发现,咱们并不能透过半透明物体观察它们的内部状况,这是不合乎常理的,所以咱们须要进行双面渲染。
在Unity中,咱们能够经过Cull指令来控制剔除哪一面的渲染图元
Cull Back //剔除背面,只渲染前面,引擎的默认设置 Cull Front //剔除前面,仅渲染背面 Cull Off //关闭剔除
透明度测试的双面渲染
仅须要在Tags标签后插入一行
Cull Off
效果对比
透明度混合的双面渲染
将Pass代码块复制一份,一个Pass负责渲染前面,一个Pass负责渲染后面就行,格式以下
Shader "........."{ propeties { /*.....*/ } SubShader{ Pass { Tags{.......} Cull Front //剔除前面 /*其他代码保持不变 .... */ } Pass { Tags{.......} Cull Back //剔除后面 /*其他代码保持不变 .... */ } } }
实现的效果以下