Files
HelloVulkan/shaders/taa.frag
T
atdunbg 6c80693618 Fix bloom glow flicker on static geometry under TAA jitter
TAA subpixel jitter makes rasterized HDR specular highlights oscillate
per frame. The old RGB min/max neighborhood clamp re-clamped the
converged history against jitter-driven extremes every frame, leaving a
periodic residual that the bloom threshold then amplified into visible
glow pulsing on static models.

- TAA: replace the RGB AABB clamp with YCoCg variance clipping
  (history clamped toward the stable neighborhood mean +/- 1.25 sigma,
  intersected with the min/max box as a ghosting upper bound)
- Bloom extract: 5-tap cross prefilter before the threshold spreads
  surviving 1px jitter energy over a ~4x4 footprint so the glow no
  longer pulses
2026-10-05 12:08:53 +08:00

83 lines
3.2 KiB
GLSL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#version 450
// TAA resolve:时域抗锯齿。
// 1. velocity 反推历史像素位置,取历史累积色
// 2. 方差裁剪(YCoCg):历史色钳向邻域均值 ± γσ(∩ AABB 极值盒)——
// 比 RGB min/max 盒稳得多:AABB 盒子被逐帧抖动的高亮像素拽来拽去,
// 收敛历史持续被重钳 → 周期性残差经 bloom 阈值放大 = 静态模型泛光闪烁
// 3. mix(cur, hist, alpha) 时域累积
// 输出写入 TAAColor,它同时也是下一帧的历史(ReadHistory 自引用)。
layout(location = 0) in vec2 fragUV;
layout(location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform sampler2D curImage; // 当前帧 HDR
layout(set = 0, binding = 1) uniform sampler2D velocityImage; // 运动向量(UV 空间)
layout(set = 0, binding = 2) uniform sampler2D historyImage; // 上一帧累积结果
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
layout(push_constant) uniform PushConstants {
float exposure; // 与其它全屏 pass 布局一致(本 pass 不用)
float alpha; // 历史权重(0.9 = 90% 历史)
float param1;
float param2;
} pc;
// YCoCg 感知空间:亮度/色度分离,方差裁剪不在 HDR RGB 直接做
//(RGB 各通道独立 σ 会偏色;Y 通道主导几何相关性)
vec3 RgbToYCoCg(vec3 c) {
return vec3(0.25 * c.r + 0.5 * c.g + 0.25 * c.b,
0.5 * c.r - 0.5 * c.b,
-0.25 * c.r + 0.5 * c.g - 0.25 * c.b);
}
vec3 YCoCgToRgb(vec3 c) {
return vec3(c.x + c.y - c.z,
c.x + c.z,
c.x - c.y - c.z);
}
void main() {
vec4 cur = texture(curImage, fragUV);
vec2 vel = texture(velocityImage, fragUV).xy;
vec2 histUV = fragUV - vel;
vec3 result = cur.rgb;
// 历史采样:越出画面(相机快速平移)直接用当前帧,不累积
if (all(greaterThanEqual(histUV, vec2(0.0))) &&
all(lessThanEqual(histUV, vec2(1.0)))) {
vec3 hist = texture(historyImage, histUV).rgb;
// 邻域统计(3×3,YCoCg):均值 m1、均方 m2、极值盒 [mn, mx]
vec2 ts = 1.0 / vec2(textureSize(curImage, 0));
vec3 m1 = vec3(0.0);
vec3 m2 = vec3(0.0);
vec3 mn = vec3(1e9);
vec3 mx = vec3(-1e9);
for (int y = -1; y <= 1; ++y) {
for (int x = -1; x <= 1; ++x) {
vec3 c = RgbToYCoCg(texture(curImage, fragUV + vec2(x, y) * ts).rgb);
m1 += c;
m2 += c * c;
mn = min(mn, c);
mx = max(mx, c);
}
}
m1 /= 9.0;
m2 /= 9.0;
vec3 sigma = sqrt(max(m2 - m1 * m1, vec3(0.0)));
// 方差裁剪盒:均值 ± 1.25σ 再 ∩ 极值盒。历史被钳向"稳定均值"
// 而非抖动极值 → 收敛残差大幅下降(泛光闪烁的治本点)。
// 极值盒交集保证不引入邻域外颜色(鬼影上界)
const float kGamma = 1.25;
vec3 lo = max(m1 - kGamma * sigma, mn);
vec3 hi = min(m1 + kGamma * sigma, mx);
vec3 clamped = YCoCgToRgb(clamp(RgbToYCoCg(hist), lo, hi));
result = mix(cur.rgb, clamped, pc.alpha);
}
outColor = vec4(result, cur.a);
}