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(辐照图贴图):
    从任何方向采样这张辐照度贴图,都能得到该方向受到的场景辐照度(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来计算有点困难,可将半球领域按照经纬度划分成小格子,然后根据经纬度计算积分:
    反射率方程变为:
    精度的取值范围是0-2π,纬度是0-1/2π,经纬度分别按照n1和n2进行刻度划分:
    vec3 irradiance = vec3(0.0);  

    vec3 up    = vec3(0.0, 1.0, 0.0);
    vec3 right = cross(up, normal);
    up         = cross(normal, right);

    float sampleDelta = 0.025;
    float nrSamples = 0.0;
    for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta)
    {
        for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta)
        {
            // spherical to cartesian (in tangent space)
            vec3 tangentSample = vec3(sin(theta) * cos(phi),  sin(theta) * sin(phi), cos(theta));
            // tangent space to world
            vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;

            irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta);
            nrSamples++;
        }
    }
    irradiance = PI * irradiance * (1.0 / float(nrSamples));
    
 
二 反射部分,高光IBL Specular IBL:
    这一部分我们将看到PBR真正的魅力。
    specular积分项不是常量,而会依赖入射光方向、视角方向。这样积分运算非常之复杂,怎么办?
    Epic Games提出了一个 分段聚合近似(split sum approximation)方法,预计算specular积分项。
    方法:将specular计算分为两部分,两部分可以单独卷积求解,然后乘在一起。
    我们先来看一下反射率方程(只考虑specular部分):
    按照Epic Games的方法分成两个积分项:
    第一部分使用一张预生成的卷积计算的 预过滤环境贴图(pre-filtered environment map),并考虑roughtness的影响。并且将每逐渐增加的roughness的卷积结果图以此存在pre-filtered map的mipmap levels里面。也就是使用cubemap的mipmap来实现对粗糙度的支持。
    存储了5个不同roughness level的预过滤环境贴图(pre-filtered environment map):
    Epic Games进一步通过以下假设来近似:
    vec3 N = normalize(w_o);
    vec3 R = N;
    vec3 V = R;
    这样卷积过程不需要考虑视线方向。那么从某些掠角观察的高光发射效果就不是很好,但总的来说这个方案是个不错的折衷。
    第二部分,如果我们假设任何方向的入射光辐射率的值都是1,那么我们在给定roughness和n·wi的情况下可以算出BRDF的值。Epic Games将这些预计算的BRDF值存储在一张2D的LUT查询图(BRDF 2D LUT)里面,这张图叫做 BRDF积分图(BRDF integration map),红色表示对菲涅尔系数的缩放,绿色表示对菲涅尔系数的偏移:
 
    BRDF积分图是一张用于查询的LUT图, 使用方法:
        采样UV:(NdotV, roughness),第一个参数其实是n·wi,但同时采用V = R = N
        采样值: x用于缩放菲涅尔系数F,y用于偏移F
    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)
    Specular IBL计算过程(重要):
    首先引入两张贴图:
    uniform samplerCube prefilterMap;
    uniform sampler2D   brdfLUT;
    使用反射向量(reflection vector)采样预过滤环境贴图(pre-filtered environment map)。这里我们需要依据粗糙度来采样cubemap对应的mipmap等级,越粗糙的表面其高光反射越模糊:
    void main()
    {
        [...]
        vec3 R = reflect(-V, N);   

        const float MAX_REFLECTION_LOD = 4.0;
        vec3 prefilteredColor = textureLod(prefilterMap, R,  roughness * MAX_REFLECTION_LOD).rgb;    
        [...]
    }
    然后依据材质的粗糙度和法线/视线方向的夹角查询BRDF 2D LUT贴图,
    vec3 F        = FresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);
    vec2 envBRDF  = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg;
    vec3 specular = prefilteredColor * (F * envBRDF.x + envBRDF.y);=
    现在结合IBL.Diffuse和IBL.Specular得到IBL的 完整的PBR计算过程:
    vec3 F = FresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);

    vec3 kS = F;
    vec3 kD = 1.0 - kS;
    kD *= 1.0 - metallic;      
      
    vec3 irradiance = texture(irradianceMap, N).rgb;
    vec3 diffuse    = irradiance * albedo;
      
    const float MAX_REFLECTION_LOD = 4.0;
    vec3 prefilteredColor = textureLod(prefilterMap, R,  roughness * MAX_REFLECTION_LOD).rgb;   
    vec2 envBRDF  = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg;
    vec3 specular = prefilteredColor * (F * envBRDF.x + envBRDF.y);
      
    vec3 ambient = (kD * diffuse + specular) * ao;
    注意,specular部分没有乘以Ks,因为我们已经乘了菲涅尔系数。
    
说明:
    上面提到的预处理的IBL贴图,都可以使用工具生成,比如cmftStudio或IBLBaker。
    dds支持存储mip levels。  

猜你喜欢

转载自www.cnblogs.com/sifenkesi/p/11924963.html
PBR