混合和透明物体渲染
之前一直没有很仔细的地思考过Blend这个概念,唯一的映像就是\(C_o = C_s*Alpha+C_d*(1-Alpha)\)。所以在软件渲染器中也只是理论上支持这个东西,但实际上却只做了深度测试。现在来仔细看一看这个玩意儿,也记录一下透明物体渲染的东西。
Blending
这部分主要参考DX11龙书Blending一章。
简单的说,Blending就是使当前颜色作为混合源\(C_s\)不要直接覆盖前面一个颜色\(C_d\),而是和前面的一个颜色混合起来,所以叫做混合。这个有助于渲染透明物体(当然这只是其中一种,只是我们目前最关注这一种而已)。当然对物要求也是有顺序的,所以在投入片元的时候要求渲染保序。
在D3D中混合方程如下:
其中\(C_s、C_d\)是参与混合的源颜色,和混合目标颜色,\(F_x\)则是混合函数。\(*\)是逐项乘符号,\(@\)则是任意运算符号。
在D3D11中,他可以使最大最小加减反减等等。
D3D11中对颜色的RGBA分开设置混合符号和混合函数,RGB为一个,A为一个。可以参考下列代码,这应该是配置Blending选项,然后再OM阶段进行混合。
D3D11_BLEND_DESC blendStateDescription;
ZeroMemory(&blendStateDescription, sizeof(D3D11_BLEND_DESC));
// Create an alpha enabled blend state description.
blendStateDescription.RenderTarget[0].BlendEnable = TRUE;
//blendStateDescription.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
blendStateDescription.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
blendStateDescription.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
blendStateDescription.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
blendStateDescription.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
blendStateDescription.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
blendStateDescription.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
blendStateDescription.RenderTarget[0].RenderTargetWriteMask = 0x0f;
g_env->d3d_device->CreateBlendState(&blendStateDescription, &m_alphaEnableBlendingState);
具体的用法不是重点就不说了,查文档就知道了。
一般来说,混合只管RGB值而不管A值,除非在back buffer中A有特别的用途。
透明物体渲染
Blending渲染
混合可以做很多事情,但是这里只关注透明物体渲染。公式就是我最开始写的公式。只是在使用公式混合之前要对他们全部按照由远到近排序,然后从后往前绘制,而且在绘制所有透明物体之前必须先绘制所有不透明物体。这种从后往前的方法叫做OVER Blending。
这种混合操作有各种稀奇古怪的优化,比如在D3D11Shader中使用clip函数等,这个函数可以导致指定的像素在结束shader之后直接跳过OM阶段省事不少,所以在alpha接近0的时候就全部discard掉。
Using a DISCARD instruction in most circumstances will break EarlyZ (more on this later). However, if you’re not writing to depth (which is usually the case when alpha blending is enabled) it’s okay to use it and still have EarlyZ depth testing!
还有一种就是使用一个低的分辨率(一般一半)来渲染目标做混合的。
另外还有一种叫做UNDER Blending的从前往后渲染的方法。这种方法把公式拆为
\(A_d\)初始化为1.0。
最后,需要把算出了的颜色和背景颜色\(C_{bg}\)混合:
OIT
传统的ODT透明物体渲染方法一般是先渲染不透明对象,然后对透明的对象按照深度排序,然后进行渲染。这种方法对于交叉物体的渲染是有问题的。为了处理更一般的透明物体渲染,就要使用OIT方法。
下面有几个实时方法:
depth peeling[1]
渲染正常视图,将这一层深度剥离,渲染下一层深度的视图,以此类推。渲染每一个深度的图像,然后把所有的深度渲染结果混合起来。
这是一个多pass算法,具体的pass数和场景最大的深度复杂度有关。
还有一种拓展是Dual Depth Peeling[2]。这种算法在一个pass内剥离最近和最远的两个深度,如果有中间深度就按照规定来处理。最后对于这两种剥离,分别使用OVER和UNDER的blending方法来进行blending。
Per-Pixel Linked Lists[3]
这种方法利用SM5.0(这算是一个限制吧)的structured buffers和atomic operations[4]来建立每个像素的链表,然后对链表进行深度排序,最后按照排序结果使用Ray Marching方法来进行渲染。
下图大概说明了这种算法:

Start Offset Buffer对应于每个像素,存放链表头。
Fragment and Link Buffer是屏幕的n倍大,每个fragment的辐射度,体积衰减系数,物体的反照度,朝向信息,摄像机距离等数据都会打包成一个Structed Buffer然后扔到Fragment and Link Buffer中,这些单独的structed buffer用一个成员储存Fragment and Link Buffer中的索引链接下一个structed buffer。具体的可以参考这个slider[5]。
排序原论文通过权衡考虑使用的插排。
这个算法的理论性能比Depth peeling高一些,因为后者要N个pass所有pass都要处理所有的fragment,而这个算法只要一个pass就可。但是,Fragment and Link Buffer的大小无法事先比较精确的控制,这可能会导致空间浪费或者溢出。
值得注意的是:这种类似的方法产生的数据(这里的链表)涵盖整个场景的信息,我们可以使用这些数据来做很多事,比如:体素化甚至光线跟踪。
Ref
【1】Interactive Order-Independent Transparency
【2】Order Independent Transparency with Dual Depth Peeling
【3】Order Independent Transparency with Per-Pixel Linked Lists
【4】https://msdn.microsoft.com/en-us/library/windows/desktop/ff476335(v=vs.85).aspx#Structured_Buffer
【5】Oit And Indirect Illumination Using Dx11 Linked Lists