diff --git a/shaders/bloom_extract.frag b/shaders/bloom_extract.frag index 4602835..58950a5 100644 --- a/shaders/bloom_extract.frag +++ b/shaders/bloom_extract.frag @@ -16,7 +16,16 @@ layout(push_constant) uniform PushConstants { } pc; 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)); // 软阈值:只保留超出 threshold 的能量比例,避免硬切产生的块状边缘 diff --git a/shaders/taa.frag b/shaders/taa.frag index c3074f9..1492bd4 100644 --- a/shaders/taa.frag +++ b/shaders/taa.frag @@ -2,7 +2,9 @@ // TAA resolve:时域抗锯齿。 // 1. velocity 反推历史像素位置,取历史累积色 -// 2. 邻域 clamp(3×3 AABB)压制鬼影:历史色超出当前帧邻域范围的部分丢弃 +// 2. 方差裁剪(YCoCg):历史色钳向邻域均值 ± γσ(∩ AABB 极值盒)—— +// 比 RGB min/max 盒稳得多:AABB 盒子被逐帧抖动的高亮像素拽来拽去, +// 收敛历史持续被重钳 → 周期性残差经 bloom 阈值放大 = 静态模型泛光闪烁 // 3. mix(cur, hist, alpha) 时域累积 // 输出写入 TAAColor,它同时也是下一帧的历史(ReadHistory 自引用)。 @@ -21,6 +23,19 @@ layout(push_constant) uniform PushConstants { 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; @@ -33,18 +48,33 @@ void main() { all(lessThanEqual(histUV, vec2(1.0)))) { 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)); - vec3 minC = cur.rgb; - vec3 maxC = cur.rgb; + 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 = texture(curImage, fragUV + vec2(x, y) * ts).rgb; - minC = min(minC, c); - maxC = max(maxC, c); + vec3 c = RgbToYCoCg(texture(curImage, fragUV + vec2(x, y) * ts).rgb); + m1 += 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); }