文章目录

  • PBR中引入IBL——镜面反射篇
    • 回顾上一篇:漫反射
    • 镜面反射的计算思路
      • 镜面反射部分方程的分解
      • 生成预计算纹理
    • 必要的预备知识
      • 镜面波瓣
      • 蒙特卡洛积分(Monte Carlo Integration)
      • 重要性采样
      • 低差异序列(Low Discrepancy Sequence)
    • 预滤波环境贴图prefilterMap
      • 生成prefilterMap的mipmap贴图
      • 计算积分
        • 实现低差异序列
        • 实现重要性采样
        • prefilterMap的着色器
    • BRDF积分贴图brdfLUT
      • 整理积分方程
      • brdfLUT着色器
    • 完成IBL的镜面反射
    • 完成IBL
    • 终极合并——全局光照

PBR中引入IBL——镜面反射篇

回顾上一篇:漫反射

  • Cook-Torrance反射方程分解
    Lo(p,ωo)=∫Ω(kdcπ)Li(p,ωi)n⋅ωidωi+∫Ω(ksDFG4(ωo⋅n)(ωi⋅n))Li(p,ωi)n⋅ωidωiL_o(p,\omega_o) = \int\limits_\Omega(k_d\frac{c}{π})L_i(p,\omega_i)n\cdot\omega_id\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_id\omega_iLo​(p,ωo​)=Ω∫​(kd​πc​)Li​(p,ωi​)n⋅ωi​dωi​+Ω∫​(ks​4(ωo​⋅n)(ωi​⋅n)DFG​)Li​(p,ωi​)n⋅ωi​dωi​
    =Lo′(p,ωo)+Lo′′(p,ωo)= L_o'(p,\omega_o) + L_o''(p,\omega_o)=Lo′​(p,ωo​)+Lo′′​(p,ωo​)

  • 漫反射部分为:
    Lo′(p,ωo)=kdcπ∫ΩLi(p,ωi)n⋅ωidωiL_o'(p,\omega_o) = k_d\frac{c}{π}\int\limits_\Omega L_i(p,\omega_i)n\cdot\omega_id\omega_iLo′​(p,ωo​)=kd​πc​Ω∫​Li​(p,ωi​)n⋅ωi​dωi​

  • 对于已给出的或计算出的环境立方体贴图envCubeTexture,在方向ωi\omega_iωi​上采样,得到的是此方向上的radiance Li(p,ωi)L_i(p,\omega_i)Li​(p,ωi​)

  • 我们的目的是得到Lo′(p,ωo)L_o'(p,\omega_o)Lo′​(p,ωo​),所以关键在计算∫ΩLi(p,ωi)n⋅ωidωi\int\limits_\Omega L_i(p,\omega_i)n\cdot\omega_id\omega_iΩ∫​Li​(p,ωi​)n⋅ωi​dωi​

  • 计算∫ΩLi(p,ωi)n⋅ωidωi\int\limits_\Omega L_i(p,\omega_i)n\cdot\omega_id\omega_iΩ∫​Li​(p,ωi​)n⋅ωi​dωi​时,不可能从半球 Ω 的每个可能的方向采样envCubeTexture。不过我们可以对有限数量的方向采样以近似求解,在半球内均匀间隔取方向可以获得一个相当精确的积分结果,从而离散地计算积分

  • 用有限数量的样本近似求解积分的方法叫黎曼和:∫abf(x)dx=∑k=1nf(εk)Δxk\int_a^bf(x)dx = \sum_{k=1}^{n}f(\varepsilon_k)\Delta x_k∫ab​f(x)dx=∑k=1n​f(εk​)Δxk​,用黎曼和的方法求出积分结果后再进行卷积,在这里指的就是对纹理envCubeTexture的采样结果的进行卷积

  • 对每个片段实时计算积分显然不现实,我们通过预计算,将积分结果提前存入纹理irradianceMap中

  • 对纹理irradianceMap,在方向ωo\omega_oωo​上采样,可以理解为场景中所有能够击中面向ωo\omega_oωo​的表面的间接漫反射光的总和,即Lo′(p,ωo)L_o'(p,\omega_o)Lo′​(p,ωo​)

镜面反射的计算思路

  • 这一篇的目标是计算Lo′′(p,ωo)L_o''(p,\omega_o)Lo′′​(p,ωo​),计算的关键同样是积分的计算
  • 根据上一篇的经验,这一部分的处理思路也是先分解渲染方程,然后预计算基础环境纹理envCubeTexture的卷积,创建纹理保存卷积结果

镜面反射部分方程的分解

Lo′′(p,ωo)=∫Ω(ksDFG4(ωo⋅n)(ωi⋅n))Li(p,ωi)n⋅ωidωiL_o''(p,\omega_o) = \int\limits_\Omega(k_s\frac{DFG}{4(\omega_o\cdot n)(\omega_i\cdot n)})L_i(p,\omega_i)n\cdot\omega_id\omega_i Lo′′​(p,ωo​)=Ω∫​(ks​4(ωo​⋅n)(ωi​⋅n)DFG​)Li​(p,ωi​)n⋅ωi​dωi​
=∫Ωfr(p,ωi,ωo)Li(p,ωi)n⋅ωidωi= \int\limits_\Omega f_r(p,\omega_i,\omega_o)L_i(p,\omega_i)n\cdot \omega_id\omega_i=Ω∫​fr​(p,ωi​,ωo​)Li​(p,ωi​)n⋅ωi​dω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=Ω∫​Li​(p,ωi​)dωi​∗Ω∫​fr​(p,ωi​,ωo​)n⋅ωi​dωi​

  • Lo′′(p,ωo)L_o''(p,\omega_o)Lo′′​(p,ωo​)被分成了前后两部分,所以要进行两次卷积,生成两个新的纹理

生成预计算纹理

  • 对基础纹理envCubeTexture按照∫ΩLi(p,ωi)dωi\int\limits_\Omega L_i(p,\omega_i)d\omega_iΩ∫​Li​(p,ωi​)dωi​进行卷积,将卷积的结果保存至新建的纹理中,这张纹理被称为预滤波环境贴图prefilterMap
  • 在生成prefilterMap时,要考虑粗糙度的影响。因为随着粗糙度的增加,参与基础纹理envCubeTexture卷积的采样向量会更分散,导致反射更模糊,所以对于卷积的每个粗糙度级别,我们将按顺序把模糊后的结果存储在mipmap中
  • 对基础纹理envCubeTexture按照∫Ωfr(p,ωi,ωo)n⋅ωidωi\int\limits_\Omega f_r(p,\omega_i, \omega_o)n\cdot \omega_i d\omega_iΩ∫​fr​(p,ωi​,ωo​)n⋅ωi​dωi​进行卷积,将卷积后的结果保存至2d查找纹理(Look Up Texture, LUT)中,这张纹理被称为BRDF积分贴图brdfLUT
  • 我们假设每个方向的入射辐射度都是白色,在给定粗糙度、光线 ωi 法线 n 夹角 n⋅ωi 的情况下,预计算 BRDF 的响应结果。Epic Games 将预计算好的 BRDF 对每个粗糙度和入射角的组合的响应结果存储在一张 2d查找纹理上,即brdfLUT。这个纹理存储是菲涅耳响应的系数(R 通道)和偏差值(G 通道),它提供了分割版镜面反射积分的第二个部分
  • 然后分别对这两张纹理采样,将采样结果相乘就得到了Lo′′(p,ωo)L_o''(p,\omega_o)Lo′′​(p,ωo​),对吗?
  • 也对也不对,因为其中还有极其复杂的细节。。。

必要的预备知识

镜面波瓣

  • 镜面反射依赖于表面的粗糙度,反射光线可能比较松散,也可能比较紧密,但是一定会围绕着反射向量,除非表面极度粗糙
  • 所有可能出射的反射光构成的形状称为镜面波瓣
  • 在微表面模型里给定入射光方向,则镜面波瓣指向微平面的半程向量的方向

蒙特卡洛积分(Monte Carlo Integration)

∫abf(x)dx≈1N∑i=0N−1f(x)pdf(x)\int_a^bf(x)dx \approx \frac{1}{N}\sum_{i=0}^{N-1}\frac{f(x)}{pdf(x)}∫ab​f(x)dx≈N1​i=0∑N−1​pdf(x)f(x)​

  • pdf (probability density function,概率密度函数)它的含义是特定样本在整个样本集上发生的概率

  • 蒙特卡洛积分告诉我们:要计算等式左边的定积分,只用简单地从总体中随机挑选N个样本,并按照等式右边的式子进行计算,就能得到定积分的结果;并且随着样本数量的增加,结果会越来越接近定积分的精确结果

  • 蒙特卡洛积分在计算机图形学中应用非常普遍,因为它是一种以高效的离散方式对连续的积分求近似而且非常直观的方法

重要性采样

  • 重要性采样是蒙特卡洛积分的一种采样策略
  • 蒙特卡洛积分中pdf(x)的意义在于:采样的样本不局限于均匀采样,我们可以根据被积分函数的形状去选择更高效的pdf(x)来指导采样点的生成,这也是重要性采样的核心思想
  • 一个区间被采样到的概率越高,则这次采样在求和中的权重就越小,这也符合我们的直觉,假如不降低高频样本的权重,采样次数越多,估计出来的结果就越大,而不是收敛于期望

低差异序列(Low Discrepancy Sequence)

  • 目的是生成分布均匀的随机数

  • 对于一切需要采样的算法来说,分布均匀的随机数就意味着更加优秀的样本分布;在计算蒙特卡洛积分的过程中采样无处不在,所以好的样本分布直接影响积分过程的收敛速度

  • 伪随机数组成的二维点集

  • 低差异序列点集

预滤波环境贴图prefilterMap

生成prefilterMap的mipmap贴图

  • 由于镜面反射受粗糙度的影响,所以对于每个粗糙度级别,我们将按顺序把模糊后的结果存储在预滤波贴图prefilterMap的mipmap中
  • 为了确保为其 mip 级别分配足够的内存,一个简单方法是调用 glGenerateMipmap
  • 注意将prefilterMap的缩小过滤器设置为 GL_LINEAR_MIPMAP_LINEAR 以启用三线性过滤

计算积分

  • 根据蒙特卡洛积分算法,求解积分需要对样本进行采样,这里的样本指的就是入射光线ωi\omega_iωi​
  • 根据重要性采样的思想,把采样的范围锁定在镜面波瓣内才会有意义
  • 由低差异序列可知,不同种类的随机数也会影响蒙特卡洛积分的结果
  • 因此使用低差异序列生成的随机数,并采用重要性采样的思想,在镜面波瓣内进行采样,最终可以得到不错的结果

实现低差异序列

  • 低差异序列也有很多种,这里使用 Hammersley 序列, Hammersley序列又是基于 Van Der Corput 序列
  • Van Der Corput 序列,该序列是把十进制数字的二进制表示镜像翻转到小数点右边而得
float RadicalInverse_VdC(uint bits)
{bits = (bits << 16u) | (bits >> 16u);bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);return float(bits) * 2.3283064365386963e-10; // / 0x100000000
}vec2 Hammersley(uint i, uint N)
{return vec2(float(i)/float(N), RadicalInverse_VdC(i));
}

实现重要性采样

  • 这里的重要性采样的目标是:使采样基础纹理的样本的反射光线在镜面波瓣内;为了实现这个目标,我们需要将重要性采样的结果作为半程向量使用,然后用反射光线和半程向量计算出采样基础纹理的样本,这也是生成prefilterMap的关键步骤
  • 重要性采样的过程:开始一个大循环,生成一个低差异序列值,用该序列值在切线空间中生成样本向量,将样本向量变换到世界空间
vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness)
{float a = roughness*roughness;float phi = 2.0 * PI * Xi.x;float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y));float sinTheta = sqrt(1.0 - cosTheta*cosTheta);vec3 H;H.x = cos(phi) * sinTheta;H.y = sin(phi) * sinTheta;H.z = cosTheta;vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0,
0.0);vec3 tangent   = normalize(cross(up, N));vec3 bitangent = cross(N, tangent);vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z;return normalize(sampleVec);
}
const uint SAMPLE_COUNT = 4096u;
for(uint i = 0u; i < SAMPLE_COUNT; ++i)
{ vec2 Xi = Hammersley(i, SAMPLE_COUNT);vec3 H = ImportanceSampleGGX(Xi, N, roughness);
}

prefilterMap的着色器

  • 利用重要性采样生成的半程向量H和观察向量V,计算采样基础纹理的样本L,即入射光线ωi\omega_iωi​
  • Epic将观察方向V近似为光线的出射方向
  • 在立方体贴图中,顶点坐标又可以认为是法线N和观察方向V
  • 计算L的方向跟施密特正交化的方法很像,但不是生成正交向量,而是V关于H的反射向量:vec3 L = normalize(2.0 * dot(V, H) * H - V);
void main()
{ vec3 N = normalize(localPos); vec3 V = N; const uint SAMPLE_COUNT = 1024u; float totalWeight = 0.0; vec3 prefilteredColor = vec3(0.0); for(uint i = 0u; i < SAMPLE_COUNT; ++i) { vec2 Xi = Hammersley(i, SAMPLE_COUNT); vec3 H = ImportanceSampleGGX(Xi, N, roughness); vec3 L = normalize(2.0 * dot(V, H) * H - V); float NdotL = max(dot(N, L), 0.0); if(NdotL > 0.0) { prefilteredColor += texture(envCubeTexture, L).rgb * NdotL; totalWeight += NdotL; } } prefilteredColor = prefilteredColor / totalWeight; FragColor = vec4(prefilteredColor, 1.0);
}

BRDF积分贴图brdfLUT

整理积分方程

  • 首先通过变形把∫Ωfr(p,ωi,ωo)n⋅ωidωi\int\limits_\Omega f_r(p,\omega_i, \omega_o)n\cdot \omega_i d\omega_iΩ∫​fr​(p,ωi​,ωo​)n⋅ωi​dωi​中菲涅尔系数F0F_0F0​移到积分外部
  • 菲涅尔函数FSchlick(h,v,F0)=F0+(1−F0)(1−h⋅v)5F_{Schlick}(h,v,F_0) = F_0 + (1 - F_0)(1 - h⋅v)^5FSchlick​(h,v,F0​)=F0​+(1−F0​)(1−h⋅v)5,其中的v就是ωo\omega_oωo​,F0F_0F0​是个常数,因此又可以写成F(ωo,h)=F0+(1−F0)(1−ωo⋅h)5F(\omega_o,h) = F_0 + (1 - F_0)(1 - \omega_o\cdot h)^5F(ωo​,h)=F0​+(1−F0​)(1−ωo​⋅h)5

∫Ωfr(p,ωi,ωo)n⋅ωidωi=∫Ωfr(p,ωi,ωo)F(ωo,h)F(ωo,h)n⋅ωidωi\int\limits_\Omega f_r(p,\omega_i, \omega_o)n\cdot \omega_i d\omega_i = \int\limits_\Omega f_r(p,\omega_i, \omega_o)\frac{F(\omega_o,h)}{F(\omega_o,h)}n\cdot \omega_i d\omega_iΩ∫​fr​(p,ωi​,ωo​)n⋅ωi​dωi​=Ω∫​fr​(p,ωi​,ωo​)F(ωo​,h)F(ωo​,h)​n⋅ωi​dωi​
=∫Ωfr(p,ωi,ωo)F(ωo,h)F(ωo,h)n⋅ωidωi= \int\limits_\Omega \frac{f_r(p,\omega_i, \omega_o)}{F(\omega_o,h)}F(\omega_o,h)n\cdot \omega_i d\omega_i=Ω∫​F(ωo​,h)fr​(p,ωi​,ωo​)​F(ωo​,h)n⋅ωi​dωi​
=∫Ωfr(p,ωi,ωo)F(ωo,h)(F0+(1−F0)(1−ωo⋅h)5)n⋅ωidωi= \int\limits_\Omega \frac{f_r(p,\omega_i, \omega_o)}{F(\omega_o,h)}(F_0 + (1 - F_0)(1 - \omega_o\cdot h)^5)n\cdot \omega_i d\omega_i=Ω∫​F(ωo​,h)fr​(p,ωi​,ωo​)​(F0​+(1−F0​)(1−ωo​⋅h)5)n⋅ωi​dωi​
用α\alphaα代替(1−ωo⋅h)5(1-\omega_o\cdot h)^5(1−ωo​⋅h)5,得:
=∫Ωfr(p,ωi,ωo)F(ωo,h)(F0+(1−F0)α)n⋅ωidωi= \int\limits_\Omega \frac{f_r(p,\omega_i, \omega_o)}{F(\omega_o,h)}(F_0 + (1 - F_0)\alpha)n\cdot \omega_i d\omega_i=Ω∫​F(ωo​,h)fr​(p,ωi​,ωo​)​(F0​+(1−F0​)α)n⋅ωi​dωi​
=∫Ωfr(p,ωi,ωo)F(ωo,h)(F0+α−F0α)n⋅ωidωi= \int\limits_\Omega \frac{f_r(p,\omega_i, \omega_o)}{F(\omega_o,h)}(F_0 + \alpha - F_0\alpha)n\cdot \omega_i d\omega_i=Ω∫​F(ωo​,h)fr​(p,ωi​,ωo​)​(F0​+α−F0​α)n⋅ωi​dωi​
=∫Ωfr(p,ωi,ωo)F(ωo,h)(F0(1−α)+α)n⋅ωidωi= \int\limits_\Omega \frac{f_r(p,\omega_i, \omega_o)}{F(\omega_o,h)}(F_0(1 - \alpha) + \alpha)n\cdot \omega_i d\omega_i=Ω∫​F(ωo​,h)fr​(p,ωi​,ωo​)​(F0​(1−α)+α)n⋅ωi​dωi​
=∫Ωfr(p,ωi,ωo)F(ωo,h)(F0(1−α))n⋅ωidωi+∫Ωfr(p,ωi,ωo)F(ωo,h)α⋅n⋅ωidωi= \int\limits_\Omega \frac{f_r(p,\omega_i, \omega_o)}{F(\omega_o,h)}(F_0(1 - \alpha))n\cdot \omega_i d\omega_i + \int\limits_\Omega \frac{f_r(p,\omega_i, \omega_o)}{F(\omega_o,h)}\alpha \cdot n\cdot \omega_i d\omega_i=Ω∫​F(ωo​,h)fr​(p,ωi​,ωo​)​(F0​(1−α))n⋅ωi​dωi​+Ω∫​F(ωo​,h)fr​(p,ωi​,ωo​)​α⋅n⋅ωi​dωi​
用frf_rfr​中得菲涅尔函数与分子进行约分,把F0F_0F0​提到积分外,再替换α\alphaα,得:
=F0∫Ωfr′(p,ωi,ωo)(1−(1−ωo⋅h)5)n⋅ωidωi+∫Ωfr′(p,ωi,ωo)(1−ωo⋅h)5⋅n⋅ωidωi= F_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 \cdot n\cdot \omega_i d\omega_i=F0​Ω∫​fr′​(p,ωi​,ωo​)(1−(1−ωo​⋅h)5)n⋅ωi​dωi​+Ω∫​fr′​(p,ωi​,ωo​)(1−ωo​⋅h)5⋅n⋅ωi​dωi​
最终这个式子的前半部分表示F0F_0F0​的比例,后半部分表示偏差,我们把这两个部分分别计算和存储,所以就成了开始时候提到的2d纹理

brdfLUT着色器

//...
const uint SAMPLE_COUNT = 1024u;
for(uint i = 0u; i < SAMPLE_COUNT; ++i)
{ vec2 Xi = Hammersley(i, SAMPLE_COUNT); vec3 H = ImportanceSampleGGX(Xi, N, roughness); vec3 L = normalize(2.0 * dot(V, H) * H - V); float NdotL = max(L.z, 0.0); //??float NdotH = max(H.z, 0.0); //??float VdotH = max(dot(V, H), 0.0);if(NdotL > 0.0) { float G = GeometrySmith(N, V, L, roughness); float G_Vis = (G * VdotH) / (NdotH * NdotV); float Fc = pow(1.0 - VdotH, 5.0); A += (1.0 - Fc) * G_Vis; B += Fc * G_Vis; }
}
A /= float(SAMPLE_COUNT);
B /= float(SAMPLE_COUNT);
return vec2(A, B);
  • 注意:几何函数GSchlickGGX(n,v,k)=n⋅v(n⋅v)(1−k)+kG_{SchlickGGX}(n,v,k) = \frac{n⋅v}{(n⋅v)(1-k)+k}GSchlickGGX​(n,v,k)=(n⋅v)(1−k)+kn⋅v​中,k的值不再是kdirect=(α+1)28k_{direct} = \frac{(\alpha+1)^2}{8}kdirect​=8(α+1)2​,而是kIBL=α22k_IBL = \frac{\alpha^2}{2}kI​BL=2α2​,α\alphaα表示粗糙度

完成IBL的镜面反射

  • 首先,使用反射向量采样预过滤的环境贴图,获取表面的间接镜面反射。请注意,我们会根据表面粗糙度在合适的 mip 级别采样,以使更粗糙的表面产生更模糊的镜面反射。
  • 然后我们用已知的材质粗糙度和视线-法线夹角作为输入,采样 brdfLUT
uniform samplerCube prefilterMap;
uniform sampler2D brdfLUT;
void main()
{
//1.
vec3 R = reflect(-V, N);
const float MAX_REFLECTION_LOD = 4.0;
vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb;
//2.
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);//直接用间接光菲涅尔项F代替F0
}
  • 至此IBL的镜面反射计算完成

完成IBL

  • 上一篇完成的漫反射 + 这一篇完成的镜面反射
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)=全局光照
vec3 N = Normal;
vec3 V = normalize(camPos - WorldPos);
vec3 R = reflect(-V, N);
vec3 F0 = vec3(0.04);
vec3 Lo = vec3(0.0);
F0 = mix(F0, albedo, metallic);
vec3 L = normalize(lightPositions[i] - WorldPos);
vec3 H = normalize(V + L);
//...
vec3 kS = F;
Lo += (kD * albedo / PI + specular) * radiance * NdotL;//直接光照vec3 F = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);
//...
vec3 ambient = (kD * diffuse + specular) * ao;//间接光照(IBL)vec3 color = ambient + Lo;//全局光照
color = color / (color + vec3(1.0));// HDR tonemapping
color = pow(color, vec3(1.0/2.2)); // gamma correct
FragColor = vec4(color , 1.0);

PBR中引入IBL——镜面反射篇相关推荐

  1. PBR中引入IBL——漫反射篇

    文章目录 PBR中引入IBL--漫反射篇 IBL 分解渲染方程 irradiance map的生成和作用 irradiance map生成过程 环境纹理envTexture的格式 环境纹理envTex ...

  2. 关于pbr中镜面IBL低差异序列中的 Van Der Corput 序列

    源代码是这样的,乍一看的很难的,但其实仔细去解剖还是很好理解的 float RadicalInverse_VdC(uint bits) {bits = (bits << 16u) | (b ...

  3. C/C++基础进阶篇:C++11 中引入的 delete 描述符使用场景

    C++11 中引入的 delete 描述符主要有如下两个使用场景: 禁止编译器自动生成拷贝 Effective C++中提到 通过"私有化 + 只声明.不定义" 的方法禁止编译器生 ...

  4. PBR中BRDF的实现

    文章目录 PBR中BRDF的实现 白说 辐射度量学(Radiometry) Radiant flux Radiant Intensity 立体角(Solid Angle) 圆心角 Irradiance ...

  5. vue ajax highcharts,在vue项目中引入highcharts图表的方法(详解)

    npm进行highchars的导入,导入完成后就可以进行highchars的可视化组件开发了 npm install highcharts --save 1.components目录下新建一个char ...

  6. vue-cli构建的vue项目中引入stylus文件

    stylus是css预处理器.还有另外两种css预处理器语言详解:less.sass. 不懂的可以先看一下这篇文章:stylus预处理入门 在vue项目引入stylus css预处理器,可以让我们的c ...

  7. 如何在 Web Forms 中引入依赖注入机制

    依赖注入技术就是将一个对象注入到一个需要它的对象中,同时它也是控制反转的一种实现,显而易见,这样可以实现对象之间的解耦并且更方便测试和维护,依赖注入的原则早已经指出了,应用程序的高层模块不依赖于低层模 ...

  8. mock模拟接口测试 vue_在 Vue-CLI 中引入 simple-mock实现简易的 API Mock 接口数据模拟...

    在 https://www.jb51.net/article/151520.htm这篇文章中,我们介绍了在 Angular-CLI 中引入 simple-mock 的方法. 本文以 Vue-CLI 为 ...

  9. 引入 javascript_在您JavaScript项目中引入类型安全性? 再想一想

    引入 javascript by James Wright 詹姆斯·赖特(James Wright) 在您JavaScript项目中引入类型安全性? 再想一想 (Introducing Type Sa ...

最新文章

  1. 【Java源码分析】Vector源码分析
  2. 设计模式(结构型模式)——享元模式(Flyweight)
  3. android 消除标题,Android Activity 去掉标题栏及全屏显示
  4. [蓝桥杯2018初赛]字母阵列-单向dfs
  5. python中bar的用法_python使用matplotlib绘图 -- barChart
  6. 力扣算法题—075颜色分类
  7. 二级VB培训笔记06:窗体与常用控件综合案例【个人信息注册】
  8. 【LeetCode】【HOT】102. 二叉树的层序遍历(队列)
  9. hiberntate教程笔记6
  10. SSH: 关于remote主机上操作系统变更后SSH连接问题
  11. 多种方法去除按钮以及链接点击时虚线
  12. YOLOV5训练数据(火焰检测)
  13. fu7推挽胆机音质_fu7电子管功放电路图大全(6N8P\6P3P\胆机功放电路\耦合电容器) - 全文...
  14. 涂师傅手机数据恢复官方版
  15. apt cyg 安装php,Windows下安装Cygwin及apt-cyg
  16. 把日期横杠转化为斜杠
  17. 【NOIP2015提高组】信息传递
  18. libGDX学习之路01(续):把libGDX项目部署到iOS
  19. 东南大学计算机专硕录取分数线,东南大学研究生录取分数线
  20. win11任务栏图标闪烁|任务栏QQ图标闪动|新消息任务栏自动弹出|设置自动隐藏任务栏之后,QQ或微信等工具新消息自动弹出任务栏并颜色提示问题解决方案

热门文章

  1. 划分训练集、测试集,制作自己的数据集
  2. sql2java-excel(一):基于apache poi实现数据库表的导出及支持spring web
  3. ev3和python哪个好_乐高教育EV3比SPIKE Prime更好的十个理由!
  4. 流失预测模型实证-Pareto/NBD模型
  5. 一分钟教你们证件照如何换背景颜色,快来收藏
  6. RFID标签的基础知识(3)--了解芯片(之超高频标签芯片篇)
  7. ESP32----NVS使用
  8. 计算机考研与就业的利弊分析,考研和就业怎么选择 考研和就业的利弊分析
  9. Tensorflow+gensim实现文章自动审核功能
  10. win10打开计算机加载很慢,win10电脑文件夹打开特别慢怎么回事 文件夹假死问题快速解决步骤...