透明物体的渲染

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sixdaycoder/article/details/78093531

概述

一般来说,我们使用Alpha Test(透明度测试)或者Alpha Blend(透明度混合)来实现透明效果,其中Alpha Blend(之后简称)Blend可以实现真正的半透明效果。

无论哪种实现方式,我们都需要关闭透明物体的深度写入(禁止ZWrite)。

为什么要关闭透明物体的深度写入?

设想一种情况,假设半透明物体A在不透明物体B的前面。

如果开启了深度写入,那么由于A在B的前面,ZBuffer中存储的肯定是A的Z值,那么B就无法通过ZTest,导致A的颜色会覆盖掉B的颜色。这样最终呈现的效果就是半透明物体A挡住了不透明物体B,这显然不是我们需要的结果。

所以,我们必须关闭透明物体的深度写入,不过这样做实际上破坏了深度缓冲的工作机制,这带来了很大的副作用,为了让深度缓冲机制正常工作,我们就必须严格的控制物体的渲染顺序。

半透明物体和透明物体的渲染顺序

假设半透明物体A和不透明物体B,A仍然在B的前面。

  • 先渲染半透明物体A再渲染不透明物体B :

    • (1) 渲染A,ZBuffer为空,通过ZTest,由于关闭了透明物体的ZWrite,所以A的Z值不会写入ZBuffer
    • (2) 渲染B,实际上此时ZBuffer还是初始值,B通过ZTest之后直接写入ZWrite,导致B的颜色覆盖掉A。
  • 先渲染不透明物体B再渲染透明物体A

    • (1) 渲染B,ZBuffer为空,通过ZTest,B的Z值写入ZBuffer。
    • (2) 渲染A,由于A是在B的前面,所以A会通过ZTest(但不写入ZWrite),之后根据设置好的Blend公式做出混合操作,实现半透明效果。

结论是,如果半透明物体和不透明物体共存,那么首先开启ZWrire,渲染不透明物体。再关闭ZWrite,渲染半透明物体。

半透明物体和半透明物体的渲染顺序

假设半透明物体A和半透明物体B, A在B的前面。

  • 先渲染A再渲染B :

    • (1) 渲染A,ZBuffer为空,通过ZTest,将A写入Framebuffer(颜色缓冲不是ZBuffer)
    • (2) 渲染B,ZBuffer仍然为空,通过ZTest,将B和A颜色混合。如果使用SrcAlpha和OneMinusSrcAlpha这样的公式,会让B的颜色重于A,造成了B在A前面的视觉效果。
  • 先渲染B再渲染A :

    • (1) 渲染B,ZBuffer为空,通过ZTest,将B写入Framebuffer(颜色缓冲不是ZBuffer)
    • (2) 渲染A,ZBuffer仍然为空,通过ZTest,将B和A颜色混合。如果使用SrcAlpha和OneMinusSrcAlpha这样的公式,会让A的颜色重于B,A在B前面的视觉效果,正是我们要的效果。

结论是,多个透明物体,要按照由远到近的顺序渲染。

PS : Blend公式

DstColor(new)=SrcAlphaSrcColor+(1SrcAlpha)DstColor(old)

其中DstColor是颜色缓冲中的颜色值,SrcColor是(纹理采样+光照计算)后的的颜色,SrcAlpha是纹理采样的alpha通道。

开启ZWrite的透明效果

关闭ZWrite并依靠排序的方法来搞定渲染顺序,有时候也会出错(因为某个model很可能是多个不在一个平面上的不规则模型),这里有一种开启ZWrite的渲染方法。

核心思想就是渲染两次:

  • 第一遍渲染开启ZWrite但是不把颜色写入到Framebuffer中,得到一个正确结果的ZBuffer
  • 第二遍渲染就像正常的Blend过程,关闭ZWrite直接混合,由于深度信息已经正确,所以无需排序。

这样的方法是比较消耗性能的,之前的Blend效果就比较可以,一般不使用这种方式。

PS : 这种方法仅适用于渲染半透明物体,对不透明物体不需要这么做。

双向透明渲染

核心思想也是渲染两次 :

  • 第一次渲染,CullFront,渲染背面,剔除前面,当然这个过程是关闭ZWrite的,就像常规的Blend操作。
  • 第二次渲染,CullBack, 渲染前面,剔除背面,当然这个过程是关闭ZWrite的,就像常规的Blend操作。

这两次渲染隐含了半透明物体共存时,渲染顺序必须是由远到近的顺序渲染。

猜你喜欢

转载自blog.csdn.net/sixdaycoder/article/details/78093531