HBAO的水平角度限制必须手动实现,因其需在切空间中对采样方向投影并用atan2或等价余弦不等式判断方位角是否在阈值内,标准SSAO无此概念。

为什么 HBAO 的水平角度限制必须手动实现
标准 SSAO 实现里没有“水平角度”这个概念——它只采样深度缓冲区,算一个简单的遮蔽因子。HBAO 的核心改进是:只考虑与当前像素法线夹角小于 90°(即朝上的)的采样点,再进一步限制为仅在水平面(XY 平面)投影后落在某个角度范围内的方向。这个“水平角度限制”不是 OpenGL 或 Vulkan 自带的功能,必须在 shader 中对采样方向做显式投影和极角判断。
常见错误是直接用 dot(viewDir, normal) 过滤,这只能剔除背向摄像机的点,无法约束“水平方向分布”。真正要限制的是采样向量在切空间下的 atan2(x, z)(假设 Y 为法线方向),然后检查其是否落在 [-θ, θ] 区间内。
- 切空间构建必须稳定:用
normalize(cross(normal, worldUp))构造切线时,worldUp若与normal接近平行(如顶面法线 ≈ (0,1,0)),会导致切线抖动;应改用分支安全的orthonormalBasis构造 - 角度阈值建议用弧度传入 uniform,例如
u_hbaoAngleRad = 0.785f(45°),避免 shader 内反复计算degrees→radians - 不要在 for 循环外预计算所有采样方向——HBAO 要求每个像素的采样方向都相对于其自身法线旋转,否则会丢失几何保真度
如何在 GLSL 中正确实现水平方向采样筛选
关键不是“生成一堆方向再过滤”,而是在生成每一轮采样向量时就约束其水平方位角。典型做法是:先在单位圆上生成均匀分布的角度 theta,再结合半径 r 和高度偏移 zOffset 构造切空间坐标,最后变换到世界空间并测试是否朝上且水平角度合规。
示例片段(简化版):
用 Godot 4 + GDScript 开发 3D 游戏的端到端工作流。触发场景:"做一个 3D 游戏"、 "第一人称/第三人称/FPS/TPS/3D 平台跳跃"、"用 Godot 做 3D"、"加个 3D 角色控制器/ 相机/敌人寻路/关卡"、"导入 glb 模型不显示"、"3D 场景太暗/太卡"、"make a 3D game"、 "first-per
vec3 sampleDir = vec3(cos(theta) * r, zOffset, sin(theta) * r); vec3 worldSample = T * sampleDir.x + B * sampleDir.y + N * sampleDir.z; float ndotl = dot(normal, worldSample); // 水平角度限制:把 worldSample 投影到法线垂直平面,再转极角 vec2 proj = vec2(dot(worldSample, T), dot(worldSample, B)); // 在切-副法线平面上的坐标 float angle = atan(proj.y, proj.x); // 注意顺序:y,x → [-π, π] if (abs(angle) > u_hbaoAngleRad || ndotl
-
T和B必须是正交归一化的切线/副法线,不能直接用cross(N, vec3(0,1,0))硬编码 -
zOffset应随采样半径r缩放(如zOffset = r * 0.1),否则远处采样点易穿透表面 - 使用
atan(y,x)而非acos或asin,避免因浮点精度导致的nan或区间跳跃
HBAO 性能陷阱:别让角度判断拖垮循环展开
现代 GPU 上,if 分支本身不致命,但若编译器无法将角度判断优化为 predicated execution(比如依赖纹理查表或复杂三角函数),会导致 shader core 大量空转。尤其当采样数 >16 且每轮都调 atan 时,指令延迟明显上升。
- 把
u_hbaoAngleRad的余弦值(cos(u_hbaoAngleRad))作为 uniform 传入,用dot(proj, proj) * cos²θ > proj.x²替代atan判断——这是等价的极角不等式变形,完全免去三角函数 - 确保采样循环被 unroll:用
#pragma unroll(Vulkan/GLSL 450)或显式展开(如for (int i = 0; i ),避免动态长度循环触发保守执行 - 如果用 compute shader 实现 HBAO,注意
groupShared内存无法用于跨线程角度筛选——水平限制必须 per-pixel 独立完成
为什么屏幕空间法线贴图会让 HBAO 角度限制失效
很多项目把高模烘焙的法线存在 GBuffer 的 gNormal 中,但该法线是切线空间、经压缩(如 STS)、且未反量化。直接用它构建切空间会导致 T/B 方向错误,进而让 proj.x/proj.y 坐标失真,水平角度筛选完全跑偏。
- 务必在 GBuffer 写入前,用原始世界法线(而非贴图采样结果)重建
T/B;或者在 HBAO shader 中对法线贴图做完整解包 + 世界空间转换 - 检查法线贴图是否启用 sRGB 读取——若误启,
texture(gNormalTex, uv).xyz会过暗,导致normalize()后方向坍缩 - 移动端 Mali GPU 对
dot精度敏感,建议在角度筛选前加proj = normalize(proj) * 0.999防止边界处数值溢出
水平角度限制看着只是加个 if,实际牵扯切空间稳定性、浮点安全、GPU 执行模型和 GBuffer 数据质量。漏掉任一环,HBAO 就退化成带方向偏好的 SSAO,失去“水平遮蔽”的物理意义。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










