[笔记]OpenGL

您所在的位置:网站首页 数学中的镜面反射 [笔记]OpenGL

[笔记]OpenGL

2024-07-15 23:40| 来源: 网络整理| 查看: 265

镜面反射IBL

        在上一节教程中,我们预计算了辐照度图作为光照的间接漫反射部分,以将 PBR 与基于图像的照明相结合。

        在本教程中,我们将重点关注反射方程的镜面部分:

        你会注意到 Cook-Torrance 镜面部分在整个积分上不是常数,不仅受入射光方向影响,还受视角影响。

        如果试图解算所有入射光方向加所有可能的视角方向的积分,二者组合数会极其庞大,实时计算太昂贵。

        我们可以预计算镜面部分的卷积,这种方案被称为分割求和近似法(split sum approXimation)。

        分割求和近似将方程的镜面部分分割成两个独立的部分,然后在 PBR 着色器中求和。

        之前能进行预处理,是因为积分仅依赖于ωi,但这一次,积分不仅仅取决于ωi,还依赖ωo:

        在实时状态下,对每种可能的ωi和ωo的组合预计算该积分是不可行的。 

         Epic Games 的分割求和近似法将预计算分成两个单独的部分求解,再将两部分组合起来得到后文给出的预计算结果。

        卷积的第一部分被称为预滤波环境贴图,它类似于辐照度图,是预先计算的环境卷积贴图,但这次考虑了粗糙度。

        对于卷积的每个粗糙度级别,我们将按顺序把模糊后的结果存储在预滤波贴图的 mipmap 中。例如,预过滤的环境贴图在其 5 个 mipmap 级别中存储 5 个不同粗糙度值的预卷积结果,如下图所示:(说实话,这应该不是卷积结果图)

        我们使用 Cook-Torrance BRDF 的法线分布函数(NDF)生成采样向量及其散射强度,该函数将法线和视角方向作为输入。

        由于我们在卷积环境贴图时事先不知道视角方向,因此 Epic Games 假设视角方向——也就是镜面反射方向——总是等于输出采样方向ωo。

        以作进一步近似。翻译成代码如下:

        没有看懂没关系,接下来一步一步拆解做法。

预滤波HDR环境贴图

        预滤波环境贴图的方法与我们对辐射度贴图求卷积的方法非常相似。

        其中权重函数 W(wi)=n·wi。具体推导过程就省略了,具体看这里:https://zhuanlan.zhihu.com/p/66518450

        我们需要生成一个新的立方体贴图来保存预过滤的环境贴图数据。为了确保为其 mip 级别分配足够的内存,一个简单方法是调用 glGenerateMipmap。

        注意,因为我们计划采样 prefilterMap 的 mipmap,所以需要确保将其缩小过滤器设置为 GL_LINEAR_MIPMAP_LINEAR 以启用三线性过滤。

        现在对环境贴图进行卷积:镜面反射依赖于表面的粗糙度,反射光线可能比较松散,也可能比较紧密,但是一定会围绕着反射向量r,除非表面极度粗糙:

        所有可能出射的反射光构成的形状称为镜面波瓣。随着粗糙度的增加,镜面波瓣的大小增加;随着入射光方向不同,形状会发生变化。

        因此,镜面波瓣的形状高度依赖于材质。 

        大多数光线最终会反射到一个基于半向量的镜面波瓣内,采样时以类似的方式选取采样向量是有意义的,因为大部分其余的向量都被浪费掉了,这个过程称为重要性采样。

#1 蒙特卡洛积分和重要性采样

        蒙特卡洛积分:

        为了求解这个积分,我们在 a 到 b 上采样 N 个随机样本,将它们加在一起并除以样本总数来取平均。

        pdf 代表概率密度函数 (probability density function),它的含义是特定样本在整个样本集上发生的概率。

        例如,人口身高的 pdf 看起来应该像这样:

        在这种情况下,我们会将每个样本乘以或除以相应的 pdf 再求和。

        但是,某些蒙特卡洛估算是有偏的,这意味着生成的样本并不是完全随机的,而是集中于特定的值或方向。这些有偏的蒙特卡洛估算具有更快的收敛速度,它们会以更快的速度收敛到精确解,但是由于其有偏性,可能永远不会收敛到精确解。通常来说,这是一个可以接受的折衷方案,尤其是在计算机图形学中。因为只要结果在视觉上可以接受,解决方案的精确性就不太重要。下文我们将会提到一种(有偏的)重要性采样,其生成的样本偏向特定的方向,在这种情况下,我们会将每个样本乘以或除以相应的 pdf 再求和。

        蒙特卡洛积分在计算机图形学中非常普遍,因为它是一种以高效的离散方式对连续的积分求近似而且非常直观的方法:对任何面积/体积进行采样——例如半球 Ω ——在该面积/体积内生成数量 N 的随机采样,权衡每个样本对最终结果的贡献并求和。

        蒙特卡洛积分是一个庞大的数学主题,在此不再赘述,但有一点需要提到:生成随机样本的方法也多种多样。

        例如,我们可以对一种名为低差异序列的东西进行蒙特卡洛积分,该序列生成的仍然是随机样本,但样本分布更均匀:

        拟蒙特卡洛方法具有更快的收敛速度,这使得它对于性能繁重的应用很有用。

        好了,现在我们可以使用一个属性来获得更快的收敛速度,这就是重要性采样。

        本质上来说,这就是重要性采样的核心:通过将拟蒙特卡洛采样与低差异序列相结合,并使用重要性采样偏置样本向量的方法,我们可以获得很高的收敛速度。

        这套组合方法甚至可以允许图形应用程序实时求解镜面积分,虽然比预计算结果还是要慢得多。

#2 低差异序列

        我们将使用的序列被称为 Hammersley 序列。

        给出一些巧妙的技巧,我们可以在着色器程序中非常有效地生成 Van Der Corput 序列,我们将用它来获得 Hammersley 序列,设总样本数为 N,样本索引为 i:

        GLSL 的 Hammersley 函数可以获取大小为 N 的样本集中的低差异样本 i。

        并非所有 OpenGL 相关驱动程序都支持位运算符(例如WebGL和OpenGL ES 2.0),在这种情况下,你可能需要不依赖位运算符的替代版本 Van Der Corput 序列:

#3 GGX重要性采样

        有别于均匀或纯随机地(比如蒙特卡洛)在积分半球 ΩΩ 产生采样向量,我们的采样会根据粗糙度,偏向微表面的半向量的宏观反射方向。

        我们现在使用低差异序列值作为输入来生成采样向量:

        此外,要构建采样向量,我们需要一些方法定向和偏移采样向量,以使其朝向特定粗糙度的镜面波瓣方向。

        我们可以如理论教程中所述使用 NDF,并将 GGX NDF 结合到 Epic Games 所述的球形采样向量的处理中:

        别管为什么这么算了,反正这个算法就是结合了NDF和重要性采样。

        基于特定的粗糙度输入和低差异序列值 Xi,我们获得了一个采样向量,该向量大体围绕着预估的微表面的半向量。

        我们可以最终完成预滤波器卷积着色器:

        这里重新看了好多遍才理解,根据光路是可逆的,上面的N其实是视角方向,所有的随机采样向量都是根据这个视角方向形成的。L向量则是光的入射方向,所有的光照都会反射到这个视角方向,这是根据视角方向形成的卷积图。

        prefilteredColor += texture(environmentMap, L).rgb * NdotL 是为了计算垂直光的大小。

        再用 prefilteredColor 除以采样权重总和,其中对最终结果影响较小(NdotL 较小)的采样最终权重也较小。

#4 捕获预过滤Mipmap级别

        剩下要做的就是让 OpenGL 在多个 mipmap 级别上以不同的粗糙度值预过滤环境贴图。有了最开始的辐照度教程作为基础,实际上很简单:

        我们将帧缓冲区缩放到适当的 mipmap 尺寸, mip 级别每增加一级,尺寸缩小为一半。

        此外,我们在 glFramebufferTexture2D 的最后一个参数中指定要渲染的目标 mip 级别,然后将要预过滤的粗糙度传给预过滤着色器。

        如果我们在天空盒着色器中显示这张预过滤的环境立方体贴图,并在其着色器中强制在其第一个 mip 级别以上采样,如下所示:

预过滤卷积的伪像

        当前的预过滤贴图可以在大多数情况下正常工作,不过你迟早会遇到几个与预过滤卷积直接相关的渲染问题。

#1 高粗糙度的立方体贴图接缝

        在具有粗糙表面的表面上对预过滤贴图采样,也就等同于在较低的 mip 级别上对预过滤贴图采样。

        默认情况下,OpenGL不会在立方体面之间进行线性插值。由于较低的 mip 级别具有更低的分辨率,并且预过滤贴图代表了与更大的采样波瓣卷积,因此缺乏立方体的面和面之间的滤波的问题就更明显:

        OpenGL 可以启用 GL_TEXTURE_CUBE_MAP_SEAMLESS,以为我们提供在立方体贴图的面之间进行正确过滤的选项:

#2 预过滤卷积的亮点

        对镜面反射进行卷积需要大量采样,才能正确反映 HDR 环境反射的混乱变化。

        但是在某些环境下,采样数量在某些较粗糙的 mip 级别上可能仍然不够,导致明亮区域周围出现点状图案:

        一种方案如 Chetan Jags 所述,我们可以在预过滤卷积时,不直接采样环境贴图,而是基于积分的 PDF 和粗糙度采样环境贴图的 mipmap ,以减少伪像:

        https://disq.us/url?url=https%3A%2F%2Fdeveloper.nvidia.com%2Fgpugems%2Fgpugems3%2Fpart-iii-rendering%2Fchapter-20-gpu-based-importance-sampling%3ADfJ1oaXLpF8UevaXS66wqM63C5Y&cuid=4392290

        如果对上述的公式不理解,请看这里20.4节。

        既然要采样 mipmap,不要忘记在环境贴图上开启三线性过滤:

        设置立方体贴图的基本纹理后,让 OpenGL 生成 mipmap:

预计算 BRDF

        让我们再次简要回顾一下镜面部分的分割求和近似法:

        我们现在开始计算第二部分BRDF。

        右半部分要求我们在 n⋅ωo、表面粗糙度、菲涅尔系数 F0 上计算 BRDF 方程的卷积。

        这等同于在纯白的环境光或者辐射度恒定为 Li=1.0 的设置下,对镜面 BRDF 求积分。

        我们可以把 F0 移出镜面 BRDF 方程得到如下等式:

        用 Fresnel-Schlick 近似公式替换右边的 F 可以得到:

        让我们用 α 替换 (1−ωo⋅h)^5 以便更轻松地求解 F0:

        然后我们将菲涅耳函数 F 分拆到两个积分里:

        这样,F0在整个积分上是恒定的,我们可以从积分中提取出F0。

        接下来,我们将α替换回其原始形式,从而得到最终分割求和的 BRDF 方程:

        总结一下:

        注意,由于 f(p,ωi,ωo) 已经包含 F 项,它们被约分了,这里的 f 中不计算 F 项,因此变量只剩下了角度和粗糙度。

        scale 和 bias 可以用根据法线分布函数进行重要性采样的蒙特卡洛积分来解决。

        bias 也类似。

        这两部分都可以用一个 2D 的纹理来表示预计算的结果,它们都是 1 维变量,可以放在同一张纹理里边,scale 放在红色通道, bias 放在绿色通道。这个纹理称为 LUT。

        和之前卷积环境贴图类似,我们可以对 BRDF 方程求卷积,其输入是 n 和 ωo 的夹角,以及粗糙度,并将卷积的结果存储在纹理中。这张纹理被称为 BRDF 积分贴图,稍后会将其用于 PBR 光照着色器中,以获得间接镜面反射的最终卷积结果。

        代码与预滤波器的卷积代码大体相似,不同之处在于,它现在根据 BRDF 的几何函数和 Fresnel-Schlick 近似来处理采样向量:

        如你所见,BRDF 卷积部分是从数学到代码的直接转换。我们将角度 θ 和粗糙度作为输入,以重要性采样产生采样向量,在整个几何体上结合 BRDF 的菲涅耳项对向量进行处理,然后输出每个样本上 F0 的系数和偏差,最后取平均值。

        与 IBL 一起使用时,BRDF 的几何项略有不同,因为 k 变量的含义稍有不同:

        由于 BRDF 卷积是镜面 IBL 积分的一部分,因此我们要在 Schlick-GGX 几何函数中使用 kIBL:

        最后,为了存储 BRDF 卷积结果,我们需要生成一张 512 × 512 分辨率的 2D 纹理。

        请注意,我们使用的是 Epic Games 推荐的16位精度浮点格式。

        将环绕模式设置为 GL_CLAMP_TO_EDGE 以防止边缘采样的伪像。

        然后,我们复用同一个帧缓冲区对象,并在 NDC屏幕空间四边形上运行此着色器:

        分割积分和的 BRDF 卷积部分应该得到以下结果:

完成IBL反射

        为了使反射方程的间接镜面反射部分正确运行,我们需要将分割求和近似法的两个部分缝合在一起。第一步是将预计算的光照数据声明到 PBR 着色器的最上面:

        首先,使用反射向量采样预过滤的环境贴图,获取表面的间接镜面反射。请注意,我们会根据表面粗糙度在合适的 mip 级别采样,以使更粗糙的表面产生更模糊的镜面反射。

        然后我们用已知的材质粗糙度和视线-法线夹角作为输入,采样 BRDF LUT。

        这样我们就从 BRDF LUT 中获得了 F0 的系数和偏移,这里我们就直接用间接光菲涅尔项 F 代替F0。把这个结果和 IBL 反射方程左边的预过滤部分结合起来,以重建整个近似积分,存入specular。

        于是我们得到了反射方程的间接镜面反射部分。现在,将其与上一节教程中的反射方程的漫反射部分结合起来,我们可以获得完整的 PBR IBL 结果:

        请注意,specular 没有乘以 kS,因为已经乘过了菲涅耳系数。

        现在,我们终于可以在最终的 PBR 渲染器中看到其真实颜色:

        不容易阿。

        源码请看这里:https://learnopengl.com/code_viewer_gh.php?code=src/6.pbr/2.2.1.ibl_specular/ibl_specular.cpp

        带贴图的源码请看这里:https://learnopengl.com/code_viewer_gh.php?code=src/6.pbr/2.2.2.ibl_specular_textured/ibl_specular_textured.cpp



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3