Ambient Occlusion(环境光遮蔽)

环境光遮蔽(AO)是一种近似全局光照效果的技术,通过估算每个表面点被周围几何体遮蔽的程度,模拟出接触阴影、角落变暗等真实感效果。

项目目标

  • 实现基于蒙特卡洛积分的 AO 渲染器
  • 在半球方向随机采样光线,统计遮蔽率
  • 构建包含多个球体和围合墙面的 Cornell Box 场景
  • 量化验证 AO 梯度(角落暗、开阔区域亮)

效果展示

AO渲染结果

场景细节:

  • 🔵 中央大球:球顶最亮(无遮蔽),底部接地点最暗(地面遮蔽)
  • 🔵 角落小球:被墙角和地面三面遮蔽,明显偏暗
  • 🟫 地面:球体正下方出现接触阴影,物理正确

核心原理

蒙特卡洛积分

AO 的数学定义是对法线半球上的可见性函数积分:

1
AO(p) = (1/π) ∫_Ω V(p, ω) · (ω · N) dω

其中 V(p, ω) 是可见性函数(1=未遮蔽,0=遮蔽),用蒙特卡洛方法估算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
double computeAO(const Vec3& p, const Vec3& N, const Scene& scene,
int numSamples, double maxDist) {
Vec3 T, B;
buildTBN(N, T, B); // 构造切线空间

int occluded = 0;
for (int i = 0; i < numSamples; ++i) {
Vec3 localDir = cosineSampleHemisphere(); // 余弦加权采样
Vec3 worldDir = localToWorld(localDir, N, T, B);

Ray aoRay { p + N * 1e-4, worldDir };
if (scene.occluded(aoRay, 1e-4, maxDist)) ++occluded;
}

return 1.0 - (double)occluded / numSamples;
}

余弦加权重要性采样

相比均匀半球采样,余弦加权采样让更多光线集中在法线方向附近(贡献最大的区域),减少噪声、加速收敛:

1
2
3
4
5
6
7
8
Vec3 cosineSampleHemisphere() {
double r1 = rand01(), r2 = rand01();
double phi = 2.0 * M_PI * r1;
// r2 = cos²θ → sinθ = sqrt(r2)
double sinTheta = sqrt(r2);
double cosTheta = sqrt(1.0 - r2);
return { cos(phi)*sinTheta, sin(phi)*sinTheta, cosTheta };
}

TBN 矩阵(Gram-Schmidt 正交化)

将采样方向从局部坐标(Z 轴 = 法线)变换到世界坐标:

1
2
3
4
5
6
void buildTBN(const Vec3& N, Vec3& T, Vec3& B) {
Vec3 up = (abs(N.x) < 0.9) ? Vec3(1,0,0) : Vec3(0,1,0);
T = up.cross(N).normalized();
B = N.cross(T);
// T, B, N 构成正交基
}

量化验证

程序内置量化验证,检查 AO 梯度是否物理正确:

测试点 AO 值 像素亮度 预期
球顶部 1.00 246 最亮 ✅
角落(墙角地面) 0.30 222 较暗 ✅
球底接地点 约0 145 最暗 ✅

亮度梯度:球顶(246) > 角落(222) > 球底接触(145),物理规律正确!

技术总结

今天掌握的技术点:

  1. 蒙特卡洛 AO 积分:随机采样估算遮蔽率,样本数越多噪声越小
  2. 余弦加权采样:PDF = cosθ/π,与被积函数形状匹配,比均匀采样高效
  3. TBN 矩阵构造:Gram-Schmidt 正交化是最稳健的方法,避免法线平行奇异
  4. 自交偏移(Bias):光线起点沿法线偏移 1e-4,解决浮点精度引起的自遮蔽
  5. 最大遮蔽距离:限制 AO 光线的最大距离,模拟局部光照(而非全局)

与 Phong/PBR 的区别:

  • Phong/PBR 描述直接光照(光源→表面→眼睛)
  • AO 描述间接光照的遮蔽(环境光被周围几何体阻挡)
  • 现代渲染管线通常将 AO 叠加到直接光照上,得到更真实的效果

代码仓库

GitHub: https://github.com/chiuhoukazusa/daily-coding-practice/tree/main/2026/02/02-28-ambient-occlusion


完成时间: 2026-02-28 05:35
迭代次数: 1 次(一次编译通过)
运行时间: ~15 秒(64 samples/pixel, 800×600, 2×2 SSAA)
编译器: g++ 12.x (C++17, -O2)