0%

UnityShader半透明物体问题

为了写一个半透明物体的shader,有以下几点基础要求:

  1. 更近的不透明物体要挡住透明物体。因此透明物体需要深度测试来决定是否渲染。
  2. 透明物体不能挡住透明物体。因此透明物体之间不能进行深度测试来剔除渲染,即透明物体不能写入深度开启深度写入,透明物体错误地挡住透明物体
  3. 既然不能写入深度,不透明物体与透明物体之间的渲染顺序就十分关键。如果更远的不透明物体晚一点渲染,其会不合理地挡住更近的透明物体(因为透明物体没有写入深度值)。因此需要分离渲染顺序队列
  4. 透明物体之间的顺序问题。当然Unity也知道顺序很关键,由于没有fragment-level的顺序信息,因此unity会对透明物体按object-level从远到近排序,再进行渲染。这通常没有问题,但是当两个透明物体的深度是交叉的时候就会产生错误。因此理想解决方案是顺序无关透明(order independent transparency, OIT),即逐像素排序。 透明物体之间的渲染顺序错误。交叉地带应该由A覆盖B,实际上由于B的中心点更近,因此其所有fragment都在A之后渲染,导致B错误的覆盖A。

总之,一个寻常的半透明shader,通常要设置渲染队列,关闭深度写入,开启透明混合,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
}

Pass
{
Tags{"LightMode"="ForwardBase"}
ZWrite Off //关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha //设置Blend模式

CGPROGRAM
...
ENDCG
}
}
寻常的透明效果。透明的knot中间包含了一个不透明的球

效果如上图所示,然而其中依然存在着一些问题:

  1. 非凸网格的自我遮挡问题(同上第4点,即透明物体的fragment-level顺序问题)。由于我们对这个shader关闭了深度写入,因此knot上前后自遮挡的几个透明fragment会产生渲染顺序问题(注意这里不是背面和正面的顺序关系,遮挡的几个fragment都是knot的正面)。这里可以选择优化分割网格,使其成为不自遮挡的凸网格
  2. 双面渲染问题,我们看不到透明物体的背面。因为Unity默认将背对摄像机的面剔除了。然而这是不合理的,哪有透明物体对自己反而不透明的道理。因此我们需要关闭背面剔除 Cull Off。而同时渲染背面和正面之后,又会回到自我穿插问题,即由于没有顺序关系,背面可能挡住正面。

自我遮挡问题可以通过切分网格解决,但是更寻常的双面渲染问题还是需要shader进行解决。由于核心问题是让背面比正面更早渲染,因此可以使用两个Pass,前面的Pass Cull Front渲染背面,后面的Pass Cull Back渲染正面。

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
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
}
...
Pass
{
Cull Front
Tags{"LightMode"="ForwardBase"}
ZWrite Off //关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha //设置Blend模式
CGPROGRAM
...
ENDCG
}

Pass
{
Cull Back
Tags{"LightMode"="ForwardBase"}
ZWrite Off //关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha //设置Blend模式
CGPROGRAM
...
ENDCG
}
}