GraphicsLab Project之基于物理的着色系统(Physical based shading) - 基于图像的光照(Image Based Lighting)(Diffuse篇)
LearnOpenGL-IBL-Diffuse-irradiance
在 图形学基础 | 基于物理的渲染PBR之直接光照 中. 直接光照可以简化 反射方程中的积分运算. 转而使用简单的每个独立光源计算光照直接叠加.
其中. 我们没有加入环境光. 或者说我们加入了一个固定的环境光.
这样还不够真实.
因为当光源照射到其他物体上的时候,一定也会反射,其中就有很多反射的光线会反射到该物体上了.
引入物体与环境之间的光照关系.可以更加真实地表现场景.
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(wi⋅n)(wo⋅n)DFG)Li(wi⋅n)dwi
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(wi⋅n)dwi
将周围环境的所有光照信息保存在一张环境贴图中,而这个环境贴图就模拟了所有的 L i L_i Li。
一般业界使用 使用 Cube Map
来计算 IBL
需要涉及以下几个步骤:
CubeMap
保存CubeMap
需要做的几个步骤:
ERP采样成为CubeMap
OpenGL CubeMap图像方向问题
先预计算辐射光照贴图.
这个计算过程输入为一张环境贴图 CubeMap
.输出也是一张 CubeMap
.
如下图所示.
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(wi⋅n)dwi
由于 k d C π k_d {\frac C\pi } kdπC 是常数. 只需对余下的部分进行积分.
应用球形坐标系以及 d w i dw_i dwi的球形表达式可以将反射方程进行化简.
化简后的反射方程为:
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正方向的上半球做积分.
图形学中一般采样概率和采样的方式进行求解积分.
根据 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π2⋅N2π0∑N10∑N2L(θ,ϕ)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=kdC⋅N1⋅N2π0∑N10∑N2L(θ,ϕ)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));
特别注意:
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 的漫反射部分为低频分量. 所以可以使用较低的分辨率.
对预计算辐射光照图 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;
PBR中的环境光的漫反射部分的计算.
CubeMap
!黎曼和
来求解积分.CubeMap
中.