前言
自然界中存在的“半透明”其实是Transclusent
这个词,指的是类似玉石那种透亮且光线可穿过的物体。只有这个范畴里的极端情况例如玻璃,才比较接近渲染上的半透明Transparent这个概念。
如果要简单来说两者的区分,可以认为Transparent一般指考虑一层颜色的着色与混合,而Transclusent 则是一个需要结合次表面散射等物理模拟的复杂方案。本文后续中文提到的“半透明”都是指Transparent,对应的度量值是Alpha(往往被俗称为透明度,但因为Alpha为100%时是不透明,所以严格来说应该是不透明程度)。
这个系列之前的文章中也提到过,半透明比不透明物体往往有更多消耗,主要体现在2个方面:
——不透明的片元可以被深度缓冲优化,半透明则不行。
——不透明的片元可以采用延迟渲染管线集中进行光照着色,半透明不行,一般来说必须使用前向渲染。
*这里提供了之前讲深度缓冲和延迟渲染的文章链接。
要使用半透明渲染(一般包含透明纹理)的原因也很明确,主要是两方面——使用透明纹理比起3D建模在显示上有更好的效果和性价比(例如植物、头发等),或是要呈现自然界本身就是半透明的物体(如玻璃、水体等)。
在这个基础上,如果谈到粒子特效,那也是半透明渲染的主战场,稍微用过游戏引擎的应该都不陌生,不过渲染原理上没有超出第二点的范畴。另外本文不会谈到折射或次表面散射,而这两者在渲染实际复杂的多层半透明结构中是常常需要考虑的。
1 透明像素裁剪
这里只介绍透明纹理最基础的一种应用——植物渲染。
(图中是一个常见的植物纹理)
植物的叶子作为自然界比较复杂的元素,如果每一片叶子都用建模以一些三角面来体现,在写实风格的游戏里肯定是一个巨量的数字。
这种场合就很适合透明纹理出场。以图中植物的贴图为例,可以看到图中有大量的透明像素(Alpha值为0)。
在渲染对应的片元时,对于这种纹理会进行透明度检测(AlphaTest),丢弃Alpha值在给定阈值以下的像素。这里的“丢弃”英文是discard,意思是不进行任何处理,即对缓冲区和屏幕像素都没有后续读写操作了。
(图中是经过AlphaTest丢弃透明像素后得到的结果)
这种处理得到的渲染结果,往往由于纹理精度问题而显得有锯齿感或颗粒感,一般要结合抗锯齿使用。另外,相同的像素处理方式也需要应用到对应的阴影pass中,使阴影与实体的轮廓一致。
顺便一提,使用AlphaTest会打破现在较新的图形硬件的Early-Z(预计算深度)优化,这里就不展开了。
2 半透明渲染基本原理
讲到半透明渲染,其实际渲染的结果等同于——使用一定的颜色混合规则,使半透明材质与其后的背景颜色混合得到“看起来是半透明”的最终颜色。
这段概括里引入了两个基本预设:
1)半透明是一种颜色混合——既有符合视觉直觉的混合,也有其它美术风格化上的混合方案。但反过来颜色混合远不止用来做半透明混合,还能提供各种图像处理效果。
2)透明的混合需要遵循从远到近渲染,因为颜色的前后会直接影响混合的结果。
这两点在后面的章节里还会展开论述。
下面的图片展示了渲染顺序错误的一种情况,还展示了半透明颜色混合的结果:
(渲染顺序错误时)
(正确从后向前渲染并混合时)
和不透明片元渲染比较起来,由于不能依赖深度缓冲,因此半透明渲染在远近排序或是顺序无关的渲染策略上都提出了新的课题。
3 半透明渲染的颜色
图形学上的颜色混合可以认为是将当前颜色和目标颜色进行混合,代入各种公式进行预算,得到最终的结果。因此混合模式可以理解成源色(及系数)、目标色(及系数)、公式这3者的各种组合。
最常用的有俗称Additive和AlphaBlend这2种归纳好的方案(即这两者都不是学术概念)。前者是一种常用的提高亮度的半透明渲染方案,主要用于粒子特效之类,最终颜色是当前颜色和背景像素颜色直接相加的;后者得到的颜色则是当前颜色和背景色的按一定规则的组合。(这里背景像素颜色指颜色缓冲中以渲染出的颜色)
Additive(图形软件里也被称为Linear Dodge)的公式是:
C¯result=Src+Dst (相当于前面一篇文章提到的人眼感知颜色的线性叠加)
传统意义上的AlphaBlend(图形软件里也被称为Normal混合)公式是(:
C¯result=C¯source∗Fsource+C¯destination∗Fdestination(F = Alpha时)
其中C就是颜色,F是混合系数,而最传统的混合系数就是Alpha值。GL里写法就是 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ,这里就会进行RGB和A通道统一规则的混合。
这里引用图文来说明一个基本的AlphaBlend计算例子,假设绿色Alpha值为0.6:
(图中行向量数字代表最终的RGBA值)
在此基础上还可以拆分RGB和A通道的混合规则,或者进一步引入其它的混合公式。例如还有一些不常见的如减法 C¯result=Src−Dst 和反减 C¯result=Dst -Src,往往在对比色相关的效果中有其作用;F乘数也可以有不同的系数,而不仅仅只是Alpha值;甚至有一些公式是分段函数的形式,或者是更复杂的指数或对数形式。
(这里列举了GL中的BlendFunc的一些参数)
在这个基础上把公式扩展,又能带来更广泛的一些混合模式——人们已经为一些主流的混合模式起了各种符合直觉(或反直觉)的名字,如果用过图形软件的话应该都不陌生。这些在游戏引擎中也可以通过Shader实现,很多也已经被引擎预集成了。
(这里不特别指出时透明度都是要经过混合的,例如T=S也不是直接赋值而是经过了AlphaBlend函数)
*个人觉得这些名词的中文翻译其实都挺抽象的,例如Multiply是正片叠底 、Screen是滤色——可能因为我不是学美术的。另外,上面公式对于两个颜色Alpha值都为1也能产生混合效果,图形软件中也有大量应用(但已经超出这里讨论的半透明混合的范畴了)。
(一图流展示各种混合结果)
4 半透明物体排序的难度
不透明物体的渲染一般考虑剔除即可,具体片元可以靠深度缓冲来实现正确渲染。而半透明由于需要混合,只比对锚点或是以包围盒做简单策略都无法解决物体有穿插的情况。
进一步能想到的就是比对三角面,以及比对片元像素——精度逐步变高,但性能消耗则是能做和基本不能做的区别了。后面章节会介绍现有的一些可行方案,基本都是一定的近似策略。
目前主流引擎的传统渲染管线里也不提供基于三角面或片元的排序比对方案,基本思路还是物体排序(可以一定程度上修正渲染队列Queue),并关闭深度写入只进行颜色混合。
按照这一策略进行前向渲染时,即便出现了多透明体重叠的情况,在关闭深度写入的情况下,至少能保证所有位于前方的透明体稳定通过深度测试,并保证能以某种混合呈现出来,不至于在摄像机变换时突显或消失。但是基于前面提到的,如果局部顺序不对得到的颜色其实是不对的。
在不进行定制化渲染的情况下,多个透明片穿插的情况其实渲染结果是不对的,在透明物体是凹面体时其内部结构的排序可能也是不对的。
(图中列举了半透明渲染中常见的一种无法通过整体排序做对的情况)
5 Screen-Door Transparency
玩过一些主机游戏的玩家应该对这个效果不陌生,因为这是一类把物体整个渲染出来并进行纹理遮罩处理的方案。
(图中展示了这一方案展示出的不同半透明效果)
这种透明感来自于像素渲染的间隔,即渲染的像素是完全不透明的,而不渲染的像素则透出背景色。可以认为是利用像素比较近产生的视觉混合感,并不是真的颜色混合;真实的颜色取舍类似AlphaTest,用或者丢弃。
这个方案的基础思想还是不透明渲染的那一套,且透明度只能是几个特定的值,并且在接近50%时才有最好的效果;同时整体的透明度必须是均匀的,即不能表现出基于局部Alpha值不同的混合效果。
如果需要多层的半透明混合,就需要在不同的层使用不同的遮罩模式(遮罩纹理或遮罩密度不同),且层数不能太多。
6 顺序无关半透明算法
在不透明渲染中,深度缓冲就是一个顺序无关的好方案;所以在做半透明渲染时,人们还是想以类似的思想来设计管线,付出额外绘制或计算次数的代价来避免做整体的精确排序。为此人们提出了顺序无关的半透明算法(Order-independent transparency OIT)的思想。
OIT思想在发展过程中主要有精确(Exact)和非精确(Approximate 词语原意是近似)两个方向。精确OIT最终还是要对每个半透明片元做排序,目前在算法上没什么突破,主要是通过硬件加速来优化;非精确OIT则不要求绝对的精确,这就为游戏引擎引入一些Trick提供了空间。
这里稍微展开介绍非精确OIT其中一种——深度剥离(Depth Peeling)。
这一方案的基本思想是先进行无关顺序的半透明片元绘制(这里可以理解成是pass而不是实际绘制),并将最浅的深度排入一个多层深度缓冲结构中;渲染时在基于这个多层深度缓冲进行从远到近的渲染,就能逐层正确混合了。
(图中展示了Nvidia官方文档中演示的不同层深度剥离效果)
这一方案的局限也很明显,就是层数不能是无限的,因此也不能保证百分百就是精确的。不过对于半透明物体有限的场合来说基本是足够了。后续人们还提出了双向深度剥离作为优化方案,用来提高计算效率——类似的双向优化思想其实图形学看多了会发现很多。
最后再列举一些其它的算法,例如Stochastic Transparency主要基于超采样思想, Adaptive Transparency 考虑减少被排序的片元数量等。这里就不逐一展开了,有兴趣可以去看看Wiki。
结语
查资料的时候发现了一个趣闻,就是世嘉的Dreamcast主机曾经在硬件设计上支持过OIT。印象中那时候主机游戏似乎不太多用半透明渲染,因此这一设计也不知道是不是太过超前了。
由于半透明渲染为了不丢失信息要逐层进行颜色混合,因此引擎中也有一个性能监测指标叫Overdraw——因为很多时候叠特别多层一起混合得到一个颜色其实往往也是不合理的,因此对于Overdraw特别多的场合往往需要进行针对性的优化(调整美术资源或引入LOD等)。
最后是一些资料链接:
混合模式Wiki
Screen-door transparency 的shaderToy
顺序无关半透明渲染Wiki
Nvidia的OIT文档,包含深度剥离