图形学基础 | 基于物理的渲染PBR之基于图像的光照IBL(Diffuse篇)

GraphicsLab Project之基于物理的着色系统(Physical based shading) - 基于图像的光照(Image Based Lighting)(Diffuse篇)
LearnOpenGL-IBL-Diffuse-irradiance

0 绪论

在 图形学基础 | 基于物理的渲染PBR之直接光照 中. 直接光照可以简化 反射方程中的积分运算. 转而使用简单的每个独立光源计算光照直接叠加.

其中. 我们没有加入环境光. 或者说我们加入了一个固定的环境光.
这样还不够真实.
因为当光源照射到其他物体上的时候,一定也会反射,其中就有很多反射的光线会反射到该物体上了.
引入物体与环境之间的光照关系.可以更加真实地表现场景.

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

PBR里的环境光. 使用的是 IBL.
一般来说. 会使用一张 环境贴图(Environment Map) 来保存环境信息.

环境光也是拆成两个部分. 即漫反射和镜面高光反射.

L o = ∫ Ω ( k d C π + D F G 4 ( w i ⋅ n ) ( w o ⋅ n ) ) L i ( w i ⋅ n ) d w i \begin{array}{l}L_o=\int_\Omega (k_d\frac C\pi+\frac{DFG}{4\left(w_i\cdot n\right)\left(w_o\cdot n\right)})L_i\left(w_i\cdot n\right) \\\end{array}dwi Lo=Ω(kdπC+4(win)(won)DFG)Li(win)dwi

2 漫反射部分

L o = ∫ Ω k d C π L i ( w i ⋅ n ) d w i L_o=\int_\Omega k_d\frac C\pi L_i\left(w_i\cdot n\right)dw_i Lo=ΩkdπCLi(win)dwi

将周围环境的所有光照信息保存在一张环境贴图中,而这个环境贴图就模拟了所有的 L i L_i Li

3 具体计算

一般业界使用 使用 Cube Map 来计算 IBL
需要涉及以下几个步骤:

  1. 获得环境贴图. 用 CubeMap 保存
  2. 预计算辐射光照贴图
  3. IBL光照计算

3.1 获得环境贴图 CubeMap

需要做的几个步骤:

  1. 读取 .hdr 文件
  2. 将 ERP 通过采样得到 CubeMap
    可能涉及到:

ERP采样成为CubeMap
OpenGL CubeMap图像方向问题

3.2 预计算辐射光照贴图

先预计算辐射光照贴图.
这个计算过程输入为一张环境贴图 CubeMap .输出也是一张 CubeMap .
如下图所示.
图形学基础 | 基于物理的渲染PBR之基于图像的光照IBL(Diffuse篇)_第1张图片

1 先看下反射方程

L o = ∫ Ω k d C π L i ( w i ⋅ n ) d w i L_o=\int_\Omega k_d\frac C\pi L_i\left(w_i\cdot n\right)dw_i Lo=ΩkdπCLi(win)dwi
由于 k d C π k_d {\frac C\pi } kdπC 是常数. 只需对余下的部分进行积分.
应用球形坐标系以及 d w i dw_i dwi的球形表达式可以将反射方程进行化简.
图形学基础 | 基于物理的渲染PBR之基于图像的光照IBL(Diffuse篇)_第2张图片
化简后的反射方程为:
L o = k d C π ∫ ϕ ∫ θ L i ( p , θ i , φ i ) cos ⁡ ( θ ) sin ⁡ ( θ ) d θ d φ L_o=k_d\frac C\pi\int_\phi\int_\theta L_i\left(p,\theta_i,\varphi_i{}\right)\cos(\theta)\sin(\theta)d\theta d\varphi Lo=kdπCϕθLi(p,θi,φi)cos(θ)sin(θ)dθdφ
其中,积分的定义域是以法线 n n n正方向的上半球做积分.

  • θ \theta θ的取值范围 [0, π / 2 \pi/2 π/2].
  • φ \varphi φ的取值范围 [0, π \pi π].

图形学基础 | 基于物理的渲染PBR之基于图像的光照IBL(Diffuse篇)_第3张图片

2. 如何计算积分

图形学中一般采样概率和采样的方式进行求解积分.

根据 Riemann Sum(黎曼和)

可以将反射方程按以下方式求解:
L o = k d C π 2 π N 1 π 2 ⋅ N 2 ∑ 0 N 1 ∑ 0 N 2 L ( θ , ϕ ) cos ⁡ ( θ ) sin ⁡ ( θ ) L_o=k_d\frac C\pi\frac{2\pi}{N1}\frac\pi{2\cdot N2}\sum_0^{N1}\sum_0^{N2}L(\theta,\phi)\cos(\theta)\sin(\theta) Lo=kdπCN12π2N2π0N10N2L(θ,ϕ)cos(θ)sin(θ)
即:
L o = k d C ⋅ π N 1 ⋅ N 2 ∑ 0 N 1 ∑ 0 N 2 L ( θ , ϕ ) cos ⁡ ( θ ) sin ⁡ ( θ ) L_o=k_dC\cdot\frac\pi{N1\cdot N2}\sum_0^{N1}\sum_0^{N2}L(\theta,\phi)\cos(\theta)\sin(\theta) Lo=kdCN1N2π0N10N2L(θ,ϕ)cos(θ)sin(θ)

求解的代码如下:

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

特别注意:

  • 如何将 tangent space to world
  • 每个面片输出的 该法线 n n n为出射光线的IBL漫反射部分.

3 OpenGL API 操作

1 创建 CubeMap 空间

unsigned int irradianceMap;
glGenTextures(1, &irradianceMap);
glBindTexture(GL_TEXTURE_CUBE_MAP, irradianceMap);
for (unsigned int i = 0; i < 6; ++i)
{
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 32, 32, 0, 
                 GL_RGB, GL_FLOAT, nullptr);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

2 预计算是离屏渲染计算. 所以需要FBO

glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
glBindRenderbuffer(GL_RENDERBUFFER, captureRBO);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 32, 32);

3 进行计算

irradianceShader.use();
irradianceShader.setInt("environmentMap", 0);
irradianceShader.setMat4("projection", captureProjection);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, envCubemap);

glViewport(0, 0, 32, 32); // don't forget to configure the viewport to the capture dimensions.
glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
for (unsigned int i = 0; i < 6; ++i)
{
    irradianceShader.setMat4("view", captureViews[i]);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 
                           GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradianceMap, 0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    renderCube();
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);  

由于 IBL 的漫反射部分为低频分量. 所以可以使用较低的分辨率.

3.3 IBL光照计算

对预计算辐射光照图 CubeMap 进行采样. 计算 IBL的diffuse.

用什么进行采样呢?

  • 用每个片元的 法线
N = normalize(normal);
vec3 ambient = texture(irradianceMap, N).rgb;

还需要 考虑 k d k_d kd a o ao ao

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; 

4 总结

PBR中的环境光的漫反射部分的计算.

  1. 采样 CubeMap !
  2. 通过球形坐标系来简化反射方程.
  3. 通过 黎曼和 来求解积分.
  4. 最后的结果也是存储在 CubeMap 中.

你可能感兴趣的:([图形学基础])