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
This commit is contained in:
@@ -16,7 +16,16 @@ layout(push_constant) uniform PushConstants {
|
|||||||
} pc;
|
} pc;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 c = texture(hdrImage, fragUV).rgb;
|
// 5 tap 十字预滤波(中心 0.4 + 四邻 0.15):TAA 残留的亚像素震荡
|
||||||
|
// 在进阈值前被抹平——1px 级抖动的能量摊到 ~4×4 足印,泛光不再脉冲。
|
||||||
|
// 半分辨率输出中心的双线性已覆盖 2×2 块,此步把足印扩到近似 4×4
|
||||||
|
vec2 ts = 1.0 / vec2(textureSize(hdrImage, 0));
|
||||||
|
vec3 c = texture(hdrImage, fragUV).rgb * 0.4
|
||||||
|
+ (texture(hdrImage, fragUV + vec2(ts.x, 0.0)).rgb
|
||||||
|
+ texture(hdrImage, fragUV + vec2(-ts.x, 0.0)).rgb
|
||||||
|
+ texture(hdrImage, fragUV + vec2(0.0, ts.y)).rgb
|
||||||
|
+ texture(hdrImage, fragUV + vec2(0.0, -ts.y)).rgb) * 0.15;
|
||||||
|
|
||||||
float l = max(c.r, max(c.g, c.b));
|
float l = max(c.r, max(c.g, c.b));
|
||||||
|
|
||||||
// 软阈值:只保留超出 threshold 的能量比例,避免硬切产生的块状边缘
|
// 软阈值:只保留超出 threshold 的能量比例,避免硬切产生的块状边缘
|
||||||
|
|||||||
+38
-8
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
// TAA resolve:时域抗锯齿。
|
// TAA resolve:时域抗锯齿。
|
||||||
// 1. velocity 反推历史像素位置,取历史累积色
|
// 1. velocity 反推历史像素位置,取历史累积色
|
||||||
// 2. 邻域 clamp(3×3 AABB)压制鬼影:历史色超出当前帧邻域范围的部分丢弃
|
// 2. 方差裁剪(YCoCg):历史色钳向邻域均值 ± γσ(∩ AABB 极值盒)——
|
||||||
|
// 比 RGB min/max 盒稳得多:AABB 盒子被逐帧抖动的高亮像素拽来拽去,
|
||||||
|
// 收敛历史持续被重钳 → 周期性残差经 bloom 阈值放大 = 静态模型泛光闪烁
|
||||||
// 3. mix(cur, hist, alpha) 时域累积
|
// 3. mix(cur, hist, alpha) 时域累积
|
||||||
// 输出写入 TAAColor,它同时也是下一帧的历史(ReadHistory 自引用)。
|
// 输出写入 TAAColor,它同时也是下一帧的历史(ReadHistory 自引用)。
|
||||||
|
|
||||||
@@ -21,6 +23,19 @@ layout(push_constant) uniform PushConstants {
|
|||||||
float param2;
|
float param2;
|
||||||
} pc;
|
} 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() {
|
void main() {
|
||||||
vec4 cur = texture(curImage, fragUV);
|
vec4 cur = texture(curImage, fragUV);
|
||||||
vec2 vel = texture(velocityImage, fragUV).xy;
|
vec2 vel = texture(velocityImage, fragUV).xy;
|
||||||
@@ -33,18 +48,33 @@ void main() {
|
|||||||
all(lessThanEqual(histUV, vec2(1.0)))) {
|
all(lessThanEqual(histUV, vec2(1.0)))) {
|
||||||
vec3 hist = texture(historyImage, histUV).rgb;
|
vec3 hist = texture(historyImage, histUV).rgb;
|
||||||
|
|
||||||
// 邻域 clamp:当前帧 3×3 邻域的 min/max 包围盒
|
// 邻域统计(3×3,YCoCg):均值 m1、均方 m2、极值盒 [mn, mx]
|
||||||
vec2 ts = 1.0 / vec2(textureSize(curImage, 0));
|
vec2 ts = 1.0 / vec2(textureSize(curImage, 0));
|
||||||
vec3 minC = cur.rgb;
|
vec3 m1 = vec3(0.0);
|
||||||
vec3 maxC = cur.rgb;
|
vec3 m2 = vec3(0.0);
|
||||||
|
vec3 mn = vec3(1e9);
|
||||||
|
vec3 mx = vec3(-1e9);
|
||||||
for (int y = -1; y <= 1; ++y) {
|
for (int y = -1; y <= 1; ++y) {
|
||||||
for (int x = -1; x <= 1; ++x) {
|
for (int x = -1; x <= 1; ++x) {
|
||||||
vec3 c = texture(curImage, fragUV + vec2(x, y) * ts).rgb;
|
vec3 c = RgbToYCoCg(texture(curImage, fragUV + vec2(x, y) * ts).rgb);
|
||||||
minC = min(minC, c);
|
m1 += c;
|
||||||
maxC = max(maxC, c);
|
m2 += c * c;
|
||||||
|
mn = min(mn, c);
|
||||||
|
mx = max(mx, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vec3 clamped = clamp(hist, minC, maxC);
|
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);
|
result = mix(cur.rgb, clamped, pc.alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user