首页 > 代码库 > 【Unity Shaders】Alpha Test和Alpha Blending

【Unity Shaders】Alpha Test和Alpha Blending


写在前面


关于alpha的问题一直是个比较容易摸不清头脑的事情,尤其是涉及到半透明问题的时候,总是不知道为什么A就遮挡了B,而B明明在A前面。这篇文章就总结一下我现在的认识~


Alpha Test和Alpha Blending是两种处理透明的方法。


Alpha Test采用一种很霸道极端的机制,只要一个像素的alpha不满足条件,那么它就会被fragment shader舍弃,“我才不要你类!”;否则,就会按正常方式写入到缓存中,并进行正常的深度检验等等,也就是说,Alpha Test是不需要关闭ZWrite的Alpha Test产生的效果也很极端,要么完全透明,即看不到,要么完全不透明。


Alpha Blending则是一种中庸的方式,它使用当前fragment的alpha作为混合因子,来混合之前写入到缓存中颜色值。但Alpha Blending麻烦的一点就是它需要关闭ZWrite,并且要十分小心物体的渲染顺序。如果不关闭ZWrite,那么在进行深度检测的时候,它背后的物体本来是可以透过它被我们看到的,但由于深度检测时大于它的深度就被剔除了,从而我们就看不到它后面的物体了。因此,我们需要保证物体的渲染顺序是从后往前,并且关闭该半透明对象的ZWrite。



Surface Shader


在Unity的Surface Shader里实现上述两种技术是非常简单的,可以参见之前的文章——Alpha Test和Alpha Blending。简单总结一下就是,只要在#pragma里设置alphatest:_Cutoffalpha指令即可。


但是,它们背后的原理是什么呢?这就要从它们生成的Vertex & Fragment Shader说起了。那么,请看下一节~



Vertex & Fragment Shader


我们先来说比较简单的Alpha Test


在Vertex & Fragment Shader里,要实现它非常简单。


一种方法是自己在shader中编写代码,只要使用类似下面的语句就可以了:

  // alpha test
  clip (o.Alpha - _Cutoff);

clip函数非常简单,就是检查它的参数是否小于0。如果是,就调用discard舍弃该fragment;否则就放过它。


另一种方法是使用固定管线的Alphatest指令。具体可见官方文档。使用Alphatest指令的方法选择更多,我们不仅仅是判断它小于_Cutoff时舍弃该fragment,还可以是判断它是否大于、是否大于等于,等等。但原理是和第一种方法一样,归根到底都是要靠discard函数来舍弃那么不符合条件的fragments。


Alpha Blending略微复杂一点,因为它涉及到了ZWrite的一些问题。它首先需要关闭ZWrite:

 ZWrite Off

然后,我们可以指定混合函数,类似下面这样:

Blend SrcAlpha OneMinusSrcAlpha

上述是最常见的混合函数因子,其他可以参见官方文档。


在使用Alpha Blending时,一定要格外小心由于它关闭了深度缓存而造成的种种问题。从Unity的这张图可以看出:



深度检验是在Vertex Shader后面就进行的,因此在Fragment Shader阶段,由于它关闭了深度缓存,所以像素的覆盖与否完全取决于渲染的先后顺序。


当然,有时我们可以混合使用这两种技术,例如第一个pass里使用Alpha Test渲染实体部分,第二pass里对上一个pass里被剔除的fragment使用Alpha Blending进行柔和渲染。



性能


Unity的官方文档中,有两个地方提到了它们的性能问题——一个是编写Shaders时的性能提示,一个是优化图像性能。微微地感觉这两个页面有很多重复,未来某一天可能会合成一个页面。。。


它们是这么写的:

      Fixed function AlphaTest or it’s programmable equivalent, clip(), has different performance characteristics on different platforms:

  • Generally it’s a small advantage to use it to cull out totally transparent pixels on most platforms.
  • However, on PowerVR GPUs found in iOS and some Android devices, alpha testing is expensive. Do not try to use it as “performance optimization” there, it will be slower.

以及

     Keep in mind that alpha test (discard) operation will make your fragments slower.


总结一下,就是使用Alpha Test看似更简单,但其实在大多数平台上,相比与Alpha Blending,只有一点小小的性能提升。但是!!!在iOS和某些Android设备上,由于它们使用了PowerVR GPUs,因此Alpha Test的性能消耗反而会更大。因此,一个忠告就是,尽可能使用Alpha Blending,而不要使用Alpha Test


我们会觉得很奇怪,没有关闭深度缓存,不需要计算混合颜色,仅仅调用来discard舍弃fragment不是非常简单的事吗?为什么在移动平台上反而效率更低呢?有句话叫,“简单粗暴”,可以用在这里。性能下降的原因就是它太粗暴了!(我乱说的你不要当真。。。)


好啦,言归正传~原因呢由于我经验有限,只能依靠强大的谷歌来找答案。我找到了这里、这里。总结一下,就是PowerVR GPUs使用了一种叫做“Deferred Tile-Based-Rendering”的技术。这种技术里有一个优化阶段,就是为了减少overdraw它会在调用fragment shader前判断哪些Tile是会被真正渲染的。但是,由于Alpha Test在fragment shader里使用了clip函数改变了fragment是否被渲染的结果,因此,GPUs就无法使用上述的优化策略了。也就是说,只要在完成了所有的fragment shader处理后,GPUs才知道哪些fragments会被真正渲染到屏幕上,这样,原先那些可以减少overdraw的优化就都无效了。


【Unity Shaders】Alpha Test和Alpha Blending