以前一直没有很仔细的地思考过Blend这个概念,惟一的映像就是\(C_o = C_s*Alpha+C_d*(1-Alpha)\)。因此在软件渲染器中也只是理论上支持这个东西,但实际上却只作了深度测试。如今来仔细看一看这个玩意儿,也记录一下透明物体渲染的东西。算法
这部分主要参考DX11龙书Blending一章。
简单的说,Blending就是使当前颜色做为混合源\(C_s\)不要直接覆盖前面一个颜色\(C_d\),而是和前面的一个颜色混合起来,因此叫作混合。这个有助于渲染透明物体(固然这只是其中一种,只是咱们目前最关注这一种而已)。固然对物要求也是有顺序的,因此在投入片元的时候要求渲染保序。
在D3D中混合方程以下:
\[C=C_s*F_s @ C_d*F_d\]
其中\(C_s、C_d\)是参与混合的源颜色,和混合目标颜色,\(F_x\)则是混合函数。\(*\)是逐项乘符号,\(@\)则是任意运算符号。
在D3D11中,他可使最大最小加减反减等等。
D3D11中对颜色的RGBA分开设置混合符号和混合函数,RGB为一个,A为一个。能够参考下列代码,这应该是配置Blending选项,而后再OM阶段进行混合。windows
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);
具体的用法不是重点就不说了,查文档就知道了。ide
通常来讲,混合只管RGB值而无论A值,除非在back buffer中A有特别的用途。函数
混合能够作不少事情,可是这里只关注透明物体渲染。公式就是我最开始写的公式。只是在使用公式混合以前要对他们所有按照由远到近排序,而后从后往前绘制,并且在绘制全部透明物体以前必须先绘制全部不透明物体。这种从后往前的方法叫作OVER Blending。
这种混合操做有各类稀奇古怪的优化,好比在D3D11Shader中使用clip函数等,这个函数能够致使指定的像素在结束shader以后直接跳过OM阶段省事很多,因此在alpha接近0的时候就所有discard掉。post
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的从前日后渲染的方法。这种方法把公式拆为
\[C_d = A_d(A_s*C_s)+C_d\]
\[A_d = 0 + (1-A_s)A_d\]
\(A_d\)初始化为1.0。
最后,须要把算出了的颜色和背景颜色\(C_{bg}\)混合:
\[C_d = A_d*C_{bg}+C_d\]性能
传统的ODT透明物体渲染方法通常是先渲染不透明对象,而后对透明的对象按照深度排序,而后进行渲染。这种方法对于交叉物体的渲染是有问题的。为了处理更通常的透明物体渲染,就要使用OIT方法。
下面有几个实时方法:测试
渲染正常视图,将这一层深度剥离,渲染下一层深度的视图,以此类推。渲染每个深度的图像,而后把全部的深度渲染结果混合起来。
这是一个多pass算法,具体的pass数和场景最大的深度复杂度有关。
还有一种拓展是Dual Depth Peeling[2]。这种算法在一个pass内剥离最近和最远的两个深度,若是有中间深度就按照规定来处理。最后对于这两种剥离,分别使用OVER和UNDER的blending方法来进行blending。优化
这种方法利用SM5.0(这算是一个限制吧)的structured buffers和atomic operations[4]来创建每一个像素的链表,而后对链表进行深度排序,最后按照排序结果使用Ray Marching方法来进行渲染。
下图大概说明了这种算法:
this
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的大小没法事先比较精确的控制,这可能会致使空间浪费或者溢出。
值得注意的是:这种相似的方法产生的数据(这里的链表)涵盖整个场景的信息,咱们可使用这些数据来作不少事,好比:体素化甚至光线跟踪。atom
【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