求知若饥,虚心若愚
在实时渲染中要实现透明效果,通常会在渲染模型时控制它的透明通道。
当开启透明混合后,当一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值之外,它还有另一个属性–透明度。
++参考文章++
为什么渲染顺序很重要
在渲染不透明物体时由于深度缓冲的存在,即使不去理会渲染顺序也不打紧。渲染每个片元时,会对比深度缓冲里的值,当这个片元离摄像机更近时(缓冲值比较)才会覆盖原来颜色缓冲的像素值,并将这个片元更新到深度缓冲中。
但是渲染不透明物体时,需要关闭深度写入。原因是透明物体背面的物体本身可以看到,但如果透明物体写入了深度缓冲中,由于背后这个物体深度测试不过,便会被舍弃,也就达不到我们预期的效果。
但是关闭深度写入,会带来其他问题。

假如有透明物体A和不透明物体B,A先渲染,由于A不透明所以关闭了深度写入,接着渲染B,此时深度缓冲中没有值,所以直接覆盖掉A的颜色,这就导致渲染出错了。同理即使两个物体都是不透明物体,先渲染离摄像机近的也会得到错误的结果。
基于这两点,渲染引擎一般都会先渲染不透明物体,并开启他们的深度测试和深度写入。然后将半透明物体按它们距离摄像机的远近排序,按从后往前的顺序渲染半透明物体,并开启它们的深度测试,但不开启深度写入。但实际上情况并没有那么理想,存在半透明物体在z轴上交叉的情况,并不能准确排序,除非将模型分割网格。

Unity Shader的渲染顺序
渲染队列,值越小越先渲染。
名称 |
队列索引号 |
描述 |
Background |
1000 |
这个渲染队列会在任何其他队列之前渲染,通常用来渲染那些应该在背景的物体 |
Geometry |
2000 |
默认的渲染队列,大多数物体都使用这个渲染队列,不透明物体使用这个队列 |
AlphaTest |
2450 |
需要透明度测试的物体使用这个队列,在Unity5中从Geometry队列单独分离出来,这样会更高效。 |
Transparent |
3000 |
这个队列的物体会在前面那些队列后面渲染,再按从后到前的顺序进行渲染(按照Unity3D的默认做法,在对透明物体在渲染之前的排序,是根据多边形中心点与摄像机的远近来比较的。),任何使用了透明度混合(关闭了深度写入)的物体都应该使用这个队列。 |
Overlay |
4000 |
该队列用于实现一些叠加效果,任何需要在最后渲染的物体应该使用这个队列 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // 透明度测试应包含: SubShader { Tags {"Queue"="AlphaTest"} pass { ··· } }
// 透明度混合应包含: SubShader { Tags {"Queue"="Transparent"} pass { ZWrite Off ··· } }
|
透明度测试
透明度测试:只要一个片元的透明度不满足条件(通常是小于某个阈值),那么它对应的片元就会被舍弃。被舍弃的片元不会再进行任何处理,也不会对颜色缓冲产生任何影响,否则会按照正常的不透明片元来处理。
通常会使用clip函数来处理
1 2 3 4 5 6
| //参数有多个重载版本,可以是标量也可以是矢量 void clip(float4 x) { if (any(x < 0)) discard }
|
实践
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| Shader "UnityShadersLearn/Chapter8/Alpha Test" { Properties { _Color ("Main Tint", Color) = (1,1,1,1) _MainTex ("Main Tex", 2D) = "white"{} _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5 } SubShader { // 具体含义可参考https://zhuanlan.zhihu.com/p/51080323 // RenderType其实就是分组,可以配合camera.SetReplacementShader (EffectShader, Tag)进行着色器替换 // 比如要将某个RenderType的物体的shader切换成选中的shader(可以overlay突出) Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"} Pass { Tags {"LightMode" = "ForwardBase"} CGPROGRAM #pragma vertex vert; #pragma fragment frag;
#include "Lighting.cginc"
fixed4 _Color; sampler2D _MainTex; float4 _MainTex_ST; fixed _Cutoff;
struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; };
struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; };
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 = mul(unity_ObjectToWorld, v.vertex); 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);
clip(texColor.a - _Cutoff); // if (texColor.a - _Cutoff < 0) // discard;
fixed3 albedo = texColor.rgb * _Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, 1.0); } ENDCG } } // 保证透明度测试的物体能正确向其他物体投射阴影 FallBack "Transparent/Cutout/VertexLit" }
|
透明度混合
透明度混合:这种方法可以得到真正的半透明效果,它会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,从而得到新的颜色。但是由于需要关闭深度写入,这使得我们要非常小心物体的渲染顺序。
想要半透明效果,blend命令,设置混合因子,以及打开混合模式必不可少。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| Shader "UnityShadersLearn/Chapter8/Alpha Blend" { Properties { _Color ("Main Tint", Color) = (1,1,1,1) _MainTex ("Main Tex", 2D) = "white"{} _AlphaScale ("Alpha Scale", Range(0, 1)) = 1 } SubShader { // 具体含义可参考https://zhuanlan.zhihu.com/p/51080323 // RenderType其实就是分组,可以配合camera.SetReplacementShader (EffectShader, Tag)进行着色器替换 // 比如要将某个RenderType的物体的shader切换成选中的shader(可以overlay突出) Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} 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; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; };
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 = mul(unity_ObjectToWorld, v.vertex); 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 * saturate(dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, texColor.a * _AlphaScale); } ENDCG } } // 保证透明度测试的物体能正确向其他物体投射阴影 FallBack "Transparent/VertexLit" }
|
开启深度写入的半透明效果
存在问题:当模型网格之间有互相交叉的结构时,往往会得到错误的半透明效果

这是由于关闭了深度写入而造成的排序问题。按照Unity3D的默认做法,在对透明物体在渲染之前的排序,是根据多边形中心点与摄像机的远近来比较的。
可以多使用一个Pass来写入深度缓冲,但不输出颜色(使用ColorMask),这个Pass必须为第一个。(疑问:这和直接开启深度写入有什么区别?也是会写入深度缓冲,测试了下好像效果差不多)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| Shader "UnityShadersLearn/Chapter8/Alpha Blend Z Write" { Properties { _Color ("Main Tint", Color) = (1,1,1,1) _MainTex ("Main Tex", 2D) = "white"{} _AlphaScale ("Alpha Scale", Range(0, 1)) = 1 } SubShader { // 具体含义可参考https://zhuanlan.zhihu.com/p/51080323 // RenderType其实就是分组,可以配合camera.SetReplacementShader (EffectShader, Tag)进行着色器替换 // 比如要将某个RenderType的物体的shader切换成选中的shader(可以overlay突出) Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} Pass { // 开启深度写入,并不输出颜色 ZWrite On ColorMask 0 } Pass { ··· } } // 保证透明度测试的物体能正确向其他物体投射阴影 FallBack "Transparent/VertexLit"
|
Shaderlab的混合命令
混合还有很多其他的用处,不仅仅是用于透明度的混合。当片元着色器产生一个颜色的时候,可以选择与颜色缓存中的颜色进行混合。这样一来混合就和两个操作数有关:源颜色和目标颜色。源颜色我们可用S表示,指的是由片元着色器产生的颜色值:目标颜色我们用D表示,指的是从颜色缓存中读取到的颜色值。对对他们进行混合后得到的输出颜色,我们用O来表示,他会重新写入到颜色缓存中,需要注意的是,当我们谈及混合中的源颜色目标颜色和输出颜色时,他们都包含了RGBA四个通道的值,而并非仅仅是RGB通道。
想要使用混合,我们必须开启它。在Unity中,当我们使用Blend命令时,除了设置混合状态以外也开启了混合。但是,在其他图形api中我们是需要手动开启的。
混合等式和参数
混合是一个逐片元操作,不可编程但高度可配置。
常见的混合类型
通过混合操作和混合因子命令的组合,我们可以得到一些类似Photoshop混合模式中的混合效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| //正常,即透明度混合 Blend SrcAlpha OneMinusSrcAlpha
//柔和相加 Blend OneMinnusDstColor One
//正片叠低,即相乘 Blend DstColor Zero
//两倍相乘 Blend DstColor SrcColor
//变暗 BlendOp Min Blend One One
//变亮 BlendOp Max Blend One One
//滤色 Blend OneMinusDstColor One //等同于 Blend One OneMinusSrcColor
//线性减淡 Blend One One
|

双面渲染的透明效果
默认情况下渲染引擎剔除了物体背面(相对于摄像机的方向)的渲染图元,而只渲染了物体的正面。如果我们想要得到双面渲染的效果(需要渲染的图元数目成倍增长),可以使用Cull指令来控制需要剔除的哪个面的渲染图元。
在Unity中,Cull指令的语法如下:
Cull Back | Front | Off
透明度测试的双面渲染
直接在Pass加上Cull Off即可
1 2 3 4 5
| Pass { Tags {"LightMode" = "ForwardBase"} Cull Off ··· }
|
透明度混合的双面渲染
关闭深度写入后,需要小心控制渲染顺序来得到正确的深度关系。所以直接关闭剔除功能,将无法保种同一个物体的正面和背面图元的渲染顺序,就有可能得到错误的半透明效果。
解决办法是用两个Pass,一个渲染背面一个渲染正面。由于SubShader会顺序执行各个Pass,所以保证背面先画即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| Shader "UnityShadersLearn/Chapter8/Alpha Blend Both Sided" { Properties { _Color ("Main Tint", Color) = (1,1,1,1) _MainTex ("Main Tex", 2D) = "white"{} _AlphaScale ("Alpha Scale", Range(0, 1)) = 1 } SubShader { // 具体含义可参考https://zhuanlan.zhihu.com/p/51080323 // RenderType其实就是分组,可以配合camera.SetReplacementShader (EffectShader, Tag)进行着色器替换 // 比如要将某个RenderType的物体的shader切换成选中的shader(可以overlay突出) Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} Pass { Tags {"LightMode" = "ForwardBase"} Cull Front ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM ··· ENDCG } Pass { Tags {"LightMode" = "ForwardBase"} Cull Back ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM ··· ENDCG } } // 保证透明度测试的物体能正确向其他物体投射阴影 FallBack "Transparent/VertexLit" }
|