PBR之基于图像的光照IBL (Diffuse)

基于图像的光照(Image Based Lighting)

在开始之前,我们先来了解下什么是基于图像的光照(IBL)。一个物体,不会单独的存在一个空空的环境里面,它的周围一定有其他的物体。当光源照射到其他物体上的时候,一定也会反射,其中就有很多反射的光线会反射到该物体上去。上一篇文章中我们模拟的是直接光照。对于直接光照系统,像上面那种其他物体反射过来的光,我们一般就只是使用一个Ambient项来模拟。这种模拟方法只能够模拟单调的环境光照效果,想要更加丰富,更加精细的效果,我们就需要使用更加丰富的环境光照系统,而IBL就是实现它的一种方式。

一般来说,我们通过一张环境贴图(Environment Map)来保存一个物体周围的环境信息,然后通过某种处理,来实现丰富的环境光照效果。本文就是讲述,如何通过对环境贴图进行处理,然后实现丰富的环境光照效果。

从渲染方程解释IBL

根据前面对环境光照的描述,环境光照也应该符合这个公式,只不过相对于直接光照,它需要计算更多的入射光线。

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}{\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 Lo(p,ωo)=Ω(kdπc+ks4(ωon)(ωin)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}{\pi}) L_i(p,\omega_i) n \cdot \omega_i d\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_i d\omega_i Lo(p,ωo)=Ω(kdπc)Li(p,ωi)nωidωi+Ω(ks4(ωon)(ωin)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) = k_d\frac{c}{\pi} \int\limits_{\Omega} L_i(p,\omega_i) n \cdot \omega_i d\omega_i Lo(p,ωo)=kdπcΩLi(p,ωi)nωidωi

对于这个方程,我们就可以将周围环境的所有光照信息保存在一张环境贴图中,而这个环境贴图就模拟了所有的。

环境贴图

在图形领域,用于保存周围环境信息的环境贴图有多种形式,如

现在业界,对于IBL普遍使用的是Cube Map的形式。本篇文章也将主要使用Cube Map来进行IBL。

HDR对于PBR非常重要,没有了HDR,PBR的效果将大大折扣。所以,对于IBL来说,我们依然需要使用HDR。也就说,对于周围环境光照的描述,需要通过HDR的格式文件来保存。

本文的所有使用的环境光照贴图将从sIBL中获取,这个网站里面有很多免费使用的HDR光照贴图,我们将从这些图中选择一些进行测试。

PBR之基于图像的光照IBL (Diffuse)_第1张图片

需要注意的是,这个网站里面的HDR贴图并不是CubeMap的形式,而是EquirectangularMap的形式进行保存的,所以接下来我们需要解决两个问题:如何读取.hdr文件,如何对这个贴图进行filter。

.hdr文件读取

在sIBL网站上,已经给出了.hdr文件格式的详细描述。我这里为了方便就直接使用了github上开源的stb_image库来读取.hdr文件。这个库里面都是一些单个文件的c代码库,感兴趣的读者可以自行探索。

得到.hdr文件保存的HDR数据了,然后可以通过图形API创建一个2D的HDR纹理

Equirectangular Map Filter

我们前面说过,我们将使用Cube Map来进行IBL。所以,我们需要一种方法来将该Equirectangular Map转换为Cube Map。为此,我们先简单的绘制一个球体,然后将这个Equirectangular Map贴上去,然后使用传统的创建Cube Map的方式产生一张Cube Map。

#version 330 core
out vec4 FragColor;
in vec3 WorldPos;

uniform sampler2D equirectangularMap;

const vec2 invAtan = vec2(0.1591, 0.3183);
vec2 SampleSphericalMap(vec3 v)
{
    vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
    uv *= invAtan;
    uv += 0.5;
    return uv;
}

void main()
{		
    vec2 uv = SampleSphericalMap(normalize(WorldPos));
    vec3 color = texture(equirectangularMap, uv).rgb;
    
    FragColor = vec4(color, 1.0);
}

通过计算atan(n.z, n.x)就能够得到具有该法线顶点的UV坐标的U值,通过计算asin(n.y)就能够得到具有该法线顶点的UV坐标的V值。同时,由于atan函数返回的结果在[−π,π]
之间,而asin返回的结果在[−π/2,π/2]之间,所有需要把它们都映射到[0,1]之间。

在得到了这个球体之后,我们就可以简单的使用传统的方法来创建CubeMap,主要就是通过设置FOV为90度的摄像机,分别朝着+X,-X,+Y,-Y,+Z,-Z去观察该球体,然后渲染CubeMap的6个面,从而得到一张HDR的CubeMap。

预计算辐射光照贴图

PBR之基于图像的光照IBL (Diffuse)_第2张图片

为了方便进行积分运算,一般都将渲染方程改为球面坐标的积分形式,其中:

n ⋅ ω i = cos ⁡ θ n⋅\omega_i=\cos\theta nωi=cosθ

d ω i = sin ⁡ θ d ϕ d θ d\omega_i=\sin\theta d\phi d\theta dωi=sinθdϕdθ

所以,方程转变为如下形式:

(1) L o ( p , ϕ o , θ o ) = k d c π ∫ ϕ = 0 2 π ∫ θ = 0 1 2 π L i ( p , ϕ i , θ i ) cos ⁡ θ sin ⁡ θ 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 \tag{1} Lo(p,ϕo,θo)=kdπcϕ=02πθ=021πLi(p,ϕi,θi)cosθsinθdϕdθ(1)

上述公式转换为Riemann Sum(黎曼和)的表述:

Riemann Sum是一种很简单的积分方法,当我们的步进值越小的时候,通过这种方法计算出来的h值就越加的接近真实值。

(2) L o ( p , ϕ o , θ o ) = k d c π 1 n 1 n 2 ∑ ϕ = 0 n 1 ∑ θ = 0 n 2 L i ( p , ϕ i , θ i ) cos ⁡ ( θ ) sin ⁡ ( θ ) d ϕ d θ L_o(p,\phi_o, \theta_o) = k_d\frac{c}{\pi} \frac{1}{n1 n2} \sum_{\phi = 0}^{n1} \sum_{\theta = 0}^{n2} L_i(p,\phi_i, \theta_i) \cos(\theta) \sin(\theta) d\phi d\theta \tag{2} Lo(p,ϕo,θo)=kdπcn1n21ϕ=0n1θ=0n2Li(p,ϕi,θi)cos(θ)sin(θ)dϕdθ(2)

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));

预计算辐射光照贴图(irradianceMap). 这个计算过程输入为一张环境贴图 CubeMap .输出也是一张 CubeMap. 如下图所示.

PBR之基于图像的光照IBL (Diffuse)_第3张图片

在球谐面卷积采样, 生成的图像有点类似模糊的效果。离线生成的irradianceMap大小就32x32, 精度不需要那么高, 中间值使用插值就可以得到。

整个流程:

equirectangularmap(.hdr) -> envCubemap -> irradianceMap

在引擎里看到的效果,就是如下所示:

PBR之基于图像的光照IBL (Diffuse)_第4张图片

你可能感兴趣的:(PBR之基于图像的光照IBL (Diffuse))