☰
高动态范围(HDR)泛光(Bloom)的能量守恒:双下采样滤波与物理曝光提取
2026/10/12 20:20:49 网站建设 项目流程

在渲染管线迈入全流程基于物理的着色(Physically Based Shading, PBS)与高动态范围(HDR)管线之后,屏幕空间泛光(Bloom)已不仅仅是一种夸张的游戏艺术风格修饰,而是模拟现实摄影镜头内部散射、晶状体折射以及传感器光溢出(Optical Glare)的必要物理现象。

早期很多引擎中实现的泛光算法非常粗糙:设定一个固定的硬亮度阈值(如 RGB > 1.0),提取超出阈值的像素,然后经过几次标准的高斯模糊(Gaussian Blur)叠加回主画面。这种做法在 HDR 极端强光源(如烈日直射、爆炸火光或高反射铬合金表面)下会产生灾难性的视觉缺陷——阈值截断边缘出现剧烈的高频闪烁与断层锯齿,强光区域能量极度失控爆白,完全破坏了 PBR 材质的光度学平衡。

为了在现代引擎中呈现柔和、自然且具备物理能量守恒的电影级光晕效果,我们必须从两个核心维度对泛光管线进行重构:基于平滑膝部函数的物理曝光高亮提取,以及基于双重加权滤波(Dual Filtering)的金字塔下采样与上采样重建。


一、传统阈值提取的破绽与平滑膝部(Soft Knee)物理提取

在标准的非物理泛光中,人们常采用硬截断函数:
$$C_{\text{bloom}} = \max(C - \text{Threshold}, 0.0)$$
这种函数在导数上是不连续的。当一个像素的亮度恰好在阈值边缘微小抖动时,提取出的能量会剧烈跳变,在开启摄像机动态抗锯齿(TAA)后,会演变成极其恶劣的时域闪烁噪点。

平滑膝部衰减函数推导

为了使超出曝光极限的高动态像素平滑进入泛光缓冲区,我们引入一段由二次曲线平滑过渡的“膝部”(Knee)结构。定义亮度标量 $L = \text{Luminance}(C)$,设定的基准阈值为 $T$,过渡软度参数为 $K \in [0, 1]$:

设膝部半宽为 $W = T \cdot K$。我们构建分段加权曲线:

$$\text{Weight}(L) = \begin{cases}
0 & \text{if } L \le T - W \
\frac{(L - T + W)^2}{4W + 10^{-5}} & \text{if } T - W < L \le T + W \
L - T & \text{if } L > T + W
\end{cases}$$

最终提取出的颜色为:
$$C_{\text{extracted}} = C \cdot \frac{\max(\text{Weight}(L), 0.0)}{\max(L, 10^{-5})}$$

通过这种光滑可导的二次过渡,微弱的高光能够以非常柔和的权重渗入模糊缓冲区,不仅消除了发光边缘的几何切口,更为后续的多级滤波提供了纯净的低频输入。


二、卡瓦斯双滤波(Dual Filtering)与 13 采样抗闪烁下采样

传统的全尺寸大半径高斯模糊计算复杂度极高,哪怕将其分离为一维水平与垂直 Pass,面对几十像素的散射半径,采样开销依旧难以承受。现代高端图形引擎(如 Unreal Engine 5、Frostbite 以及 Call of Duty 引擎架构)普遍采用渐进式金字塔滤波体系(Pyramid Dual Filtering)。

整个管线由多级连续降采样的下采样链(Downsampling Chain,通常为 5 到 6 级,分辨率依次减半:$1/2 \to 1/4 \to 1/8 \to 1/16 \to 1/32$)与逆向累加的上采样链(Upsampling Chain)构成。

13-Tap 盒装与十字交叉加权下采样

在向下生成低分辨率 Mip 时,如果简单采用标准的 $2 \times 2$ 双线性均值滤波,单像素高光(Fireflies)在被降采样时会严重下沉甚至丢失,导致光晕在物体移动时剧烈闪烁。业界公认的最优解是结合了中心重叠的 13-Tap 滤波核(包含 1 个中心盒、4 个内侧半像素采样点与 4 个外侧十字对角采样点),并在着色器内执行局部亮度加权抑制(Karis Average),从根源上扑灭次像素高光噪点。

以下为基于 HLSL 实现的 13-Tap 物理加权下采样核心着色器:

Texture2D<float4> g_InputTexture : register(t0); SamplerState g_LinearClampSampler : register(s0); cbuffer BloomConstants : register(b0) { float2 g_TexelSize; // 当前输入纹理的单个像素尺寸 (1/Width, 1/Height) float g_MipLevel; }; // 提取色彩亮度,用于 Karis 平均加权 float ColorLuminance(float3 c) { return dot(c, float3(0.2126, 0.7152, 0.0722)); } // 抑制单个像素极高亮度的加权因子,防止下采样闪烁 float PartialKarisWeight(float3 c) { return 1.0 / (1.0 + ColorLuminance(c)); } float4 Downsample13Tap(float2 uv) { float x = g_TexelSize.x; float y = g_TexelSize.y; // 采样周围 13 个点 // A - B - C // - D - E - // F - G - H // - I - J - // K - L - M float3 a = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2(-2 * x, 2 * y), g_MipLevel).rgb; float3 b = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( 0 * x, 2 * y), g_MipLevel).rgb; float3 c = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( 2 * x, 2 * y), g_MipLevel).rgb; float3 d = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2(-1 * x, 1 * y), g_MipLevel).rgb; float3 e = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( 1 * x, 1 * y), g_MipLevel).rgb; float3 f = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2(-2 * x, 0 * y), g_MipLevel).rgb; float3 g = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( 0 * x, 0 * y), g_MipLevel).rgb; float3 h = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( 2 * x, 0 * y), g_MipLevel).rgb; float3 i = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2(-1 * x, -1 * y), g_MipLevel).rgb; float3 j = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( 1 * x, -1 * y), g_MipLevel).rgb; float3 k = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2(-2 * x, -2 * y), g_MipLevel).rgb; float3 l = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( 0 * x, -2 * y), g_MipLevel).rgb; float3 m = g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( 2 * x, -2 * y), g_MipLevel).rgb; // 分解为 5 个具有平滑重叠的局部盒子结构 // 并在 Mip 0 提取层应用 Karis 加权平均 float3 box1 = (a + b + f + g) * 0.25; float3 box2 = (b + c + g + h) * 0.25; float3 box3 = (f + g + k + l) * 0.25; float3 box4 = (g + h + l + m) * 0.25; float3 boxCenter = (d + e + i + j) * 0.25; // 核心中央赋予 50% 总体权重,外围四个盒子各占 12.5% float3 finalColor = boxCenter * 0.5 + (box1 + box2 + box3 + box4) * 0.125; return float4(finalColor, 1.0); }

三、上采样 Tent 帐篷滤波与多级能量守恒混合

完成了自顶向下的分层能量提取后,下一步是将低分辨率宏观光晕反向累加回上一层级。

如果在上采样阶段直接采用双线性放大,低分辨率的块状锯齿(Bilinear Blocky Artifacts)会直接污染清晰的高分辨率画面。为此,必须在放大过程中使用3x3 Tent(帐篷)滤波核进行平滑重构。

// 9-Tap Tent 滤波上采样着色器 float4 UpsampleTent(float2 uv, float filterRadius) { float2 d = g_TexelSize * filterRadius; // 9-Tap 权重分布: // 1 2 1 // 2 4 2 * (1 / 16) // 1 2 1 float3 color = float3(0.0, 0.0, 0.0); color += g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2(-d.x, d.y), g_MipLevel).rgb * 1.0; color += g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( 0.0, d.y), g_MipLevel).rgb * 2.0; color += g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( d.x, d.y), g_MipLevel).rgb * 1.0; color += g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2(-d.x, 0.0), g_MipLevel).rgb * 2.0; color += g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( 0.0, 0.0), g_MipLevel).rgb * 4.0; color += g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( d.x, 0.0), g_MipLevel).rgb * 2.0; color += g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2(-d.x, -d.y), g_MipLevel).rgb * 1.0; color += g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( 0.0, -d.y), g_MipLevel).rgb * 2.0; color += g_InputTexture.SampleLevel(g_LinearClampSampler, uv + float2( d.x, -d.y), g_MipLevel).rgb * 1.0; return float4(color * (1.0 / 16.0), 1.0); }
能量守恒的多尺度插值混合(Energy-Preserving Accumulation)

上采样的核心数学控制在于每一层的融合权重。设第 $k$ 层低分辨率泛光经过 Tent 滤波后得到 $F_{k}^{\text{up}}$,它与当前层原生分辨率 $F_{k-1}$ 进行混合时的公式为:

$$F_{k-1}^{\text{accum}} = \text{lerp}(F_{k-1}, F_{k}^{\text{up}}, \alpha)$$

其中混合因子 $\alpha \in [0.65, 0.85]$ 控制了漫反射散射在宏观与微观层级之间的能量扩散比重。如果简单使用直接加法:
$$F_{\text{out}} = F_{\text{base}} + F_{\text{blur}}$$
当金字塔层级深达 6 层时,能量会在不断叠加中指数级爆炸,导致整个画面的暗部被冲刷发白(Washed-out Contrast)。通过线性插值归一化,我们严格锁定了全通滤镜的总体增益积分上限恒等于 1,从而保证在任何曝光变化下,都不会无中生有地凭空放大辐射通量。


四、色调映射(Tone Mapping)前的无缝物理合流

在整个延迟渲染与后处理管线的最终阶段,经过能量守恒重建的泛光纹理必须在应用色调映射(Tone Mapping / ACES)之前与原始 HDR 场景渲染目标进行合成:

$$C_{\text{composite}} = C_{\text{scene_hdr}} + C_{\text{bloom_hdr}} \times \text{BloomIntensity}$$

因为此时两者均处于线性光物理空间(Linear Radiance Space),泛光本身作为一种额外的镜头内散射光子,符合物理光学叠加原理。随后,整个合成后的高动态色彩被统一推入 ACEScg 或 Filmik 色调映射曲线中,高亮部分自然经历肩部滚降压缩(Shoulder Roll-off),展现出柔润且富有冲击力的辉光边缘。

这种结合了平滑膝部物理提取、13-Tap Karis 加权降采样以及能量守恒 Tent 累加的现代泛光管线,不仅在 GPU 消耗上比传统高斯方案减少了 60% 以上的带宽开销,更彻底驯服了强光闪烁,赋予了虚拟游戏世界极具光学可信度的非凡质感。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询