LearnOpenGL.PBR.IBL

概述:
IBL:image based lighting,一种间接光照(indirect lighting)技术,将周围的环境存在一张环境贴图(基于现实世界或3D场景生成)里面,然后将环境贴图上的每一个像素都当做一个光源发射器。这样我们能有效地捕捉环境的全局光照环境和大体感觉,让渲染物体有一种沉浸在环境中的视觉效果。在PBR pipeline中引入IBL环境光照可以让渲染结果更加的物理可信。
    那么如何在PBR中实现IBL光照计算了,我们先来回忆一下PBR的反射率方程:
    IBL和直接光照不一样(光照来源可枚举),每个材质表面上的点都需要计算半球领域上的所有入射光线。
    那么两点:
    (1)根据某个方向得到该方向上光照对应的辐射率?方法:贴图采样
    vec3 radiance = texture(_cubemapEnvironment, w_i).rgb;
    (2)需要解决实时积分运算的效率问题?方法:预处理卷积
    首先将反射率方程的反射和折射部分分开计算:
    那么我们就可以分开讨论两部分的优化实现方法。
 
一 折射部分,漫反射辐照度 Diffuse irradiance:
    我们将不依赖积分因子的常量移出积分部分:
    这样积分就只和wi入射光方向有关系了(在IBL计算中我们假设p处于环境的中心位置)。然后我们通过 预计算的方式, 生成一个新的cubemap(irradiance cubemap, convoluted cubemap),通过 卷积计算将每个采样方向漫反射积分结果(diffuse integral's result)存储在cubemap对应的像素上。
    卷积是将一些计算应用于数据集中的每一个条目,同时考虑到数据集中的所有其他条目。也就是说,新生成的cubemap每一个方向的采样结果,都已经直接考虑了半球领域里其他所有方向的采样值(最后取平均值),这样采样一次,就可以得到diffuse irradiance,效率问题解决。
    左边是环境贴图cubemap,右边是预计算生成的irradiance map(辐照图贴图):
    图片来源: https://www.indiedb.com/features/using-image-based-lighting-ibl
    从任何方向采样这张辐照度贴图,都能得到该方向受到的场景辐照度(irradiance)。
        vec3 irradiance = texture(irradianceMap, N);
PBR和HDR:
    PBR和HDR紧密相联。irrandiance map使用每个像素存储indirect light intensity(间接光照强度)。物理上的环境光照范围很广,灯泡和太阳的光照强度差异非常之大,所以environment map光照强度的取值范围很广,也就是说这张环境贴图必须是HDR的。
    普通的cubemap是LDR的(每个面各存储了一张普通LDR贴图),在使用时直接从某个面上的贴图采样颜色(颜色范围从0.0-1.0),这个小范围的值用来做颜色输出是没任何问题的,但是,当用来作PBR的物理输入参数时,0.0-1.0的取值范围就明显不够用了。
HDR辐射率格式文件(The radiance HDR file):
    格式:***.hdr
    存储: 不使用每通道32位存储,而是使用每通道8位存储,然后使用alpha通道作为指数。不过这样确实会损失一些精度。
    使用:需要手动做一次转换,将采样到的颜色值转换为对应的浮点值。
    HDR下载地址: http://www.hdrlabs.com/sibl/archive.html
    equirectangular map:从一个球体投射到平面上所得到的一张单一的图。多数情况,都采用水平视角来进行投影,不过也有从底部或顶部视角来投影的。
    基本所有的HDR贴图都是默认处在线性空间。
diffuse lighting计算过程(重要):
    因为IBL计算的是周围环境的光照影响,没有任何的直接光照,所以IBL计算出来的diffuse成分和specular成分都被当做ambient lighting(环境光)的组成部分。
    首先引入预计算的irradiance map:
    uniform samplerCube irradianceMap;
    按照直接光照PBR的计算过程:
    vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0);
    vec3 kD = 1.0 - kS;
    vec3 irradiance = texture(irradianceMap, N).rgb;
    vec3 diffuse    = irradiance * albedo;
    vec3 ambient    = (kD * diffuse) * ao;
(1)因为光照来自于半球领域的所有方向,所有就没有直接光照的半角向量的概念,于是为了模拟Fresnel,我们使用法线和观察向量的夹角来计算Fresnel系数;
(2)因为没有考虑到roughness,所以反射率会偏高,按照直接光照的经验,我们希望粗糙的表面反射会弱一些,所以我们在计算菲涅尔系数时直接引入粗糙度, https://seblagarde.wordpress.com/2011/08/17/hello-world/:
    vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
    {
        return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
    }   
    引入粗糙度以后的计算过程:
    vec3 kS = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);
    vec3 kD = 1.0 - kS;
    vec3 irradiance = texture(irradianceMap, N).rgb;
    vec3 diffuse    = irradiance * albedo;
    vec3 ambient    = (kD * diffuse) * ao;
从Equiretangular到Cubemap的转换(了解即可):
    直接使用equirectangular map对比使用cubemap要耗费一些,因为需要额外的转换过程。
cubemap卷积计算思路(了解即可):
    在半球领域按照立体角dw来计算有点困难,可将半球领域按照经纬度划分成小格子,然后根据经纬度计算积分:

你可能感兴趣的:(LearnOpenGL.PBR.IBL)