skate3recomp-multiplayer/src/native/shaders/shadow_blur.hlsl

45 lines
1.7 KiB
HLSL

// Shadow blur: the game's shadow_h/vblur passes, exact semantics from the
// disassembled Xenos ucode. One fullscreen-triangle draw
// per tile per direction; taps stay inside the tile (clamped). Coverage is
// Gaussian-blurred; depth keeps the exact center value where covered and
// dilates (binary-coverage-weighted neighbour average) into the penumbra
// otherwise, so depth compares stay valid across the blurred edge.
// b0 floats: c0 = (dir.x, dir.y, ntaps, src_is_raw), c1 = (w0, w1, w2, 0),
// c2 = (tile_x0, tile_x1, tile_y1, 0). ntaps 0 = format-convert only
// (cascade 2 is never blurred).
cbuffer C : register(b0) {
float4 c0;
float4 c1;
float4 c2;
};
Texture2D<float2> src : register(t0);
float4 vs_main(uint id : SV_VertexID) : SV_Position {
float2 uv = float2((id << 1) & 2, id & 2);
return float4(uv * float2(2, -2) + float2(-1, 1), 0, 1);
}
float2 ps_main(float4 pos : SV_Position) : SV_Target {
int2 p = int2(pos.xy);
int ntaps = int(c0.z);
float w[3] = {c1.x, c1.y, c1.z};
float2 center = src.Load(int3(p, 0));
float ccen = c0.w > 0.0 ? 1.0 - center.y : center.y;
float cov = 0.0;
float dsum = 0.0;
float dcnt = 0.0;
[unroll] for (int k = -2; k <= 2; ++k) {
if (abs(k) > ntaps) continue;
int2 q = p + int2(c0.xy) * k;
q.x = clamp(q.x, int(c2.x), int(c2.y));
q.y = clamp(q.y, 0, int(c2.z));
float2 t = src.Load(int3(q, 0));
float c = c0.w > 0.0 ? 1.0 - t.y : t.y;
cov += w[abs(k)] * c;
if (k != 0) {
float hard = c > 0.001 ? 1.0 : 0.0;
dcnt += hard;
dsum += hard * t.x;
}
}
float depth = ccen > 0.001 ? center.x : (dcnt > 0.0 ? dsum / dcnt : center.x);
return float2(depth, saturate(cov));
}