文章目录
PBR中引入IBL——漫反射篇
IBL
- IBL(Image based lighting,基于图像的光照)是一类光照技术的集合
- 在IBL中,将周围环境整体视为一个大光源
- IBL通常使用环境立方体贴图实现,可以理解为:将立方体贴图的采样值认为是渲染方程的计算结果,使用这个采样值可以在着色器中直接进行光照计算
- 环境立方体贴图取自现实世界或从场景中生成
- IBL是一种比较精确的环境光照输入格式,也可以说是一种间接光照的粗略近似(全局光照可以理解为直接光照+间接光照,IBL只相当于间接光照部分)
分解渲染方程
- Cook-Torrance反射方程:
L o ( p , ω o ) = ∫ Ω ( k d c π + k s D F G 4 ( ω o ⋅ n ) ( ω i ⋅ n ) ) L i ( p , ω i ) n ⋅ ω i d ω i L_o(p,\omega_o) = \int\limits_\Omega(k_d\frac{c}{π} + 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)=Ω∫(kdπc+ks4(ωo⋅n)(ωi⋅n)DFG)Li(p,ωi)n⋅ωidωi
L o ( p , ω o ) = ∫ Ω ( k d c π ) L i ( p , ω i ) n ⋅ ω i d ω i + ∫ Ω ( k s D F G 4 ( ω o ⋅ n ) ( ω i ⋅ n ) ) L i ( p , ω i ) n ⋅ ω i d ω i L_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_i Lo(p,ωo)=Ω∫(kdπc)Li(p,ωi)n⋅ωidωi+Ω∫(ks4(ωo⋅n)(ωi⋅n)DFG)Li(p,ωi)n⋅ωidωi
-
本篇内容只关注前半部分,即漫反射部分,所以就有:
L o ′ ( p , ω o ) = ∫ Ω ( k d c π ) L i ( p , ω i ) n ⋅ ω i d ω i L_o'(p,\omega_o) = \int\limits_\Omega(k_d\frac{c}{π})L_i(p,\omega_i)n\cdot\omega_id\omega_i Lo′(p,ωo)=Ω∫(kdπc)Li(p,ωi)n⋅ωidωi -
提出常数部分:
L o ′ ( p , ω o ) = k d c π ∫ Ω L i ( p , ω i ) n ⋅ ω i d ω i L_o'(p,\omega_o) = k_d\frac{c}{π}\int\limits_\Omega L_i(p,\omega_i)n\cdot\omega_id\omega_i Lo′(p,ωo)=kdπcΩ∫Li(p,ωi)n⋅ωidωi -
结合IBL的思路,将 ∫ Ω L i ( p , ω i ) n ⋅ ω i d ω i \int\limits_\Omega L_i(p,\omega_i)n\cdot\omega_id\omega_i Ω∫Li(p,ωi)n⋅ωidωi的结果存入一个立方体纹理,我们给这个纹理起个名字叫irradiance map,当需要 ω o \omega_o ωo方向上间接光的漫反射时,只用对irradiacne map采样即可
irradiance map的生成和作用
- 在生irradiance map时,会遇到一系列问题:
irradiance map生成过程
-
首先要有一个环境纹理envTexture,这个envTexture的目的是提供 ω i \omega_i ωi方向上的radiance,即 L i ( p , ω i ) L_i(p,\omega_i) Li(p,ωi)
-
envTexture可以是由美术人员提前做好的、也可以是在场景中提前生成的,具有HDR颜色值的立方体纹理、或者是可以变换成立方体纹理的其他类型纹理
-
irradiance map是在envTexture基础上生成的
-
通过对envTexture上每个纹素的半球 Ω 上所有可能的 ω i \omega_i ωi进行采样,来计算 ∫ Ω L i ( p , ω i ) n ⋅ ω i d ω i \int\limits_\Omega L_i(p,\omega_i)n\cdot\omega_id\omega_i Ω∫Li(p,ωi)n⋅ωidωi,并将结果保存到irradiance map中,即生成irradiance map;这个计算结果表示点p在所有可能的 ω i \omega_i ωi上的irradiance的和,这个结果再乘以 k d c π k_d\frac{c}{\pi} kdπc就等于 ω o \omega_o ωo方向上的radiance,即 L o ′ ( p , ω o ) L_o'(p,\omega_o) Lo′(p,ωo)
-
用来采样envTexture的半球,要面向 ω o \omega_o ωo方向
-
对于生成的irradiance map,通过每个采样方向 ω o \omega_o ωo,得到的是场景中所有能够击中 ω o \omega_o ωo的表面的间接漫反射光的总和
-
此后就可以通过采样irradiance map获取间接光照的漫反射,进行光照计算
-
生成irradiance map的过程看似简单,却每一步都暗藏玄机
环境纹理envTexture的格式
- 由上一篇的内容可知,PBR渲染中一定要使用HDR颜色值,而这个envTexture当然也不能例外
- 因此这里要引入一个新的图片格式——“.hdr”,这种格式的图片专门用来存储HDR颜色值
环境纹理envTexture的类型
- 一般常见的envTexture并不是立方体纹理,而是一个等距柱状投影图的2d纹理
- 等距柱状投影图(Equirectangular Map)的特点:图像扭曲,水平视角附近分辨率较高,而底部和顶部方向分辨率较低
- 因此需要将这种envTexture变换成立方体贴图envCubeTexture
- 此后就是对envCubeTexture进行操作了
计算irradiance map
-
实际上,不可能对envCubeTexture的每个纹素,在半球Ω的所有可能的 ω i \omega_i ωi进行采样,我们采用的方法是:在半球Ω的大量 ω i \omega_i ωi上进行离散采样,并对其采样值 L i L_i Li进行积分计算后再取平均值,这个过程其实是对envCubeTexture进行卷积
-
卷积的特性是,对数据集其中的一个条目做一些计算时,要考虑到数据集其中的所有其他条目;这里的数据集就是场景的 L i L_i Li或envCubeTexture
-
在求积分时,积分变量 d ω d\omega dω比较难处理,将其变换成球坐标的 θ \theta θ和 ϕ \phi ϕ,如下图
-
在变换了积分变量后, n ⋅ ω d ω i = c o s θ d ϕ d θ n\cdot \omega d\omega_i = cos\theta d\phi d\theta n⋅ωdωi=cosθdϕdθ,所以整个积分变成了
L o ′ ( p , ϕ o , θ o ) = k d c π ∫ ϕ = 0 2 π ∫ θ = 0 1 2 π L i ( p , ϕ i , θ i ) c o s θ d ϕ d θ L_o'(p,\phi_o,\theta_o) = k_d\frac{c}{\pi}\int_{\phi=0}^{2\pi}\int_{\theta=0}^{\frac{1}{2}\pi}L_i(p,\phi_i,\theta_i)cos\theta d\phi d\theta Lo′(p,ϕo,θo)=kdπc∫ϕ=02π∫θ=021πLi(p,ϕi,θi)cosθdϕdθ -
然而,由于球的一般性质,当采样区域朝向中心顶部会聚时,天顶角 θ 变小,半球的离散采样区域也变小;为了平衡较小的区域贡献度,使用 sinθ 来权衡区域的贡献度,即:
L o ′ ( p , ϕ o , θ o ) = k d c π ∫ ϕ = 0 2 π ∫ θ = 0 1 2 π L i ( p , ϕ i , θ i ) c o s θ s i n θ d ϕ d θ L_o'(p,\phi_o,\theta_o) = k_d\frac{c}{\pi}\int_{\phi=0}^{2\pi}\int_{\theta=0}^{\frac{1}{2}\pi}L_i(p,\phi_i,\theta_i)cos\theta sin\theta d\phi d\theta Lo′(p,ϕo,θo)=kdπc∫ϕ=02π∫θ=021πLi(p,ϕi,θi)cosθsinθdϕdθ -
积分的求解需要我们在半球 Ω 内采集固定数量的离散样本并对其结果求平均值;分别给每个球坐标轴指定离散样本数量 n1 和 n2 求其黎曼和( ∫ a b f ( x ) d x = ∑ k = 1 n f ( ε k ) Δ x k \int_a^bf(x)dx = \sum_{k=1}^{n}f(\varepsilon_k)\Delta x_k ∫abf(x)dx=∑k=1nf(εk)Δxk),即:
L o ′ ( p , ϕ o , θ o ) = k d c π 1 n 1 n 2 ∑ ϕ = 0 n 1 ∑ θ = 0 n 2 L i ( p , ϕ i , θ i ) c o s θ s i n θ d ϕ d θ L_o'(p,\phi_o,\theta_o) = k_d\frac{c}{\pi}\frac{1}{n1n2}\sum_{\phi=0}^{n1}\sum_{\theta=0}^{n2}L_i(p,\phi_i,\theta_i)cos\theta sin\theta d\phi d\theta Lo′(p,ϕo,θo)=kdπcn1n21ϕ=0∑n1θ=0∑n2Li(p,ϕi,θi)cosθsinθdϕdθ -
在变换积分变量后,对envCubeTextre采样的向量也有相应的变化:用 θ \theta θ和 ϕ \phi ϕ表示空间中的方向v=(sinθcosϕ,sinθsinϕ,cosθ);球坐标的z轴正方向始终是竖直向上,因此这个向量v相当于是切线空间中的坐标,在对envCubeTexter进行采样时,要将他变换到世界坐标
-
在绘制立方体贴图时,有一个方便的特性:顶点坐标与纹理坐标一致
-
在生成irradiance map时,假设对于envCubeTexture的每个片段,表面的半球朝向法向量 N ,对envCubeTexture进行卷积等于计算朝向 N 的半球 Ω 中每个 ω i \omega_i ωi方向的总平均irradiance。
vec3 N = normalize(WorldPos);
vec3 irradiance = vec3(0.0);
vec3 up = vec3(0.0, 1.0, 0.0);
vec3 right = cross(up, N);
up = cross(N, 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)
{
vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;
irradiance += texture(envCubeTexture, sampleVec).rgb * cos(theta) * sin(theta); nrSamples++;
}
}
irradiance = PI * irradiance * (1.0 / float(nrSamples));//为啥乘以pi???
- 完成这一步,我们就得到了一个预计算好的irradiance map,可以直接将其用于IBL 计算
计算间接光照中的漫反射
- 给定一张irradiance map,它存储了场景中的所有间接漫反射光,获取片段的irradiance就简化为给定法线的一次纹理采样:
//顶点着色器
//...
layout (location = 2) in vec3 aNormal;
uniform mat4 model;
out vec3 Normal;
void main()
{
Normal = mat3(model) * aNormal;
//...
}
//片段着色器
in vec3 Normal;
void main()
{
//...
vec3 irradiance = texture(irradianceMap, Normal).rgb;
//...
}
- 由于间接光照包括漫反射和镜面反射两部分,我们可以使用菲涅耳函数来计算表面的间接反射率 k d k_d kd
- 由于环境光来自半球内围绕法线 N 的所有方向,因此没有一个确定的半程向量来计算菲涅耳函数;为了模拟菲涅耳函数,我们用法线和视线之间的夹角计算菲涅耳系数,但是这样的近似忽略了表面粗糙度的影响,我们可以通过在 Sébastien Lagarde 提出的 Fresnel-Schlick 方程中加入粗糙度项来缓解这个问题:
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
{
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}
void main()
{
//...
vec3 kS = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);
vec3 kD = 1.0 - kS;
vec3 irradiance = texture(irradianceMap, N).rgb;
//..
}
- 根据 L o ′ ( p , ω o ) = k d c π ∫ Ω L i ( p , ω i ) n ⋅ ω i d ω L_o'(p,\omega_o) = k_d\frac{c}{\pi}\int\limits_\Omega L_i(p,\omega_i)n\cdot \omega_id\omega Lo′(p,ωo)=kdπcΩ∫Li(p,ωi)n⋅ωidω
vec3 diffuse = Lo' = kD * albedo * irradiance;//为啥没有pi分之一???
效果对比
-
只有间接漫反射光的效果
-
可见漫反射的作用:金属没有漫反射所以表现的比较暗,非金属主要是靠漫反射提供颜色或亮度
-
下一篇将介绍间接光照中的镜面反射部分,他将长这个样子
-
可见镜面反射的作用:可以使表面光滑的物体反射出周围的环境,金属主要靠镜面反射提供颜色,而非金属主要靠漫反射提供颜色,所以最下边一排的非金属小球都显示了黑色
-
将漫反射和镜面反射合起来
-
对于传说中的全局光照,还差一个直接光照,现再将直接光照加上就会是这个样子
-
可以看出所有的物体的亮度又增加了,而且光滑物体的表面出现了的高光