光照贴图烘焙时出现噪点,本质上是积分估计的方差没有收敛。光照贴图通常通过光线追踪在UV空间中为每个纹素计算间接光照,每一条采样光线都带有随机性。样本数量越低,估计结果波动越大,最终写入光照贴图纹理时就会形成颗粒状噪声。理解这一点之后,优化方向就分成两类:在采样阶段提高样本利用率,以及在输出阶段对光照贴图做去噪处理。

一、光照贴图噪点的来源与采样参数
光照贴图API在生成光照数据时,通常会对每个纹素发射多条光线进行半球采样。如果样本数设置为32或64,低粗糙度表面可能勉强可用,但高间接光区域或几何遮挡复杂的角落会出现明显噪点。噪点并不是bug,而是蒙特卡洛积分的统计误差。
以自研光照贴图API为例,采样数直接控制每个纹素的光线数量。提高采样数可以降低方差,但烘焙时间会线性甚至超线性增长。更合理的做法是先分析场景中哪些区域需要更多采样。例如靠近墙角、遮挡物密集、法线变化剧烈的区域,噪点更容易出现。可以通过自适应采样让这些区域的纹素获得更高采样密度。
// 伪代码:根据遮挡变化调整采样数
int baseSamples = 64;
float occlusionVariance = EstimateOcclusionVariance(texel);
int adaptiveSamples = baseSamples;
if (occlusionVariance > 0.4f)
{
adaptiveSamples = 256;
}
LightmapResult result = LightmapBaker.Bake(texel, adaptiveSamples);
此外,采样光线的分布也很重要。均匀半球采样虽然简单,但在高能量方向集中的场景中效率很低。重要性采样会优先在BRDF峰值和光源方向附近投射光线,相同样本数下可以得到更平滑的间接光照估计。部分光照贴图API提供采样分布参数,例如将采样模式从Uniform切换为Cosine或GGX重要性采样。
二、采样阶段降低噪点的实用手段
采样阶段的优化目标不是完全消除噪声,而是在烘焙时间可控的前提下降低噪声幅度。一种常见手段是增加光线偏移。光照贴图烘焙中如果光线与几何体重合或距离过近,会产生自遮挡黑斑或异常亮点。适当设置光线起点偏移可以避免这类伪影,但偏移过大会丢失接触细节,需要根据场景尺度调整。
另一种手段是使用预计算的可见性信息。对于静态场景,可以先烘焙一张粗略的可见性缓冲,在正式采样时跳过被完全遮挡的方向。这样做可以减少无效光线数量,把更多样本预算分配给真正有贡献的方向。比如在光照贴图API中集成体素遮挡查询,每个纹素只对可见半球发射光线。
// 假设使用可见性缓冲区来过滤采样方向
Vector3[] sampleDirs = GenerateHemisphereSamples(64);
List<Vector3> validDirs = new List<Vector3>();
foreach (var dir in sampleDirs)
{
if (!VisibilityBuffer.IsOccluded(texelPos, dir))
{
validDirs.Add(dir);
}
}
Color irradiance = Integrate(texelPos, validDirs);
除了直接筛掉被遮挡方向,采样抖动也值得关注。固定采样序列可能产生结构性条纹,而不是自然噪声。使用低差异序列(如Hammersley、Halton)代替伪随机数,可以让样本在半球上分布更均匀,同等的样本数下噪声更不明显。很多实时光照和烘焙器都提供低差异采样选项。
三、去噪算法:空间滤波与边缘保持
如果采样阶段仍然留下可见噪点,可以对生成的光照贴图纹理做后处理去噪。最简单的是高斯模糊,但直接对光照贴图模糊会导致几何边缘、阴影边界变糊,光照细节丢失。因此实际使用中更推荐双边滤波,它在平滑颜色差异的同时会考虑像素位置和亮度差异,只对相似区域进行加权平均,从而保留边缘。
双边滤波的核函数由空间距离和颜色距离两部分组成。在光照贴图的UV空间里,相邻纹素通常对应场景中相邻的表面点,但不同物体或不同材质的光照差异很大。颜色距离权重可以阻止这些边界被混合。下面是一段简化版的双边滤波着色器代码,用于对光照贴图进行去噪。
#version 330 core
uniform sampler2D lightmapTexture;
uniform vec2 texelSize;
in vec2 vUv;
out vec4 fragColor;
void main()
{
float totalWeight = 0.0;
vec3 sum = vec3(0.0);
vec3 center = texture(lightmapTexture, vUv).rgb;
float sigmaSpatial = 2.0;
float sigmaColor = 0.25;
for (int x = -3; x <= 3; x++)
{
for (int y = -3; y <= 3; y++)
{
vec2 offset = vec2(x, y) * texelSize;
vec3 sampleColor = texture(lightmapTexture, vUv + offset).rgb;
float spatialWeight = exp(-float(x*x + y*y) / (2.0 * sigmaSpatial * sigmaSpatial));
float colorDiff = distance(center, sampleColor);
float colorWeight = exp(-colorDiff * colorDiff / (2.0 * sigmaColor * sigmaColor));
float weight = spatialWeight * colorWeight;
sum += sampleColor * weight;
totalWeight += weight;
}
}
fragColor = vec4(sum / totalWeight, 1.0);
}
这段代码在7x7窗口内对光照贴图进行加权平均,sigmaSpatial控制空间影响范围,sigmaColor控制颜色差异的容忍度。对于光照贴图这种低频信号,小窗口配合合适的sigmaColor通常可以明显降低噪点,同时保留阴影边缘。
更进一步的方案是非局部均值滤波,它在更大范围内寻找相似块进行平均,适合光照贴图中重复纹理和相似光照区域。但非局部均值计算开销较大,一般只在烘焙完成后离线处理,不适合实时预览阶段。
四、结合AI去噪与工程落地的建议
近年来基于神经网络的去噪器(如OIDN)已经在路径追踪渲染中取得很好效果。光照贴图API也可以接入这类去噪器,对光照贴图纹理做离线去噪。AI去噪可以在极低采样数下恢复平滑的光照结果,但需要注意它可能引入轻微的光照偏移或高频细节丢失,尤其对于法线贴图对应的高频光照变化。
在工程落地时,建议将去噪作为独立后处理步骤,而不是把采样数压得过低。低采样数虽然可以节约烘焙时间,但会丢失真实的光照信息,去噪器只是估计,不能恢复被完全遗漏的间接光。通常保留一个基础采样数(例如每个纹素128条光线),再配合双边滤波或AI去噪,能在质量和时间之间取得较好平衡。
另外,去噪后的光照贴图如果直接用于实时光照,可能还需要在采样时使用合适的过滤方式。光照贴图纹理过滤设置会影响去噪后的平滑程度,三线性过滤或各向异性过滤可以进一步减少纹理采样带来的锯齿。
光照贴图噪点的处理并不是单一调参或单一滤镜就能完成的。采样策略、采样分布、去噪后处理三部分需要协同调整。先通过自适应采样和重要性采样把噪声控制到可接受范围,再对输出纹理做边缘保持的去噪,通常能得到稳定且细节保留良好的光照结果。