时间:2024-05-04
陈瑞
(四川大学计算机学院,成都610065)
光线与半透明物体交互可以分成三个部分,其一是被表面直接反射或者进入内部以后再反射出表面,其二是进入了内部以后被吸收,其三是进入内部以后从另一侧射出。其中第三个部分也就是我们常说的透射现象。生活中常常会发生透射现象,尤其是在光线穿过物体比较薄的部分时,这一现象更为明显。例如阳光透过手指,我们可以看到手指边缘发亮泛红的现象。
图1 阳光透射过手指
透射现象对于增强虚拟场景的视觉真实感具有不小的贡献,如果我们对于模拟的半透明物体的光照渲染,不增加透射效果,那么半透明物体会失去玲珑剔透的感觉,变得类似于塑料这种比较粗糙的质地。因此对于透射现象的模拟一直是学者们大力研究的课题。包括基于扩散理论[1]、基于偶极子模型[2]、基于多极子模型[3]、基于方向偶极子模型[4]等方式,这些方式都是在离线的情况下,模拟光线在物体内部复杂的散射情况,并且得出最后的渲染结果。这些方式模拟出来的结果非常接近与现实结果,但是计算复杂,无法保证实时的效率。
为了满足实时交互的需求,通常会使用比较粗略的方式,把光线内部的散射情况给忽略掉,将其看成是非散射物体。这种情况下,光线的衰减就会与所穿过物体的厚度与物体的浓度成正比。因此,我们就可以使用比尔定律[5]来模拟透射效果。其中It代表的是透射光强度,Io代表的是入射光强度,c代表的是物体的浓度,而d代表的是物体的厚度。
根据透射公式可知,透射厚度d的计算是我们必须解决的问题。在实时应用中,我们会忽略掉光线在入射点以及出射点的折射与光线在物体内部的散射,因此,可以将光线在物体内部的轨迹看成是从入射点到出射点的线段。对于透射厚度的计算当前主要有两种方式,一是基于Translucent Shadow Map(TSM)[6],直接使用出射点在光源下的深度减去入射点在光源下的深度。这样计算的优点在于十分高效,但它的劣势在于,对于厚度的计算,仅仅适用于入射点和出射点之间完全没有空隙的情况。如果物体拥有凹形区域,以及物体与物体之间存在空隙,会导致这些空隙也被认为是物体内部的一部分,那么该方法计算出的透射距离有很大误差。
图2 TSM计算厚度误差
二是基于局部厚度贴图[7],这种算法是最高效的,因为计算出来的局部厚度贴图是与视线以及光线方向无关的,因此可以对要处理的模型进行预处理,在离线条件下计算这些模型的厚度贴图。计算的方式是利用了计算AO的思想,AO代表了物体表面的光线被遮挡的比例,如果一个表面的点,对它周围的世界空间的点进行随机采样,这些采样点落在物体内部的比例越高,那么这个点的AO效果就越强烈。同样地,如果我们对于一个物体内部的点,对它周围的世界空间的点进行随机采样,如果采样点落在物体外部的比例越高,那么就代表这个点周围的厚度就越薄。这可以通过反转法线以后再进行AO计算来实现。
如图3是计算出来的一个龙模型的局部厚度贴图。
图3 龙模型局部厚度贴图
但是这种算法有一个显而易见的缺点,那就是得到的厚度是与光线入射方向无关的,利用这种方法得到的厚度都是假设光线入射方向是沿着物体表面的法线反方向得到的。因此光源与物体相对位置的改变并不会带来透射结果的改变,这会给人造成非常不真实的感受。
本文结合在片元排序中经常使用的深度剥离技术来计算厚度,在牺牲一定效率的前提下,达到了效果上的大大提升,解决了上述两个算法的问题。
假设在理想的情况下,物体是闭合的模型,并忽略掉光线与物体表面相切的情况,一条光线从入射点到出射点一共经过物体的n个片元,这个n一定为偶数,如图4所示。
图4 光线穿过物体的n个片元
那么我们可以认为第1个和第2个片元之间的距离是光线经过的第一段物体厚度,第3个和第4个片元是的距离是光线经过的第二段物体厚度,这样以此类推得到厚度d的结果如公式(2)所示。
基于这种思路,只要我们可以拿到光线穿过物体的所有片元,并且将它们按照到光源的距离排序以后,再使用公式(2)即可得到光线穿过物体的厚度。得到每一条光线下的所有片元可以通过使用深度剥离算法来达到目的。
渲染管线中的深度检测能够自动记录离观察视点最近的片元深度并且保存到深度缓冲中,但是在整个场景中,还会有离视点第二近的片元,第三近的片元,仅仅使用深度检测是不能够拿到这些片元的深度并且进行排序的。这个时候就要利用discard操作,对于启用了这个操作的片元,会被直接丢弃掉,被丢弃的片元也不会将自己的深度保存到深度缓冲中。那么我们只要把离视点最近的片元都给discard掉,深度缓冲中就能够记录离视点第二近的片元,如果将第一近和第二近的片元都discard掉,就能够记录离视点第三近的片元深度了。而我们是否discard片元的判断依据就是这个片元的深度是否小于等于之前深度缓冲中的深度,如果满足,就代表这个片元已经被记录过了。这样以此类推,就可以得到每一条光线下的所有片元,并且能够按照记录的次序自动排序。总结深度剥离的流程如下所示:
(1)从光源视角下将场景绘制一遍,自动记录场景中深度最小的片元到深度缓冲中,之后将深度缓冲中的值拷贝到第一层深度纹理数组中。
(2)再次绘制场景,如果片元的深度值小于等于对应位置的上一层深度纹理数组中的深度值,就丢弃掉片元,场景绘制完毕后,就能够记录下一层的最小深度到深度缓冲中,再将其拷贝到下一层深度纹理数组中。
(3)重复(2)的操作,直到场景片元被剥离完为止。
这样,我们就能够拿到每一条光线下的所有片元。
在拥有了排序好的片元深度纹理数组以后,我们使用延迟渲染中的技术,再将整个场景渲染一遍,并且将对于视点可见的世界坐标保存到Gbuffer中。再在下一个pass中,从Gbuffer中取得视点可见的世界坐标,这些世界坐标可以认为是每一条光线与物体的最后一个交点,我们接下来要做的就是拿到这些点之前光线与物体的所有交点,这就可以利用到之前求出来的片元深度纹理数组。
我们将Gbuffer中的世界坐标变换到光源空间后,使用这些变换后的坐标的xy分量进行简单的乘法和加法操作后得到对应的uv坐标,再利用这个uv坐标从纹理数组中依次读取这条光线上的片元深度,直到纹理数组中的片元深度大于等于了当前Gbuffer中的片元深度为止。这样我们就能够取到从光线入射到出射的所有排序好的片元深度了,因为深度缓冲中自动生成的深度是非线性化的,所以在计算厚度之前,还需要将这些深度给变换回线性空间中,最后将利用公式(2)求出厚度。如果取到的片元数目是奇数,那么就代表着最后一个点是光线的入射点,这个点不需要参与到厚度计算中。否则,代表最后一个点是光线的出射点,需要参与到厚度计算中。
最后再将厚度带到公式(1)中,就可以得到衰减后的光照强度,也就是我们的透射结果。可以将这个渲染流程总结为下面的步骤:
(1)使用深度剥离算法,在光源空间将场景绘制n遍,保存所有的片元在光源下的深度到一个大小为n的纹理数组中。
(2)在摄像机视角下将场景绘制一遍,保存摄像机可见的世界坐标到Gbuffer中。
(3)从Gbuffer中得到可见坐标并变换到光源空间后,依次从深度纹理数组中取出对应的在可见坐标之前的所有片元深度,根据这些深度计算得到光线穿过的物体厚度。
(4)利用得到的厚度,计算出透射颜色。
深度剥离需要将场景绘制很多个pass,因此效率比较慢,在深度剥离的基础上,Bavoil和Myers[8]提出了双重深度剥离,即每次剥离两个深度剥离层(最近的和剩余的最远的),从而将渲染次数减半。
在深度剥离中,我们比较的对象是从深度缓冲中拷贝的深度纹理,这个纹理只能保存当前未被discard的片段中的最小值,但是我们现在需要保存当前片段的最小值和最大值,因此需要一张拥有两个32位浮点数通道的纹理以及纹理数组。然后在深度剥离中,每一个pass都主要保存当前离视点最近的片元,但是在双重深度剥离中,就需要记录最近和最远的片元,这可以通过设置blendEquation为max来达到目的。例如,我们将场景绘制一次,在纹理的R通道保存片元深度的负数,在G通道保存片元深度,那么通过设置max操作,渲染管线始终会让RG通道都保留最大的值,那么在R通道就会保存最小片元深度的负数,而在G通道就会保存最大的片元深度了。双重深度剥离的流程如下:
(1)从光源视角下将场景绘制一遍,利用混合公式max,让双通道纹理的R分量保存最小的片元深度的负数,让G分量保存最大片元深度。在绘制完毕以后,将这张纹理复制到第一层双通道的纹理数组中。
(2)再次绘制场景,如果片元的深度比上一层纹理数组中R通道对应位置中记录的深度小或者比G通道记录的深度大,就丢掉片元,否则,就将片元的深度的负数保存到另一个双通道纹理的R通道,深度保存到该纹理的G通道。场景绘制完毕后,利用混合公式max,就可以记录下一层的最小深度和最大深度。在绘制完毕以后,将这张纹理复制到下一层的双通道的纹理数组中。
(3)重复步骤(2),直到场景片元被剥离完为止。
这样我们就能够将场景绘制的复杂度从n降低到n/2。为了保证我们在厚度计算中使用的是与深度剥离同样的算法,因此我们再添加一个额外的数据处理的步骤,将RG双通道的深度纹理数组变换为单通道的深度纹理数组。
因为这个变换过程只是对纹理操作,而不同的uv坐标下的纹理的变换过程不会互相影响,因此可以使用一个compute shader进行多线程的变换计算。变换的过程也很简单,读取n次双重纹理数组的值,前n/2次读取r通道,如果r通道的值不为初始值-1.0f,那么就代表它记录了片元深度,就将其取反以后保存到对应位置的单通道深度纹理数组中。后n/2次读取g通道,如果g通道的值不为初始值0.0f,那么就代表它记录了片元深度,就将它的值保存到对应位置的单通道深度纹理数组中。
本文的实验分为3个部分,第一个部分为比较深度剥离算法与TSM的误差率比较实验。第二个部分为两种方法实际渲染出来的效果对比,第三个部分为双重深度剥离对于深度剥离的效率改进实验。
实验环境:渲染的硬件是AMD 3600x(6 core)+NVIDIA RTX 1660 SUPER(索泰)。渲染的分辨率为1920×1080。
实验场景:使用的场景如下:分别为绿红蓝三个球,分别固定为(0.0,0.2,4.0),(0.0,0.2,3.2),(0.0,0.2,2.4),真实的光源为图中白色的球体。
图5 误差率实验场景
因为这个测试场景可以使用函数显示表达,那么就可以生成基本无误的符号距离场信息,根据距离场信息做ray marching,以此生成的厚度信息可以看成是完全准确的,将这样生成的厚度信息作为无偏量。
表1 误差率实验结果
场景为一只三角形数为514372,顶点数为1543116的龙模型。
图6 实验模型
侧视的渲染效果:下图从左到右依次为TSM、depth peeling。
图7 侧视的效果实验对比
俯视的渲染效果:下图从左到右依次为TSM、depth peeling。
图8 俯视的效果实验对比
最后是双重深度剥离对于深度剥离的效率改进实验。
表2
本文给出了基于深度剥离算法的厚度计算方法,在传统的厚度计算方法的基础上大大提高了厚度计算的准确率与透射的渲染效果,并且使用了双重深度剥离改进了深度剥离算法计算厚度的效率,并且都拥有相应的实验证明,在实时渲染领域具有很大的应用价值。
但是因为基于深度剥离算法来计算厚度需要将场景绘制很多个pass,这一点在效率上会带来一定的劣势。如何加速场景的剥离是未来研究的方向。
我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自各大过期杂志,内容仅供学习参考,不准确地方联系删除处理!