Bingo, Computer Graphics & Game Developer

PBR based on IBl

Introduction

本文建立在CodingLabs以及LearnOpenGL[2]^{[2]}中介绍的基本PBR概念上。

PBR自身概念不陌生,使用满足基于物理的BRDF,满足能量守恒即可。IBL[6]^{[6]}的效果能大大提高观感。 这里选用Cook-Torrance BRDF[7]^{[7]}

漫反射部分可选用Lambertian或Oren Nyar BRDF

fr=kdflambert+ksfcooktorrancef_r=k_d f_{lambert}+ksf_{cook-torrance}

Cook-Torrance的高光部分如下,公式具体含义可见[8]^{[8]}

fcooktorrance=DFG4(won)(win)f_{cook-torrance}=\frac{DFG}{4(w_o \cdot n)(w_i \cdot n)}

Normal Distribution Function, Geometry, Fresnel三部分各自选用Trowbridge-Reitz GGX,Schlick-GGX, Fresnel-Schlick approximation(离线也常用Schlick的近似菲涅尔来代替繁重计算)

Cook-Torrance的DFG部分有很多实现,GGX拖尾效果较好,其他部分可参考[5]^{[5]}

NGGX(n,h,α)=α2π((nh)2(α21)+1)2N_{GGX}(n,h,\alpha)=\frac{\alpha^2}{\pi((n \cdot h)^2(\alpha^2 - 1)+1)^2
}

GSchilickGGX(n,v,k)=nv(nv)(1k)+kG_{SchilickGGX}(n,v,k)=\frac{n \cdot v}{(n \cdot v)(1-k)+k}

FSchilick(h,v,F0)=F0+(1Fo)(1(hv))5F_{Schilick}(h,v,F_0)=F_0+(1-F_o)(1-(h \cdot v))^5

其中法线分布中的α\alpha表示表面粗糙度。 几何项中的k根据direct lighting与IBL选用不同粗糙度映射方式kdirect=(α+1)28,kIBL=α22k_{direct}=\frac{(\alpha+1)^2}{8}, k_{IBL}=\frac{\alpha^2}{2}。 最后使用光照与视线方向的G相乘G(n,v,l,k)=Gsub(n,v,k)Gsub(n,v,k)G(n,v,l,k)=G_{sub}(n,v,k)G_{sub}(n,v,k)

菲涅尔反射计算中的F0F_0可以根据下表查找所得

Material F0(Linear) F0(sRGB)
Water (0.02, 0.02, 0.02) (0.15, 0.15, 0.15)
Plastic / Glass (Low) (0.03, 0.03, 0.03) (0.21, 0.21, 0.21)
Plastic High (0.05, 0.05, 0.05) (0.24, 0.24, 0.24)
Glass (high) / Ruby (0.08, 0.08, 0.08) (0.31, 0.31, 0.31)
Diamond (0.17, 0.17, 0.17) (0.45, 0.45, 0.45)
Iron (0.56, 0.57, 0.58) (0.77, 0.78, 0.78)
Copper (0.95, 0.64, 0.54) (0.98, 0.82, 0.76)
Gold (1.00, 0.71, 0.29) (1.00, 0.86, 0.57)
Aluminium (0.91, 0.92, 0.92) (0.96, 0.96, 0.97)
Silver (0.95, 0.93, 0.88) (0.98, 0.97, 0.95)

最后渲染方程(ksk_s由菲涅尔给出)

Lo(p,ωo)=Ω(kdcπ+ksDFG4(ωon)(ωin))Li(p,ωi)(nωi)dωiL_o(p,\omega_o) = \int\limits_{\Omega} (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

Precomputed LUT

贴图选用HDR图像文件,免费HDR文件可从sIBL archive下载

将上述渲染方程左右拆开各做预计算,其中左边为漫反射部分

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

转立体角为球坐标系下
Lo(p,θo,ϕo)=cπΦΘLi(p,θi,ϕi)cos(θi)sin(θi)dθidϕiL_o(p,\theta_o,\phi_o) = \frac{c}{ \pi } \int\limits_{\Phi} \int\limits_{\Theta} L_i(p,\theta_i,\phi_i) cos(\theta_i) sin(\theta_i) d\theta_i d\phi_i

这里使用Monte Carlo Estimator将二重积分转为离散采样(baNNf(xi)\frac{b-a}{N} \sum_{}^{N} f(x_i)),这里采用Uniform Sampling(可更换为MIS方法更快收敛)

Lo(p,θo,ϕo)=cπ2πN1π2N2N1N2Li(p,θi,ϕi)cos(θi)sin(θi)L_o(p,\theta_o,\phi_o) = \frac{c}{ \pi }\frac{2 \pi}{ N_1 } \frac{\pi}{ 2 N_2 } \sum_{}^{N_1} \sum_{}^{N_2} L_i(p,\theta_i,\phi_i) cos(\theta_i) sin(\theta_i)

Lo(p,θo,ϕo)=πcN1N2N1N2Li(p,θi,ϕi)cos(θi)sin(θi)L_o(p,\theta_o,\phi_o) = \frac{\pi c}{ N_1 N_2 } \sum_{}^{N_1} \sum_{}^{N_2} L_i(p,\theta_i,\phi_i) cos(\theta_i) sin(\theta_i)

此处LearnOpenGL对于黎曼和的推导有误,可参考CodingLabs中最后的推导过程. LearnOpengl使用GPU进行预计算,这里我采用CPU端保存为HDR文件以方便Debug,实现部分基本知识公式的翻译(可查看Sophia中的precomputedMap.h查看)

irradianceDiffuseMap

for(int i = 0; i < width; i++) {
    for (int j = 0; j < height; j++) {
        float phiT = 2PI * i / width;
        float thetaT = PI * j / height;

        // avoid situations when up = normal
        vec3 up(sin(thetaT) * cos(phiT), cos(thetaT), sin(thetaT) * sin(phiT));
        vec3 right = up.crosse(normal);
        vec3 normal = right.crosse(up);

        vec3 irradiance = 0;
        // diffuse irradiance compute hemisphere
        for (float phi = 0; phi < 2PI; phi += 2PI / samples) {
            for (float theta = 0; theta < PI_DIV2; theta += PI_DIV2 / samples) {
                local = vec3(sin(theta) * cos(phi), cos(theta), sin(theta) * sin(phi));
                world = local.x * right + local.y * up + local.z * N; 

                x = SphericalPhi(world) / 2PI * width, 
                y = SphericalTheta(world) / PI * height;

                irradiance += GetColor(x, y) * cos(theta) * sin(theta);
            }
        }
        SetColor(i, j, PI * irradiance / (samples * samples));
    }
}    

右边高光部分也可以拆分为两部分,其中BRDF与IBL在某一分布下做预计算

Lo(p,ωo)=ΩLi(p,ωi)dωiΩfr(p,ωi,ωo)(nωi)dωiL_o(p,\omega_o) = \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

由于微表面的法线分布选用GGX,因此Importance Sampling的策略与PBRT中建议的一致,都是选择在法线分布上进行重要性采样。 积分拆为数值积分

具体公式详解可以参考UE4[9]^{[9]}的PBR报告

Lo(v)(1Nk=1NLi(l))(1Nk=1Nfr(l,v)(nl)pdf(l,v))L_o(v) \approx (\frac{1}{N}\sum^{N}_{k=1}L_i(l)) (\frac{1}{N}\sum^N_{k=1}\frac{f_r(l, v) (n \cdot l)}{pdf(l, v)})

很多地方都没有提到这一点,GGX的法线分布pdf在PBRT[1]^{[1]}上有过推导

pdf=D(h)(hn)4(lh)pdf=\frac{D(h)(h \cdot n)}{4(l \cdot h)}

因此代入IS下的pdf后,整体就变得更为简洁

Lo(v)(1Nk=1NLi(l))(1Nk=1NGF(nh)(nh))(vn))L_o(v) \approx (\frac{1}{N}\sum^{N}_{k=1}L_i(l)) (\frac{1}{N}\sum^N_{k=1}\frac{GF \cdot (n \cdot h)}{(n \cdot h))(v \cdot n)})


首先是左半边的Pre-filtering HDR environment map部分。 这里也就是UE4中所说的近似带来误差最大的部分(由于预计算,无法得知R具体为多少),也就是默认V=R=N,即垂直望去反射光线,法线,视线共线。 根据UE4中的展示,其误差效果仍然能够接受

代码实现部分基本上都是公式的直接翻译

for (int i = 0; i < width; i++){
    for (int j = 0; j < height; j++){
        float phi = 2PI * i / width;
        float theta = PI * j / height;

        vec3 N(sin(theta) * cos(phi), cos(theta), sin(theta) * sin(phi));
        vec3 V = N = R;

        float sumWeight = 0;
        vec3 prefilteredColor = 0;

        for (int s = 0; s < sampleCount; s++) {
            random = hammersley(s, sampleCount);
            H = importanceSampleGGX(random, N, roughness);
            L = (2.0 * V.dot(H) * H - V).getNormalized();

            cosL = max(N.dot(L), 0.0f);
            if (cosL > 0.0) {
                x = SphericalPhi(L) / 2PI * width;
                y = s3SphericalTheta(L) / PI * height;

                // The cos here is because UE4 said
                // "As shown in the code below, we have found weighting by coslk achieves better results"
                prefilteredColor += GetColor(x, y) * cosL;
                sumWeight += cosL;
            }
        }
        SetColor(i, j, prefilteredColor / sumWeight);
    }
}
Write(fileName);

这里使用CPU计算会有一个高亮点的问题,如若采样数不够(Sophia采用OpenMP进行多核计算,即便如此,20000的采样数也需要花费几小时才能完成),如图是一个高光与周围亮度值差距较大的一张HDR纹理,下图即便是采用了30000的采样数也没能解决,可见一味的提升采样数并不能完全的解决问题。

5

这里LearnOpenGL在计算prefilteredColor时,不直接从原图上采样,而是选用mipmap进行处理,由于我使用了CPU端计算,且自行实现mipmap间插值较为繁琐,因此选用了采样数较大的没有出现bright bots效果的作为替代。 具体实现可参考LearnOpenGL[2]^{[2]}

最后将不同Roughness的结果存到不同mimap level上即可,下图为几张不同粗糙度时的结果。

specularMap


剩下的右半边BRDF部分,做一定预处理

Ωfr(l,v)(nl)dl=Ωfr(l,v)F(v,h)F(v,h)(nl)dl\int\limits_{\Omega} f_r(l,v) (n \cdot l) dl = \int\limits_{\Omega} \frac{f_r(l,v)}{F(v, h)} F(v, h) (n \cdot l) dl

Ωfr(l,v)F(v,h)(F0+(1F0)(1vh)5)(nl)dl\int\limits_{\Omega} \frac{f_r(l,v)}{F(v, h)} (F_0 + (1 - F_0){(1 - v \cdot h)}^5) (n \cdot l) dl

F0Ωfr(l,v)F(v,h)(1(1vh)5)(nl)dl+Ωfr(l,v)F(v,h)(1vh)5(nl)dlF_0 \int\limits_{\Omega} \frac{f_r(l, v)}{F(v, h)}(1 - {(1 - v \cdot h)}^5) (n \cdot l) dl
+
\int\limits_{\Omega} \frac{f_r(l, v)}{F(v, h)} {(1 - v \cdot h)}^5 (n \cdot l) dl

vec3 integrateBRDF(float NoV, float roughness, int samples) {
    // V on XoY plane
    vec3 V(sqrt(1.0f - NoV * NoV), NoV, 0.0f);
    vec3 N = 0;
    float A = 0.0f, B = 0.0f;

    for (int i = 0; i < samples; ++i) {
        vec2 random = hammersley(i, samples);
        vec3 H = importanceSampleGGX(random, N, roughness);
        vec3 L = (2.0 * V.dot(H) * H - V).getNormalized();

        float NoL = max(L.y, 0.0f);
        float NoH = max(H.y, 0.0f);
        float VoH = max(V.dot(H), 0.0f);

        if (NoL > 0.0f) {
            float G = geometrySmith(N, V, L, roughness);
            float GVisibility = (G * VoH) / (NoV * NoH);
            float Fc = pow(1.0f - VoH, 5.0f);

            A += (1.0f - Fc) * GVisibility;
            B += Fc * GVisibility;
        }
    }
    return vec3(A, B, 0.0f) / samples;
}

BRDF预计算结果如下图,横纵坐标分别为cosθvcos\theta_v与Roughness

Result

最后结果也较为可观,尽管没有indirectLighting部分,但总体效果已经很不错了。

IBL2

IBL1

Reference

  1. PBRT:基于物理的BRDF及其Importance Sampling理论推导
  2. LearnOpenGL:基本PBR概念及其实现
  3. Physically Based Shading during Black Ops
  4. Klayge:Cook-Torrance BRDF的Importance Sampling PDF推导
  5. Specular BRDF Reference
  6. Image-Based Lighting
  7. Cook-Torrance BRDF
  8. Physically Based Rendering - Cook–Torrance
  9. Real Shading in Unreal Engine 4:实时PBR实现理论推导及其近似解的解释