The shipping default had NO anti-aliasing at all: the temporal resolve was removed, the setting's first option went on saying "Temporal", and MSAA defaults to one sample, so every machine without DLSS drew grass and needle cards with nothing smoothing an edge. FXAA in the sharpen pass, which already reads the neighbourhood and runs last on the LDR image. No history, so it cannot drag or smear a reflection. 25.5% less single-pixel staircase on edge pixels. The trap, recorded because it inverted the result: the unsharp delta must be computed from the RAW image and only then applied to the anti-aliased colour. Centre from FXAA and neighbours from the raw texture measures half smoothing and half signal, so the mask sharpens precisely what FXAA softened - 29% WORSE than no anti-aliasing at all. Depth of field for the photo mode: a disc whose radius is the circle of confusion, normalised by focus distance so a landscape shot is not a macro, with taps rejected unless they are at least as out of focus as the pixel they blur into - which is what stops a sharp foreground haloing into a blurred background. Between the scene and the bloom, so a blurred highlight still blooms. Skipped whole when the aperture is shut. 400 frames in ordinary play: GL 7.1 s, VK 7.2 s - the lens does not draw there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
51 lines
2.3 KiB
GLSL
51 lines
2.3 KiB
GLSL
// Depth of field, for the viewfinder. The camera mode (the game's photo mode) has had a frame,
|
|
// a zoom and a grade and no LENS: everything from the bootlaces to the Bells was equally sharp,
|
|
// which is the one thing a photograph never is.
|
|
//
|
|
// A disc of taps whose radius is this pixel's circle of confusion. It runs at full resolution
|
|
// and costs nothing when the aperture is shut, because the whole pass is skipped - in ordinary
|
|
// play there is no lens and this never draws.
|
|
in vec2 v_uv;
|
|
out vec4 o_color;
|
|
uniform sampler2D u_src;
|
|
uniform sampler2D u_depth;
|
|
uniform mat4 u_inv_proj;
|
|
uniform vec2 u_texel;
|
|
uniform float u_focus; // metres to the plane in focus
|
|
uniform float u_aperture; // how fast the blur opens either side of it
|
|
uniform float u_max_coc; // in pixels, so a wide aperture cannot eat the whole frame
|
|
|
|
float viewZ(vec2 uv) {
|
|
float d = texture(u_depth, uv).r;
|
|
vec4 p = u_inv_proj * vec4(uv * 2.0 - 1.0, d * 2.0 - 1.0, 1.0);
|
|
return -(p.z / p.w);
|
|
}
|
|
|
|
void main() {
|
|
float z = viewZ(v_uv);
|
|
// The circle of confusion, signed so the two sides of the focal plane can be told apart, and
|
|
// normalised by the focus distance: a lens focused at two metres throws the background out
|
|
// far harder than one focused at two hundred, and a constant here made a landscape shot read
|
|
// as a macro.
|
|
float coc = (z - u_focus) / max(z, 0.1) * u_aperture;
|
|
float r = clamp(abs(coc), 0.0, 1.0) * u_max_coc;
|
|
if (r < 0.75) { o_color = vec4(texture(u_src, v_uv).rgb, 1.0); return; }
|
|
// a 16-point spiral, which reads as a lens rather than as a box blur
|
|
vec3 acc = texture(u_src, v_uv).rgb;
|
|
float wsum = 1.0;
|
|
for (int i = 0; i < 16; i++) {
|
|
float a = float(i) * 2.3999632; // golden angle
|
|
float rr = sqrt((float(i) + 0.5) / 16.0) * r;
|
|
vec2 off = vec2(cos(a), sin(a)) * rr * u_texel;
|
|
vec2 uv2 = v_uv + off;
|
|
// Do not drag a SHARP FOREGROUND pixel into a blurred background: that is the halo every
|
|
// naive depth-of-field has round a near object. A tap only counts if it is at least as far
|
|
// out of focus as this pixel is.
|
|
float z2 = viewZ(uv2);
|
|
float coc2 = abs((z2 - u_focus) / max(z2, 0.1) * u_aperture);
|
|
float w = smoothstep(0.0, 0.35, coc2 * u_max_coc / max(r, 1e-3));
|
|
acc += texture(u_src, uv2).rgb * w;
|
|
wsum += w;
|
|
}
|
|
o_color = vec4(acc / wsum, 1.0);
|
|
}
|