为了写一个半透明物体的shader,有以下几点基础要求:
- 更近的不透明物体要挡住透明物体。因此透明物体需要深度测试来决定是否渲染。
- 透明物体不能挡住透明物体。因此透明物体之间不能进行深度测试来剔除渲染,即透明物体不能写入深度。
- 既然不能写入深度,不透明物体与透明物体之间的渲染顺序就十分关键。如果更远的不透明物体晚一点渲染,其会不合理地挡住更近的透明物体(因为透明物体没有写入深度值)。因此需要分离渲染顺序队列。
- 透明物体之间的顺序问题。当然Unity也知道顺序很关键,由于没有fragment-level的顺序信息,因此unity会对透明物体按object-level从远到近排序,再进行渲染。这通常没有问题,但是当两个透明物体的深度是交叉的时候就会产生错误。因此理想解决方案是顺序无关透明(order independent transparency, OIT),即逐像素排序。
总之,一个寻常的半透明shader,通常要设置渲染队列,关闭深度写入,开启透明混合,如下所示:
1 | SubShader |
效果如上图所示,然而其中依然存在着一些问题:
- 非凸网格的自我遮挡问题(同上第4点,即透明物体的fragment-level顺序问题)。由于我们对这个shader关闭了深度写入,因此knot上前后自遮挡的几个透明fragment会产生渲染顺序问题(注意这里不是背面和正面的顺序关系,遮挡的几个fragment都是knot的正面)。这里可以选择优化分割网格,使其成为不自遮挡的凸网格。
- 双面渲染问题,我们看不到透明物体的背面。因为Unity默认将背对摄像机的面剔除了。然而这是不合理的,哪有透明物体对自己反而不透明的道理。因此我们需要关闭背面剔除 Cull Off。而同时渲染背面和正面之后,又会回到自我穿插问题,即由于没有顺序关系,背面可能挡住正面。
自我遮挡问题可以通过切分网格解决,但是更寻常的双面渲染问题还是需要shader进行解决。由于核心问题是让背面比正面更早渲染,因此可以使用两个Pass,前面的Pass Cull Front渲染背面,后面的Pass Cull Back渲染正面。
1 | SubShader |