PBR_IBL镜面部分
- 镜面部分并不能像漫反射部分一样将BRDF部分像常量一样提取出来,因为它整个积分上不是常数,因为它受到wi和w0的影响,就比如一个x的等式,不能把x部分提取出来一样,他是随着等式变化的
- 如果试图解算所有入射光方向加所有可能的视角方向的积分,二者组合数会极其庞大,实时计算太昂贵。
- 我们把镜面部分再次拆分为两个部分:镜面部分再次分解(近似):积分ab = 积分a * 积分b
- 分割求和近似法(split sum approximation):预滤波环境贴图(辐照度) + BRDF积分贴图
- 镜面部分(分解后:Cook-Torrance 积分的BRDF,积分的镜面辐照度(考虑粗糙度和重要性采样)
预滤波环境贴图(镜面部分):
- 这次会考虑粗糙度,因为对于不同粗糙度的表明会使用不同的mip图采样
- 粗糙度越高,采样越分散,贴图越模糊,将保存到不同的mipmap级别的贴图中,
- 为了确保为其 mip 级别分配足够的内存,一个简单方法是调用 glGenerateMipmap(GL_TEXTURE_CUBE_MAP)。
- GL_LINEAR_MIPMAP_LINEAR 启用三线性过滤
- mip 级别每增加一级,尺寸缩小为一半。
重要性采样:
- 如果使用均匀的半球采样,计算镜面部分的辐照度,效果会很差,镜面反射依赖于表面的粗糙度,形成镜面波瓣,如果以类似方式选取采样向量将是有意义的,因此需要重要性采样
- 如果要预计算,我们并不知道视角方向,如果我们假设视角方向==输出采样方向w0,这个部分中就不用考虑视角方向,从而预计算,虽然效果不是很好
- 对于视线wo方向:N法线 = R反射向量 = V视线方向
- //
- 重要性采样:具有更快收敛速度的采样
-
蒙特卡洛积分(完全随机 | 伪随机):
-
蒙特卡洛积分:离散的解决问题,不必考虑所有
-
在 a 到 b 上采样 N 个随机样本的子集,求和并求平均数,pdf 代表概率密度函数
-
随着样本数量的不断增加,我们最终将收敛到积分的精确
-
估算是有偏的,集中于特定的值或方向,具有更快的收敛速度,但是可能永远不会收敛到精确解
-
-
//
-
低差异序列(均匀 | 半随机):
-
该序列生成的仍然是随机样本,但样本分布更均匀:具有更快的收敛速度,它们会以更快的速度收敛到精确解,这使得它对于性能繁重的应用很有用。
-
Hammersley 序列(XI):该序列是把十进制数字的二进制表示镜像翻转到小数点右边而得
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)//(i在n个样本中的第i个样本,N个子样本)生成低差异序列
{
return vec2(float(i)/float(N), RadicalInverse_VdC(i));
}
-
//
-
GGX几何函数 重要性采样:
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);
// from spherical coordinates to cartesian coordinates - halfway vector
vec3 H;
H.x = cos(phi) * sinTheta;
H.y = sin(phi) * sinTheta;
H.z = cosTheta;
// from tangent-space H vector to world-space sample vector计算TBN
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);
}
-
有别于均匀或纯随机地,采样会根据粗糙度a,偏向微表面的半向量h的宏观反射方向,将 GGX 和 NDF应用到采样中。使用低差异序列值 Xi作为输入来生成采样向量:
-
我们需要一些方法定向和偏移采样向量,以使其朝向特定粗糙度的镜面波瓣方向:
预过滤 mipmap 级别

-
帧缓冲区缩放到适当的 mipmap 尺寸, mip 级别每增加一级,尺寸缩小为一半。
-
访问该贴图时指定的 mip 等级越高,获得的反射就越模糊。
-
基于积分的 PDF 和粗糙度采样环境贴图的 mipmap
预过滤卷积的伪像:接缝和亮点:

-
较低的 mip 级别具有更低的分辨率,并且预过滤贴图代表了与更大的采样波瓣卷积,因此缺乏立方体的面和面之间的滤波的问题就更明显:
-
可以启用 GL_TEXTURE_CUBE_MAP_SEAMLESS,以为我们提供在立方体贴图的面之间进行正确过滤的选项:
-
//
-
对镜面反射进行卷积需要大量采样,才能正确反映 HDR 环境反射的混乱变化。
-
我们可以在预过滤卷积时,开启三线性过滤,不直接采样环境贴图,而是采样mapmap
BRDF积分贴图:

- 假设每个方向的入射辐射度L都是白色的,在给定粗糙度、光线 ωi 法线 n 夹角 n⋅ωi 的情况下,可以预计算结果,
- 使用RGB通道的纹理,R为菲涅耳响应的系数,G为偏差值,
n⋅ωi为横坐标,粗糙度作为纵坐标 - 需要生成一张 512 × 512 分辨率的 2D 纹理
- 对于brdf部分,我们可以再次移项变换和近似,将得到这个公式
合成最终颜色:


- 根据菲涅尔fresnelSchlick(法线n视线v的点乘,f0,粗糙度)返回ks计算镜面部分占比,1-ks = kd计算漫反射部分占比
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;//漫反射LBL
vec3 diffuse = irradiance * albedo;//漫反射部分
const float MAX_REFLECTION_LOD = 4.0;
vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb;//镜面LBL
vec2 envBRDF = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg;//BRDF
vec3 specular = prefilteredColor * (F * envBRDF.x + envBRDF.y);//镜面部分
vec3 ambient = (kD * diffuse + specular) * ao; //计算顶点的环境光