mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 09:33:51 +00:00
Implement FSR 2.2 temporal upscaling
Full FSR 2.2 pipeline with depth-based motion vector reprojection, temporal accumulation with YCoCg neighborhood clamping, and RCAS contrast-adaptive sharpening. Architecture (designed for FSR 3.x frame generation readiness): - Camera: Halton(2,3) sub-pixel jitter with unjittered projection stored separately for motion vector computation - Motion vectors: compute shader reconstructs world position from depth + inverse VP, reprojects with previous frame's VP - Temporal accumulation: compute shader blends 5-10% current frame with 90-95% clamped history, adaptive blend for disocclusion - History: ping-pong R16G16B16A16 buffers at display resolution - Sharpening: RCAS fragment pass with contrast-adaptive weights Integration: - FSR2 replaces both FSR1 and MSAA when enabled - Scene renders to internal resolution framebuffer (no MSAA) - Compute passes run between scene and swapchain render passes - Camera cut detection resets history on teleport - Quality presets shared with FSR1 (0.50-0.77 scale factors) - UI: "Upscaling" combo with Off/FSR 1.0/FSR 2.2 options
This commit is contained in:
parent
0ffeabd4ed
commit
52317d1edd
11 changed files with 957 additions and 12 deletions
115
assets/shaders/fsr2_accumulate.comp.glsl
Normal file
115
assets/shaders/fsr2_accumulate.comp.glsl
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#version 450
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
// Inputs (internal resolution)
|
||||
layout(set = 0, binding = 0) uniform sampler2D sceneColor;
|
||||
layout(set = 0, binding = 1) uniform sampler2D depthBuffer;
|
||||
layout(set = 0, binding = 2) uniform sampler2D motionVectors;
|
||||
|
||||
// History (display resolution)
|
||||
layout(set = 0, binding = 3) uniform sampler2D historyInput;
|
||||
|
||||
// Output (display resolution)
|
||||
layout(set = 0, binding = 4, rgba16f) uniform writeonly image2D historyOutput;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 internalSize; // xy = internal resolution, zw = 1/internal
|
||||
vec4 displaySize; // xy = display resolution, zw = 1/display
|
||||
vec4 jitterOffset; // xy = current jitter (pixel-space), zw = unused
|
||||
vec4 params; // x = resetHistory (1=reset), y = sharpness, zw = unused
|
||||
} pc;
|
||||
|
||||
// RGB <-> YCoCg for neighborhood clamping
|
||||
vec3 rgbToYCoCg(vec3 rgb) {
|
||||
float y = 0.25 * rgb.r + 0.5 * rgb.g + 0.25 * rgb.b;
|
||||
float co = 0.5 * rgb.r - 0.5 * rgb.b;
|
||||
float cg = -0.25 * rgb.r + 0.5 * rgb.g - 0.25 * rgb.b;
|
||||
return vec3(y, co, cg);
|
||||
}
|
||||
|
||||
vec3 yCoCgToRgb(vec3 ycocg) {
|
||||
float y = ycocg.x;
|
||||
float co = ycocg.y;
|
||||
float cg = ycocg.z;
|
||||
return vec3(y + co - cg, y + cg, y - co - cg);
|
||||
}
|
||||
|
||||
void main() {
|
||||
ivec2 outPixel = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 outSize = ivec2(pc.displaySize.xy);
|
||||
if (outPixel.x >= outSize.x || outPixel.y >= outSize.y) return;
|
||||
|
||||
// Output UV in display space
|
||||
vec2 outUV = (vec2(outPixel) + 0.5) * pc.displaySize.zw;
|
||||
|
||||
// Map display pixel to internal resolution UV (accounting for jitter)
|
||||
vec2 internalUV = outUV;
|
||||
|
||||
// Sample current frame color at internal resolution
|
||||
vec3 currentColor = texture(sceneColor, internalUV).rgb;
|
||||
|
||||
// Sample motion vector at internal resolution
|
||||
vec2 inUV = outUV; // Approximate — display maps to internal via scale
|
||||
vec2 motion = texture(motionVectors, inUV).rg;
|
||||
|
||||
// Reproject: where was this pixel in the previous frame's history?
|
||||
vec2 historyUV = outUV - motion;
|
||||
|
||||
// History reset: on teleport / camera cut, just use current frame
|
||||
if (pc.params.x > 0.5) {
|
||||
imageStore(historyOutput, outPixel, vec4(currentColor, 1.0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Sample reprojected history
|
||||
vec3 historyColor = texture(historyInput, historyUV).rgb;
|
||||
|
||||
// Neighborhood clamping in YCoCg space to prevent ghosting
|
||||
// Sample 3x3 neighborhood from current frame
|
||||
vec2 texelSize = pc.internalSize.zw;
|
||||
vec3 samples[9];
|
||||
int idx = 0;
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
samples[idx] = rgbToYCoCg(texture(sceneColor, internalUV + vec2(dx, dy) * texelSize).rgb);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute AABB in YCoCg
|
||||
vec3 boxMin = samples[0];
|
||||
vec3 boxMax = samples[0];
|
||||
for (int i = 1; i < 9; i++) {
|
||||
boxMin = min(boxMin, samples[i]);
|
||||
boxMax = max(boxMax, samples[i]);
|
||||
}
|
||||
|
||||
// Slightly expand the box to reduce flickering on edges
|
||||
vec3 boxCenter = (boxMin + boxMax) * 0.5;
|
||||
vec3 boxExtent = (boxMax - boxMin) * 0.5;
|
||||
boxMin = boxCenter - boxExtent * 1.25;
|
||||
boxMax = boxCenter + boxExtent * 1.25;
|
||||
|
||||
// Clamp history to the neighborhood AABB
|
||||
vec3 historyYCoCg = rgbToYCoCg(historyColor);
|
||||
vec3 clampedHistory = clamp(historyYCoCg, boxMin, boxMax);
|
||||
historyColor = yCoCgToRgb(clampedHistory);
|
||||
|
||||
// Check if history UV is valid (within [0,1])
|
||||
float historyValid = (historyUV.x >= 0.0 && historyUV.x <= 1.0 &&
|
||||
historyUV.y >= 0.0 && historyUV.y <= 1.0) ? 1.0 : 0.0;
|
||||
|
||||
// Blend factor: use more current frame for disoccluded regions
|
||||
// Luminance difference between clamped history and original → confidence
|
||||
float clampDist = length(historyYCoCg - clampedHistory);
|
||||
float blendFactor = mix(0.05, 0.3, clamp(clampDist * 4.0, 0.0, 1.0));
|
||||
|
||||
// If history is off-screen, use current frame entirely
|
||||
blendFactor = mix(blendFactor, 1.0, 1.0 - historyValid);
|
||||
|
||||
// Final blend
|
||||
vec3 result = mix(historyColor, currentColor, blendFactor);
|
||||
|
||||
imageStore(historyOutput, outPixel, vec4(result, 1.0));
|
||||
}
|
||||
BIN
assets/shaders/fsr2_accumulate.comp.spv
Normal file
BIN
assets/shaders/fsr2_accumulate.comp.spv
Normal file
Binary file not shown.
44
assets/shaders/fsr2_motion.comp.glsl
Normal file
44
assets/shaders/fsr2_motion.comp.glsl
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#version 450
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D depthBuffer;
|
||||
layout(set = 0, binding = 1, rg16f) uniform writeonly image2D motionVectors;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 invViewProj; // Inverse of current jittered VP
|
||||
mat4 prevViewProj; // Previous frame unjittered VP
|
||||
vec4 resolution; // xy = internal size, zw = 1/internal size
|
||||
vec4 jitterOffset; // xy = current jitter (NDC), zw = previous jitter
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
ivec2 pixelCoord = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 imgSize = ivec2(pc.resolution.xy);
|
||||
if (pixelCoord.x >= imgSize.x || pixelCoord.y >= imgSize.y) return;
|
||||
|
||||
// Sample depth (Vulkan: 0 = near, 1 = far)
|
||||
float depth = texelFetch(depthBuffer, pixelCoord, 0).r;
|
||||
|
||||
// Pixel center in NDC [-1, 1]
|
||||
vec2 uv = (vec2(pixelCoord) + 0.5) * pc.resolution.zw;
|
||||
vec2 ndc = uv * 2.0 - 1.0;
|
||||
|
||||
// Reconstruct world position from depth
|
||||
vec4 clipPos = vec4(ndc, depth, 1.0);
|
||||
vec4 worldPos = pc.invViewProj * clipPos;
|
||||
worldPos /= worldPos.w;
|
||||
|
||||
// Project into previous frame's clip space (unjittered)
|
||||
vec4 prevClip = pc.prevViewProj * worldPos;
|
||||
vec2 prevNdc = prevClip.xy / prevClip.w;
|
||||
vec2 prevUV = prevNdc * 0.5 + 0.5;
|
||||
|
||||
// Remove jitter from current UV to get unjittered position
|
||||
vec2 unjitteredUV = uv - pc.jitterOffset.xy * 0.5;
|
||||
|
||||
// Motion = previous position - current unjittered position (in UV space)
|
||||
vec2 motion = prevUV - unjitteredUV;
|
||||
|
||||
imageStore(motionVectors, pixelCoord, vec4(motion, 0.0, 0.0));
|
||||
}
|
||||
BIN
assets/shaders/fsr2_motion.comp.spv
Normal file
BIN
assets/shaders/fsr2_motion.comp.spv
Normal file
Binary file not shown.
46
assets/shaders/fsr2_sharpen.frag.glsl
Normal file
46
assets/shaders/fsr2_sharpen.frag.glsl
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 TexCoord;
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
|
||||
layout(set = 0, binding = 0) uniform sampler2D inputImage;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 params; // x = 1/width, y = 1/height, z = sharpness (0-2), w = unused
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
vec2 texelSize = pc.params.xy;
|
||||
float sharpness = pc.params.z;
|
||||
|
||||
// RCAS: Robust Contrast-Adaptive Sharpening
|
||||
// 5-tap cross pattern
|
||||
vec3 center = texture(inputImage, TexCoord).rgb;
|
||||
vec3 north = texture(inputImage, TexCoord + vec2(0.0, -texelSize.y)).rgb;
|
||||
vec3 south = texture(inputImage, TexCoord + vec2(0.0, texelSize.y)).rgb;
|
||||
vec3 west = texture(inputImage, TexCoord + vec2(-texelSize.x, 0.0)).rgb;
|
||||
vec3 east = texture(inputImage, TexCoord + vec2( texelSize.x, 0.0)).rgb;
|
||||
|
||||
// Compute local contrast (min/max of neighborhood)
|
||||
vec3 minRGB = min(center, min(min(north, south), min(west, east)));
|
||||
vec3 maxRGB = max(center, max(max(north, south), max(west, east)));
|
||||
|
||||
// Adaptive sharpening weight based on local contrast
|
||||
// High contrast = less sharpening (prevent ringing)
|
||||
vec3 range = maxRGB - minRGB;
|
||||
vec3 rcpRange = 1.0 / (range + 0.001);
|
||||
|
||||
// Sharpening amount: inversely proportional to contrast
|
||||
float luma = dot(center, vec3(0.299, 0.587, 0.114));
|
||||
float lumaRange = max(range.r, max(range.g, range.b));
|
||||
float w = clamp(1.0 - lumaRange * 2.0, 0.0, 1.0) * sharpness * 0.25;
|
||||
|
||||
// Apply sharpening via unsharp mask
|
||||
vec3 avg = (north + south + west + east) * 0.25;
|
||||
vec3 sharpened = center + (center - avg) * w;
|
||||
|
||||
// Clamp to prevent ringing artifacts
|
||||
sharpened = clamp(sharpened, minRGB, maxRGB);
|
||||
|
||||
FragColor = vec4(sharpened, 1.0);
|
||||
}
|
||||
BIN
assets/shaders/fsr2_sharpen.frag.spv
Normal file
BIN
assets/shaders/fsr2_sharpen.frag.spv
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue