当前位置: 华文星空 > 知识

[GPU Pro5] 光照篇

2021-05-18知识

一、基于物理的面积光源

这里介绍【杀戮地带:暗影坠落】中的基于物理的灯光系统,支持多种能以3D或2D函数表示的形状的面积光源。通常BRDF只支持点光源,对于一个光源只接收来自一个方向的光照。这里对提供给BRDF的光照信息进行重新建模,对面光源上所有点进行重要性采样从而计算辐射度积分。光照信息的单位都基于物理,比如光照强度单位是流明,距离单位是米等。

首先回顾光照模型

假设光源有均匀的光通量分布即Li为常数,并令BRDF由兰伯特漫反射模型和Cook-Terrance的微表面镜面模型组成,则

所以需要做的是计算两个积分

  • 近似漫反射积分
    通过重要性采样进行蒙特卡洛积分,即定义输入范围然后根据概率分布随机生成输入再进行函数计算并积分,其中重要性采样可以通过空间启发式或者一个额外初始蒙特卡洛的低采样pass来估计重要性。这里有两个假设,假设光源形状有统一的法线以及dA不为积分点而改变,则dAcosθo只需计算一次,此时积分函数只与θi和r有关。也就是说此时需要关注函数cosθi/(r^2),该函数的最大和最小值在光源形状的边界,因此必然存在一点用它来积分会使得误差最小,但计算该点十分昂贵,所以这里只是在最大值附近寻找近似点。

  • 为了找到该点,首先找到cosθi最大值:沿法线方向向光源平面发射射线,如果交点p‘不在光源内则离交点最近的有效光源边缘点为cosθi最大点pc;然后找到r^2的最小值,将表面点投影到光源平面上,如果投影点p’‘不在光源内则离它最近的有效光源边缘点为r^2最小点pr。则最重要采样在pc和pr之间,这里将pc和pr的半程向量的交点作为该采样点pd。另一种更简便快速但误差更大的方法是不计算pc和pr而是直接将p’和p‘’的半程向量的交点作为pd。

  • 近似镜面反射积分
    通过重要性采样来近似,即通过概率分布函数PDF来找到重要性采样的镜面锥体从而找到积分最重要的点和区域。镜面锥体围绕视线的反射向量r,它的顶角α由光泽度g计算: α (g) = 2\sqrt\frac2{g + 2}. 找到锥体和面积光源相交的部分As从而得到积分的范围,然后以类似上一节的方式找到一个最重要的点来近似表示这个积分,这里使用As的几何中心psc,由psc计算镜面反射项,再将As面积作为立体角微分来对它进行归一化得到结果
  • 之前都是假设辐射率Li为常数,如果改变这个假设而是假设光照强度I为常数,即光通量在立体角上分布恒定而不再是在立体角和球面面积上都分布恒定,此时可以对光源上变化的Li进行预积分再根据光源面积进行归一化得到漫反射项因子和镜面反射项因子,然后在像之前介绍的近似计算漫反射项和镜面反射项的的采样点时各自乘以对应因子来校正结果

    二、使用Epipolar Sampling实现的大气散射光

    大气的散射光效果包括蓝天、日落黄昏的多彩地平线以及体积光等,这里介绍一种高性能的基于物理的方法来实现,该方法结合了两个关键技术:Epipolar Sampling(用来减少ray marching的采样数)和1D的最小/最大二叉树(用来加速ray marching)。该方法的优势包括预计算很少、支持昼夜变化、支持相机移动、可以和级联阴影整合。

    主要的算法步骤为:

    1. 生成epipolar采样:为屏幕上的每条epipolar线计算入点和出点,并分布采样;
    2. 选择ray marching的采样:沿着epipolar线稀疏定位初始的ray marching采样,为每个采样计算粗糙的内散射,在变化明显的地方增加额外采样;
    3. 为每个epipolar切片构建1D的最小/最大二叉树
    4. 执行ray marching
    5. 从ray marching的采样中为剩下的采样插值生成内散射的radiance
    6. 将散射从epipolar坐标系转换到屏幕空间,并和衰减的back buffer结合

    其中有以下技术要点:

  • 光照传输和散射积分
  • 光照和粒子的交互分为散射和吸收两种类型,散射即改变光线方向,其中散射后光线射向相机的话则为内散射。而空气分为空气分子和气溶胶两种粒子,由空气分子造成的散射称为瑞利散射,由气溶胶造成的散射称为米氏散射,之前在大气散射的章节中有过详细介绍

  • epipolar采样
  • epipolar采样即在屏幕上沿着对极线来分布采样从而进行ray marching,对极线是光源和屏幕边缘的等距点的连线,然后在对极线上均匀分布采样点,并每隔若干个点取一个点作为ray marching的初始采样点。

  • 1D最小/最大二叉树
  • 在选择采样后进行ray marching时,对于每个采样发射射线并转换到光照空间然后在shadow map中执行ray marching从而考虑阴影,但逐个遍历阴影贴图纹素代价较大,所以使用二叉树算法。

    将光线和当前对极线形成的平面称为对极切片,而该切片和光照投影平面会有一条相交的线,沿着这条线采样阴影贴图会得到一张一维的高度图,这张高度图对于该切片上所有的相机射线都相同,所以只需检查视线中的当前点是否在高度图上或下来判断是否在阴影中。此时使用二叉树方法,即当前射线端点深度的最大值如果小于树节点的最小深度值,则该射线线段被完全照亮(图2.6的AB),如果最小值大于最大值则完全在阴影中(图2.6的CD)

    三、体积光效果

    这里介绍【杀戮地带:暗影坠落】在延迟管线中实现体积光效果,解决的挑战包括如何方便艺术家控制效果和如何处理透明物体。

    主要算法是为每个光源渲染一个形状来表示光照体积(点光源为球形,聚光灯为锥形,太阳光则为整个屏幕),然后计算视线和光照体积的相交线段,接下来在相交线段上进行ray marching,对于ray marching的每个采样都把它当作朝向光源的白色漫射表面来计算光照(为了节省计算关闭阴影过滤),根据ray march的步进大小和散射因子来对光照结果进行缩放,最后将所有ray march的采样结果叠加到一起渲染到屏幕上则为体积光效果。其中对于太阳光的ray marching由于是全屏所以是在级联上进行。

    散射因子用来描述光在所有方向上的散射数量,用一个相位方程来表示散射光在角度上的分布,从而决定进入相机的散射光,这里用来模拟米氏散射

    此时需要解决两个问题:

  • 控制散射数量:此时散射光效果过于统一和静态,可以使用粒子效果来动态控制散射数量,将粒子渲染到3D纹理中,然后再运行时查找纹理
  • 透明物体的处理:如果简单对透明物体alpha混合的话则不能和体积光正常混合,处理透明物体的方法类似于上面处理散射数量的方法,但不是在深度层存储散射数量,而是在另一个3D纹理的深度层中存储累计体积光密度,然后在渲染透明物体时采样该3D纹理来的值物体前面有多少体积光,从而得到透明物体上的体积光效果
  • 其中为了减少计算代价,使用了以下手段进行技术优化。

  • 低分辨率渲染:用一个半分辨率的buffer进行渲染然后进行双边升采样
  • 抖动ray marching:减少ray march的step数,为了减少带状走样,使用一个抖动图案来对ray march的采样位置进行偏移,来使得ray march采样分布不均匀。然后使用双边高斯模糊来使得结果平滑。
  • 四、Hi-Z屏幕空间的锥体追踪的反射

    这里介绍一种动态场景中的新颖的计算反射方法,使用了Hi-Z技术和屏幕空间的锥体追踪技术。Hi-Z即Hierarchical-Z缓存,是屏幕空间对齐的四边形树,存储在Hi-Z纹理的MIP通道中,用来跳过空白区域来快速找到交点从而加速光线追踪。屏幕空间的锥体追踪技术用来近似粗糙表面来产生模糊的反射。此外,还对该算法进行了一些优化,包括时间性过滤(temporal filtering),即利用之前帧的结果来稳定当前帧的输出并模拟多次光线弹射。

    主要算法包括五个步骤:1. Hi-Z pass;2. 预积分pass;3. 光线追踪pass;4. 预卷积pass;5. 锥体追踪pass。具体如下:

  • Hi-Z Pass
  • Hi-Z缓存是存储初始z缓存的四个相邻像素中最大或最小值的更小缓存,这里使用最小值。该缓存通过在ray marching时切换不同的层次等级来跳过空白区域从而快速查找到交点。这个pass则是用来对深度缓存或者Z-buffer构建Hi-Z缓存。

  • 预积分pass
  • 这个pass计算用于锥体追踪的场景可见性。将构建的Hi-Z缓存用作输入,已知mip0等级的每个深度像素是100%的可见性,往下遍历缓存时更粗糙等级的缓存有着更小的可见性Visibility(n)≤Visibility(n−1),这里的目标是计算粗糙等级的可见性,通过计算在四个像素的最小和最大深度形成的体积中有多少几何体的比例得到,如下图中MIP1:(1-0.5)*100%=50%,MIP2:(1-0.25)*50%=37.5%。该缓存用于在之后锥体追踪时ray marching采样可见性并累积,累积到100%时则停止追踪。

  • 光线追踪pass
  • 在屏幕空间的深度缓存值是线性的,所以在屏幕空间进行光线追踪。用以下方式表示射线

    对反射射线在Hi-Z缓存中一边ray marching一边遍历不同的层次来快速得到交点,即不相交时在更粗糙的等级上步进,相交时则在更细腻的等级上查找。

  • 预卷积pass
  • 对初始场景颜色缓存进行不同的模糊,来得到不同粗糙度对应的反射。

  • 锥体追踪pass
  • 在光线追踪pass完成后运行,光线追踪pass产生specular的完美反射,而这个pass用于产生glossy反射。在屏幕空间构建一个和反射光线分布角度对应的锥体,通过锥体的横切圆来采样预卷积的缓存来近似反射光线的积分。具体是先测试锥体是否和Hi-Z相交,相交的话则决定有多少相交并和之前预积分计算的可见性相乘,不断向下遍历Hi-Z并累加采样结果直到可见性到达100%,由此得到的颜色结果为反射结果。

    结果如下:

    五、TressFX中的头发渲染

    这里介绍头发如何按股来渲染,主要的处理包括几何扩张(即将头发从线条几何扩张为网格)、光照和阴影(包括头发的自阴影)、抗锯齿、透明度处理(对逐像素的链表填充并排序来处理)以及写入头发深度(方便整合其它透明物体和景深效果等)。具体如下:

  • 几何扩张
  • 一开始头发由线段组成的股来表示,线段由连续的顶点表示,为了使其光栅化表示,需要在顶点着色器中将线段转化为两个三角形组成的方格,此时分为两步来实现。第一步是在世界空间对线段沿着头发半径扩张,形成和视截面对齐的类似公告牌的方格;第二步是在屏幕空间将头发方格投影后继续扩张√2/2 ≈ 0.71个像素来保证一根头发丝最少占一个像素

  • 光照
  • diffuse计算为K_d*sin(t, l),其中t为切线方向,l为光照方向。而specular计算为

    但实际上头发有两层高光,基础高光向发尾偏移并且主要受灯光颜色影响,第二层高光向发根偏移并且受灯光颜色和头发颜色影响,偏移通过将反射方向靠近或远离切线方向来实现。

  • 阴影和自阴影
  • 自阴影通过存储头发深度的阴影贴图来近似计算如下,其中depth range是要着色的头发片元距阴影贴图中对应深度的距离

  • 抗锯齿
  • 抗锯齿的策略是计算屏幕像素被发丝覆盖的比例,即覆盖率,使用覆盖率来修改头发的alpha值。在这里使用的是一种图像空间的方法GPAA(geometric post-process antialiasing),即根据在多边形边缘的像素和实际边缘的距离来评估覆盖率

  • 透明度
  • 屏幕上的一个像素可以对应多层半透明的头发,此时可以逐像素生成链表来处理,分为两个pass来实现。第一个pass是A-Buffer填充,生成包含所有头发片元的未排序链表,每个链表节点包含颜色、深度和下个节点指针;第二个pass是排序和绘制pass,即遍历所有链表进行排序并根据排序结果对头发进行混合。

    六、电线抗锯齿

    这里介绍电线或其他长且细的物体的抗锯齿方法。由于电线宽度在屏幕上可能小于一个像素,所以显示结果可能闪烁或不连续。解决的思路是保证电线的宽度大于等于一个像素然后使用MSAA来解决锯齿。方案是运行时定义电线的半径(需要保证半径大于一个像素对应的半径),然后根据半径将顶点沿着法线进行位移,接下来通过计算覆盖率然后进行alpha混合来修正结果。

    但此时只是消除了几何走样,还需要解决着色走样。着色走样由一个像素对应的电线部分可能有多个法线方向造成。最简单的方法是使用半兰伯特模型的光照模型来产生最少的走样,也可以在半兰伯特模型和其他光照模型之间混合,还有种方法是将电线上所有法线都朝向相机