Files

121 lines
5.8 KiB
GLSL
Raw Permalink 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 dilation:轮廓处取最近表面的速度,反推历史像素位置
// 2. 去遮挡检测(disocclusion):重投影点上的"上一帧深度"若与当前像素
// 深度显著不同 → 该像素上一帧属于别的表面(刚露出/刚被遮挡),
// 历史颜色不属于当前表面 → 拒绝历史(alpha→0),用当前帧。
// 这是"移动时一闪一闪"的结构性根因:只查 histUV 是否出屏、不查历史
// 是否同一表面时,新暴露区域会持续采到前/背景串味的历史色
// 3. 方差裁剪(YCoCg):历史色钳向邻域均值 ± γσ(∩ AABB 极值盒)
// 4. 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; // 上一帧累积结果
layout(set = 0, binding = 3) uniform sampler2D depthImage; // 当前帧深度(近=0,远=1)
layout(set = 0, binding = 4) uniform sampler2D historyDepthImage; // 上一帧深度(同资源历史槽位)
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878):
// 本 pass 语义 = {ortho, alpha, zNear, zFar}
layout(push_constant) uniform PushConstants {
float ortho; // 1 = 正交投影(深度线性化走线性分支)
float alpha; // 历史权重(0.9 = 90% 历史)
float zNear; // 相机近裁剪面
float zFar; // 相机远裁剪面
} 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);
}
// [0,1] 深度 → 视图空间距离(正值,近小远大)。
// 透视:d = 0.5·ndc + 0.5 重映射后,z = n·f / (f − d·(f−n))
//(n → d=0,f → d=1)。正交:深度本身线性,z = n + d·(f−n)
float linearizeDepth(float d, float n, float f, float ortho) {
float persp = (n * f) / (f - d * (f - n));
float orthoL = n + d * (f - n);
return mix(persp, orthoL, ortho);
}
void main() {
vec4 cur = texture(curImage, fragUV);
vec2 ts = 1.0 / vec2(textureSize(curImage, 0));
// 每像素 velocity 直接取自 velocity 缓冲(几何体与天空各自写入),
// 不做 dilation:像素中心属谁就用谁的速度。min-depth dilation 会把
// 前景(快物体)速度串给轮廓旁的背景像素 → 背景重投影到错误位置 →
// 轮廓周围持续闪烁,故移除。
vec2 vel = texture(velocityImage, fragUV).xy;
float dCenter = texture(depthImage, fragUV).r; // 中心像素当前深度
vec2 histUV = fragUV - vel;
// 运动自适应历史权重:静止 → 高权重(充分累积抗锯齿、稳定锐利);
// 运动/遮挡 → 降权重(0.4),快速收敛,消除拖影与移动闪烁。
// 固定高权重是"移动闪烁"的根源:新暴露区域要很多帧才收敛(UE/Unity 同款)
float speed = clamp(length(vel) * 100.0, 0.0, 1.0);
float alpha = mix(pc.alpha, 0.4, speed);
vec3 result = cur.rgb;
// 历史采样:越出画面(相机快速平移)直接用当前帧,不累积
if (all(greaterThanEqual(histUV, vec2(0.0))) &&
all(lessThanEqual(histUV, vec2(1.0)))) {
// ---- 去遮挡检测:深度一致性 ----
// 比较当前像素的视图距离与重投影点上的上一帧视图距离。差异显著
// 说明该像素上一帧被另一表面占据(刚露出/刚被遮挡)→ 历史无效,
// alpha 归零(新暴露像素直接吃当前帧,下一帧起重新累积)。
// 阈值取相对量:与绝对距离无关,近/远场景同一套参数
float zCur = linearizeDepth(dCenter, pc.zNear, pc.zFar, pc.ortho);
float zHist = linearizeDepth(texture(historyDepthImage, histUV).r,
pc.zNear, pc.zFar, pc.ortho);
float relDiff = abs(zCur - zHist) / max(zCur, 1e-4);
alpha *= 1.0 - smoothstep(0.05, 0.15, relDiff);
vec3 hist = texture(historyImage, histUV).rgb;
// 邻域统计(3×3,YCoCg):均值 m1、均方 m2、极值盒 [mn, mx]
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, alpha);
}
outColor = vec4(result, cur.a);
}