孟蓉蓉
(视觉合成图形图像技术国防重点学科实验室,四川大学,成都 610065)
现今,在电影和游戏软件领域,反射是很重要的一项视觉效果。使用光线追踪技术的全局光照算法结合双向反射分布函数(Bidirectional Reflectance Distribu⁃tion Function,BRDF)可以在物体表面产生真实的反射效果。但是相关算法的代价相当昂贵,因为这些算法需要在光线追踪步骤花费大量的计算资源。
游戏产业中,用来生成实时反射效果最常见的算法是环境映射。在理想的反射表面它可以模拟貌似真实的反射效果,但是它无法生成自反射效果。环境映射算法另一个显著的缺点就是在动态场景里,它会降低渲染效率,因为动态更新每一帧的环境贴图代价较大。
近来,很多 SSR(Screen-Space Reflections)算法被提出,这些方法基于屏幕空间而非基于世界坐标系。它从相机位置出发,发射一条入射光线到屏幕上的任意一个像素点,根据法线计算出出射光线,用光线追踪技术计算出射光线和场景的交点,Hierarchical-Z算法可以用来加速求交的过程。SSR算法的主要问题在于物理缓冲区中的信息有限,无法处理反射光线与背面场景相交的问题。
本文提出一个改进的SSR算法,这个算法用来解决原有算法的背面丢失问题。解决这个问题的主要方法是扩充G-Buffer(Geometry Buffer)信息。原有算法的G-Buffer只有场景表面的信息,导致出射光线和场景背面求交时交点丢失。我们使用链表的方式动态对物理缓冲区进行扩充,将背面信息以链表的方式进行存储。当求交点在场景背面时,我们会查找链表,使得背面交点信息被补全。
屏幕空间反射算法是基于图像的算法,它从当前视点出发在G-Buffer中做光线跟踪,求到实时的反射结果。屏幕空间反射算法存在一下几点问题:
(1)对于几何体不可见的部分,我们没有办法计算它在屏幕中的反射结果,因为反射结果是基于不完整的场景信息。如图1所示,从视点出发发射两条光线,求反射光线与场景的交点,a点和b点的结果分别是a'和b'点,因为b'对视点来说不可见,所以b处的反射值为空。
(2)场景中的信息不完整可能导致反射错误。如图2所示,红色区域被遮挡,处于视点不可见的范围内,从视点出发计算a点和b的反射点,当到达红色区域时,SSR算法进入一个进退两难的境地,如果结束光线追踪,那么a点和b点的反射结果为空,如果继续光线追踪,那么反射光线与场景进行求交的结果是a''和b',显然a''不是a的结果,a的正确结果应该是a'。不管光线追踪是否继续,SSR算法都会存在错误结果。
图1
图2
本文算法将G-Buffer用链表的方式进行扩充。算法步骤如下:
(1)渲染整个场景,将处于同一像素的片元存放到一个G-Buffer链表中,再对每条链表中的片元按照深度进行排序;
(2)对像素进行遍历,通过光线追踪计算出每个像素的反射值,将反射值存放到纹理中;
(3)将反射值与场景原有的颜色混合。
本文算法将G-Buffer构建成单向链表的结构,链表访问方式为先进后出模式。在这个链表结构中,我们要在节点中存放上一个节点的索引,同时我们需要存放片元的颜色信息。因为后续光线追踪步骤中需要用到深度值,所以我们要存放每个片元的深度值。场景中背面求交的比例占整个场景的比例为10%到20%,所以链表第二层被查找的概率也是10%到20%,以此类推,链表中第三层被查找的概率微小,所以链表最大长度为3已经能够满足需求。通过上述描述,我们构建的链表结构如图3所示。
图3
G-Buffer中的内存因为链表结构被增加,为了减少内存和带宽的开销,我们压缩链表的内存,整体内存压缩率为50%左右。每个节点中,Index为整数,Color为4个浮点数,Depth为一个浮点数,在32位系统中,每个链表节点占24个字节。HLSL和GLSL本身都支持字节压缩,以GLSL为例,GLSL中的packUnorm4x8函数支持将四个浮点型数压缩为一个无符号型整数,也就是说可以将16个字节的内存压缩为4字节。压缩以后,每个链表节点占12个字节。
在后续处理过程中,我们需要找到每个像素中链表的起始位置,所以我们需要一个辅助的纹理存放每个链表头结点的索引值。关于链表存储方式,我们采用如下方式,我们分配一块连续的物理内存存放链表数据,每个节点在物理内存中的偏移量为链表的节点索引。为了保证节点索引值的唯一性,我们使用GLSL中的Atomic Counter,这个缓存对象存储一个unsigned int的变量值,我们每处理一个片元,缓存对象自增加1,我们把缓存对象的当前值作为链表的索引值。借助这个索引值,我们可以将节点数据存入到对应的物理内存中。
因为在光线追踪步骤,我们需要遍历链表去找反射点,所以我们需要对链表按照深度进行排序,方便以后对链表的遍历。
我们增加了G-Buffer的内存,但是并没有破坏GBuffer的结构,所以在我们的算法中仍然可以使用Hi-Z加速结构。在一般的SSR算法中,我们会对场景的二维深度值进行处理,构建Hi-Z算法所需要的层级深度图,供光线追踪时加速求交使用。在本文算法中,我们对G-Buffer进行扩充,由二维数据变成了三维数据,深度图因此也变成了三维深度图。为了方便起见,我们对G-Buffers中的最靠近视点的片元深度构建层级结构,不考虑链表中其他的深度值。因为在光线追踪步骤,大部分情况是处理可见场景的反射,所以用Hi-Z加速可见场景的求交速度是很有必要的。用最靠近视点的片元深度生成层级图,不会破坏原来的加速结构,也给链表求交提供了额外的空间。
我们从视点出发,发射一条入射光线到屏幕上的某一像素点,根据此像素点的法线求出反射光线的方向。当前像素点为反射光线的起始点,光线按照反射光线的方向前进。这个过程,我们会使用Hi-Z算法进行加速。如果我们在可见场景中找到反射光线与场景的相交点,那么求交过程结束。如果我们检测到交点在背面,我们会对当前链表进行遍历。如果当前链表中有满足条件的片元,我们就视为找到反射点,否则光线跟踪步骤会继续前进,对后续的链表进行排查,直到寻找到合适的值。
我们将每个像素的反射结果放到一个纹理里,在光线追踪结束以后,我们用反射结果贴图与场景原来的贴图做叠加。因为本文算法计算的是实时反射结果,所以我们只用一条反射光线做光线追踪以节约计算成本。此外,我们计算反射值的步骤是在片元着色器阶段,起点为片元坐标。本算法的这两个特点决定了我们得到的反射值是离散的数据,在最终结果处理的时候会在屏幕上产生噪点,影响最后效果。所以我们得到反射值贴图以后,我们会对反射值做高斯模糊,让反射值贴图的噪声减少。
我们用本文算法和原有的SSR算法对比,光线追踪步骤中的步长和迭代次数一致,生成出来的效果对比如图4所示。图4左边为原有的SSR算法的效果,从图中可以看到,原有的SSR算法因为背面信息缺失,导致该算法在处理背面反射时中失去作用。红色圈中的部分应该反射鼎的底部,但是原有的算法没有办法得到底部的信息。图4右边为本文算法,因为链表结构补全了一部分缺失的信息,我们在求反射效果时能够通过链表得到正确的反射结果。
图4
SSR算法因为其效率高效,在很多追求效率的软件和游戏中有广泛的应用。但是由于其本身算法的局限性,处理效果会有瑕疵。本文算法针对原有算法信息不足导致反射结果不完整的问题,提出解决方案。通过动态链表对数据进行扩充。同时我们考虑到内存开销,对数据进行压缩。但是本文算法在光线追踪步骤,因为数据量的增大,求交时间会增加。未来工作会进一步优化反射求交的效率,让算法整体效率提升。
[1]Greene,Ned.Environment Mapping and Other Applications of World Projections[N].IEEE Computer Graphics and Applications:IEEE,1986:21-29.
[2]Stachowiak,Tomasz.Stochastic Screen-Space Reflections[R].SIGGRAPH 2015 Advances in Real-time Rendering in Games Course,2015.
[3]Uludag,Yasin.Hi-Z Screen-Space Cone-Traced Reflections[M].GPU Pro 5,2014:149-192.
[4]Thibieroz,Nicolas.Order-Independent Transparency Using Per-Pixel Linked Lists[M].GPU Pro,2011:409-431.
[5]Umenhoffer,Tamas and Patow,Gustavo,Szirmay-Kalos,Laszlo.Robust Multiple Specular Reflections and Refractions[M].GPU Gems,2007:387-407.
[6]王晨昊,汤晓安,孙即祥,马伯宁.带有位置修正的环境映射[J].中国图象图形学报,2012(03).