欢迎关注公众号:sumsmile /专注图形学
内容参考learnopengl、games202整理,加入了自己的理解和补充
IBL(Image based lighting, IBL)
IBL + PBR实现全局的环境光照算是learnOpenGL中最难的内容,不太好理解,至今我也没有完全弄明白。这里仅记录我理解的思路,对代码细节不做展开。
全局光照:初级光照 + 次级光照
- 比如点光源、平行光直接照在物体表面形成初级光照
- 周边物体本身不发光,被照亮后成为次级光源,发出光线形成次级光照
回到PBR反射方程
着色时,如果对每个点都进行这样的积分,计算量就太大了,图形技术中最常用的手段是预计算。
将上面这个方程拆分成漫反射和镜面反射分别做预计算
漫反射预计算->辐照度图
漫反射和观察方向无关,对一个点来说,从任意角度观察看到的漫反射都一样,所以比较好处理。
漫反射部分将常量提出来,对光照部分预计算
漫反射情况下,是对以当前点法线为中心,一个半球形区域进行积分
对每个法线都做一次积分,就得到辐照度图
每个点对这个图进行一次采样即可,这个图可以用分辨率比较低的纹理
漫反射还有一种方案
将原环境图用球谐函数分解,类似傅里叶分解,求出每个基函数的系数
最后每个点做有限的乘法加和
再看镜面反射预计算
镜面反射和观察视角、镜面反射强度(反射波瓣)有关
- 多了一个观察视角,按漫反射的方法就多了一个维度,存储和性能会差很多
- 和反射波瓣有关,即和粗糙度有关,则预计算得考虑多个粗糙度,对应的生成多个预计算(一般用纹理存储,方便采样)
实现思路
同样,先把复杂的拆开,用分割求和的方法,拆成两个积分的求和
这里假设p点在场景中间,且只考虑这一个点
-
第一项是光照项目 只和光照本身有关,和漫反射类似,但是镜面反射得考虑波瓣的形状,所以用到了重要性采样,以入射光中心为基准进行ImportGGX采样,生成镜面反射积分图
粗糙度越大,整个图越模糊,就越不需要清晰度很高,所以这里有个技巧,用mipmap存储,越粗糙的图寸草mipmap级别越高的缓存中,最后根据粗糙度计算出mipmap级别,还可以做三线性插值,有更平滑的效果,即只需要预计算有限的粗糙度即可
再说明下,GGX只是一个类似钟形的函数(类似高斯函数)
- 第二项是光的传输项
这部分的思路是继续拆分
注意:公式中的两个积分分别表示 F0的比例和偏差。注意,由于 f(p,ωi,ωo)已经包含 F项,它们被约分了,这里的 f中不计算 F项。
即参数只有入射角和粗糙度,就可以用二维的图来存储了
生成查找纹理的时候,我们以 BRDF 的输入n⋅ωi (范围在 0.0 和 1.0 之间)作为横坐标,以粗糙度作为纵坐标。有了此 BRDF 积分贴图和预过滤的环境贴图,我们就可以将两者结合起来,以获得镜面反射积分的结果
- 最后合并起来
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)