PBR(Physically Based Rendering)-IBL

652 阅读3分钟

欢迎关注公众号:sumsmile /专注图形学

内容参考learnopengl、games202整理,加入了自己的理解和补充

IBL(Image based lighting, IBL)

IBL + PBR实现全局的环境光照算是learnOpenGL中最难的内容,不太好理解,至今我也没有完全弄明白。这里仅记录我理解的思路,对代码细节不做展开。

全局光照:初级光照 + 次级光照

  • 比如点光源、平行光直接照在物体表面形成初级光照
  • 周边物体本身不发光,被照亮后成为次级光源,发出光线形成次级光照

回到PBR反射方程

(kdcπ+ksDFG4(ωon)(ωin))Li(p,ωi)nωidωi (k_d\frac{c}{\pi} + k_s\frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}) L_i(p,\omega_i) n \cdot \omega_i d\omega_i

着色时,如果对每个点都进行这样的积分,计算量就太大了,图形技术中最常用的手段是预计算。

将上面这个方程拆分成漫反射和镜面反射分别做预计算

Lo(p,ωo)=Ω(kdcπ)Li(p,ωi)nωidωi+Ω(ksDFG4(ωon)(ωin))Li(p,ωi)nωidωiL_o(p,\omega_o) = \int\limits_{\Omega} (k_d\frac{c}{\pi}) L_i(p,\omega_i) n \cdot \omega_i d\omega_i + \int\limits_{\Omega} (k_s\frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}) L_i(p,\omega_i) n \cdot \omega_i d\omega_i

漫反射预计算->辐照度图

漫反射和观察方向无关,对一个点来说,从任意角度观察看到的漫反射都一样,所以比较好处理。

漫反射部分将常量提出来,对光照部分预计算

Lo(p,ωo)=kdcπΩLi(p,ωi)nωidωiL_o(p,\omega_o) = k_d\frac{c}{\pi} \int\limits_{\Omega} L_i(p,\omega_i) n \cdot \omega_i d\omega_i

漫反射情况下,是对以当前点法线为中心,一个半球形区域进行积分

对每个法线都做一次积分,就得到辐照度图

每个点对这个图进行一次采样即可,这个图可以用分辨率比较低的纹理

漫反射还有一种方案

将原环境图用球谐函数分解,类似傅里叶分解,求出每个基函数的系数

最后每个点做有限的乘法加和

再看镜面反射预计算

镜面反射和观察视角、镜面反射强度(反射波瓣)有关

  • 多了一个观察视角,按漫反射的方法就多了一个维度,存储和性能会差很多
  • 和反射波瓣有关,即和粗糙度有关,则预计算得考虑多个粗糙度,对应的生成多个预计算(一般用纹理存储,方便采样)

实现思路

同样,先把复杂的拆开,用分割求和的方法,拆成两个积分的求和

ΩLi(p,ωi)dωiΩfr(p,ωi,ωo)nωidωi \int\limits_{\Omega} L_i(p,\omega_i) d\omega_i * \int\limits_{\Omega} f_r(p, \omega_i, \omega_o) n \cdot \omega_i d\omega_i

这里假设p点在场景中间,且只考虑这一个点

  1. 第一项是光照项目 只和光照本身有关,和漫反射类似,但是镜面反射得考虑波瓣的形状,所以用到了重要性采样,以入射光中心为基准进行ImportGGX采样,生成镜面反射积分图

粗糙度越大,整个图越模糊,就越不需要清晰度很高,所以这里有个技巧,用mipmap存储,越粗糙的图寸草mipmap级别越高的缓存中,最后根据粗糙度计算出mipmap级别,还可以做三线性插值,有更平滑的效果,即只需要预计算有限的粗糙度即可

再说明下,GGX只是一个类似钟形的函数(类似高斯函数)

  1. 第二项是光的传输项

这部分的思路是继续拆分

F0Ωfr(p,ωi,ωo)(1(1ωoh)5)nωidωi+Ωfr(p,ωi,ωo)(1ωoh)5nωidωiF_0 \int\limits_{\Omega} f_r(p, \omega_i, \omega_o)(1 - {(1 - \omega_o \cdot h)}^5) n \cdot \omega_i d\omega_i + \int\limits_{\Omega} f_r(p, \omega_i, \omega_o) {(1 - \omega_o \cdot h)}^5 n \cdot \omega_i d\omega_i

注意:公式中的两个积分分别表示 F0的比例和偏差。注意,由于 f(p,ωi,ωo)已经包含 F项,它们被约分了,这里的 f中不计算 F项。

即参数只有入射角和粗糙度,就可以用二维的图来存储了

生成查找纹理的时候,我们以 BRDF 的输入n⋅ωi (范围在 0.0 和 1.0 之间)作为横坐标,以粗糙度作为纵坐标。有了此 BRDF 积分贴图和预过滤的环境贴图,我们就可以将两者结合起来,以获得镜面反射积分的结果

  1. 最后合并起来
float lod             = getMipLevelFromRoughness(roughness);
vec3 prefilteredColor = textureCubeLod(PrefilteredEnvMap, refVec, lod);
vec2 envBRDF          = texture2D(BRDFIntegrationMap, vec2(NdotV, roughness)).xy;
vec3 indirectSpecular = prefilteredColor * (F * envBRDF.x + envBRDF.y)