rusting_engine 2.5.7

Vulkan 3D game engine with GPU-accelerated physics for massive physics-heavy scenes
#version 450
layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput scene;
// Mip 0 of the bloom chain, half the target size.
layout(set = 0, binding = 1) uniform sampler2D bloom;
// mapper follows ToneMapper: 0 Linear, 1 Reinhard, 2 ACES. bloom is the
// glow's intensity, 0 when the frame has none; inv_extent is one over the
// target size in pixels.
// contrast, saturation, shadows, highlights and vignette follow
// ColorGrading; the defaults leave the color unchanged. shadows.w is
// ColorGrading::lens_droplets.
// A copy of the HDR image in `scene`, for effects that read other pixels;
// up to date only when `sampled` is 1.
layout(set = 0, binding = 2) uniform sampler2D scene_image;
// Factor auto exposure adapted to; read only when auto_exposure is 1.
layout(set = 0, binding = 3) readonly buffer Exposure { float value; } adapted;
// The ColorLut strip, lut_size squares of lut_size x lut_size texels; read
// only when lut > 0, its intensity.
layout(set = 1, binding = 0) uniform sampler2D lut_strip;
// Opaque depth, up to date only when blur or motion strength is above 0.
layout(set = 1, binding = 1) uniform sampler2D scene_depth;
// MotionBlur: reproject takes this frame's clip space to last frame's;
// strength is the intensity, 0 when off.
layout(set = 1, binding = 2) readonly buffer Motion {
    mat4 reproject;
    float strength;
} motion;
// film: grain, chromatic aberration, scanlines, color bleed. tape: noise
// band, distortion, seconds and frame number from the fixed tick. flags bit
// 0 is set when scene_image may be read, bit 1 for auto exposure, bit 2 for
// FXAA. focus and
// blur follow DepthOfField; depth holds the projection entries [2][2],
// [2][3], [3][2] and [3][3] (row, column) that undo stored depth.
layout(push_constant) uniform ToneMap {
    float exposure;
    uint mapper;
    float bloom;
    float contrast;
    vec2 inv_extent;
    float saturation;
    float vignette;
    vec4 shadows;
    vec4 highlights;
    vec4 film;
    vec4 tape;
    uint flags;
    float lut;
    float focus;
    float blur;
    vec4 depth;
} tone;
layout(location = 0) out vec4 f_color;
// Uniform 0..1 from a pixel and a frame number, the same on every GPU.
float noise(uvec2 pixel, uint frame) {
    uint h = pixel.x * 1973u + pixel.y * 9277u + frame * 26699u;
    h = (h ^ 61u) ^ (h >> 16u);
    h *= 9u;
    h ^= h >> 4u;
    h *= 0x27d4eb2du;
    h ^= h >> 15u;
    return float(h) / 4294967295.0;
}
vec3 hdr_at(vec2 uv) {
    if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) {
        return vec3(0.0);
    }
    vec3 c = textureLod(scene_image, uv, 0.0).rgb;
    if (tone.bloom > 0.0) {
        c += textureLod(bloom, uv, 0.0).rgb * tone.bloom;
    }
    return c;
}
// Blur radius at uv as a fraction of the screen height.
float blur_at(vec2 uv) {
    float d = textureLod(scene_depth, uv, 0.0).r;
    float z = (tone.depth.y - d * tone.depth.w) / (d * tone.depth.z - tone.depth.x);
    return min(tone.blur * abs(1.0 - tone.focus / max(-z, 1e-4)), tone.blur);
}
// Gathers a disc the size of this pixel's blur. Nearer samples sharper than
// it would reach this pixel stay out, so focused edges do not smear over a
// blurred background.
// ponytail: blurred foreground does not spread over a sharp background;
// a scatter or tiled max-blur pass would add that.
vec3 defocus(vec2 uv) {
    float radius = blur_at(uv);
    float depth = textureLod(scene_depth, uv, 0.0).r;
    vec2 to_uv = vec2(tone.inv_extent.x / tone.inv_extent.y, 1.0);
    vec3 sum = hdr_at(uv);
    float weight = 1.0;
    if (radius / tone.inv_extent.y < 0.5) {
        return sum;
    }
    for (int i = 0; i < 32; i++) {
        // Golden-angle spiral, evenly covering the disc.
        float r = sqrt((float(i) + 0.5) / 32.0) * radius;
        float a = float(i) * 2.39996;
        vec2 at = uv + vec2(cos(a), sin(a)) * r * to_uv;
        float reach = blur_at(at);
        float w = textureLod(scene_depth, at, 0.0).r < depth
            ? clamp((reach - r) / tone.inv_extent.y + 1.0, 0.0, 1.0)
            : 1.0;
        sum += hdr_at(at) * w;
        weight += w;
    }
    return sum / weight;
}
float luma_of(vec3 c) {
    return dot(c, vec3(0.2126, 0.7152, 0.0722));
}
// Perceived brightness after exposure, roughly as the tone curve shows it,
// so edges between bright areas count as much as dark ones.
float edge_luma(vec2 uv) {
    float l = luma_of(hdr_at(uv)) * tone.exposure;
    return sqrt(l / (1.0 + l));
}
// FXAA in the style of Lottes' console version: four diagonal lumas give
// the edge direction, then two short and two long taps along the edge
// blend it; the long blend is dropped when it leaves the local luma range.
vec3 fxaa(vec2 uv) {
    vec2 px = tone.inv_extent;
    float nw = edge_luma(uv + vec2(-0.5, -0.5) * px);
    float ne = edge_luma(uv + vec2(0.5, -0.5) * px);
    float sw = edge_luma(uv + vec2(-0.5, 0.5) * px);
    float se = edge_luma(uv + vec2(0.5, 0.5) * px);
    vec3 center = hdr_at(uv);
    float m = sqrt(luma_of(center) * tone.exposure / (1.0 + luma_of(center) * tone.exposure));
    float lo = min(m, min(min(nw, ne), min(sw, se)));
    float hi = max(m, max(max(nw, ne), max(sw, se)));
    if (hi - lo < max(0.0625, hi * 0.125)) {
        return center;
    }
    vec2 dir = vec2(-((nw + ne) - (sw + se)), (nw + sw) - (ne + se));
    float reduce = max((nw + ne + sw + se) * 0.03125, 1.0 / 128.0);
    dir = clamp(dir / (min(abs(dir.x), abs(dir.y)) + reduce), -8.0, 8.0) * px;
    vec3 near = 0.5 * (hdr_at(uv + dir * (1.0 / 3.0 - 0.5))
        + hdr_at(uv + dir * (2.0 / 3.0 - 0.5)));
    vec3 far = near * 0.5 + 0.25 * (hdr_at(uv - dir * 0.5) + hdr_at(uv + dir * 0.5));
    float l = luma_of(far) * tone.exposure;
    l = sqrt(l / (1.0 + l));
    return l < lo || l > hi ? near : far;
}
// Rain on the lens: two grids of cells, each holding at most one round drop
// that lives a few seconds at a random spot and shows the image behind it
// flipped, like a small lens, with a darker rim. strength is the share of
// cells with a drop; everything follows seconds, so a tick repeats.
vec3 lens_droplets(vec2 uv, vec3 color, float strength, float seconds) {
    vec2 aspect = vec2(tone.inv_extent.y / tone.inv_extent.x, 1.0);
    for (uint layer = 0u; layer < 2u; layer++) {
        float cells = layer == 0u ? 6.0 : 11.0;
        vec2 p = uv * aspect * cells;
        uvec2 id = uvec2(floor(p)) + uvec2(layer * 977u, 0u);
        float t = seconds / (3.0 + 2.0 * noise(id, 1u)) + noise(id, 2u);
        uint life = uint(t) * 8u;
        if (noise(id, life + 3u) >= strength) {
            continue;
        }
        vec2 center = 0.3 + 0.4 * vec2(noise(id, life + 4u), noise(id, life + 5u));
        float radius = 0.12 + 0.16 * noise(id, life + 6u);
        vec2 d = (fract(p) - center) / radius;
        float r2 = dot(d, d);
        if (r2 >= 1.0) {
            continue;
        }
        float age = fract(t);
        float fade = smoothstep(0.0, 0.05, age) * (1.0 - smoothstep(0.6, 1.0, age));
        vec2 bend = d * radius / (cells * aspect) * 1.6;
        vec3 seen = hdr_at(uv - bend) * (1.0 - 0.5 * smoothstep(0.5, 1.0, r2));
        return mix(color, seen, fade);
    }
    return color;
}
// The post shader hook; see post.glsl.
#include "post.glsl"
void main() {
    vec4 hdr = subpassLoad(scene);
    vec2 uv = gl_FragCoord.xy * tone.inv_extent;
    if ((tone.flags & 1u) != 0u) {
        if (tone.tape.y > 0.0) {
            // Tube bulge, plus rows that sway slowly with time.
            vec2 centered = uv - 0.5;
            uv = 0.5 + centered * (1.0 - tone.tape.y * 0.25
                + tone.tape.y * 0.5 * dot(centered, centered));
            uv.x += tone.tape.y * 0.004
                * sin(uv.y * 60.0 + tone.tape.z * 5.0);
        }
        hdr.rgb = tone.blur > 0.0 ? defocus(uv)
            : (tone.flags & 4u) != 0u ? fxaa(uv) : hdr_at(uv);
        if (tone.shadows.w > 0.0) {
            hdr.rgb = lens_droplets(uv, hdr.rgb, tone.shadows.w, tone.tape.z);
        }
        if (motion.strength > 0.0) {
            // Screen motion since last frame, in uv, centered on the pixel.
            float d = textureLod(scene_depth, uv, 0.0).r;
            vec4 last = motion.reproject * vec4(uv * 2.0 - 1.0, d, 1.0);
            vec2 moved = (uv - (last.xy / last.w * 0.5 + 0.5)) * motion.strength;
            float pixels = length(moved / tone.inv_extent);
            // Longer than a sixth of the height is a cut, not motion.
            if (pixels > 0.5 && pixels * tone.inv_extent.y < 0.17) {
                vec3 sum = vec3(0.0);
                for (int i = 0; i < 16; i++) {
                    sum += hdr_at(uv + moved * (float(i) / 15.0 - 0.5));
                }
                hdr.rgb = sum / 16.0;
            }
        }
        if (tone.film.y > 0.0) {
            vec2 split = (uv - 0.5) * tone.film.y * 0.02;
            hdr.r = hdr_at(uv + split).r;
            hdr.b = hdr_at(uv - split).b;
        }
        if (tone.film.w > 0.0) {
            // Color smears to the right; brightness stays sharp.
            vec2 step_x = vec2(tone.film.w * 8.0 * tone.inv_extent.x, 0.0);
            vec3 smear = (hdr.rgb + hdr_at(uv - step_x) + hdr_at(uv - 2.0 * step_x)
                + hdr_at(uv - 3.0 * step_x)) * 0.25;
            hdr.rgb = max(smear - luma_of(smear) + luma_of(hdr.rgb), 0.0);
        }
    } else if (tone.bloom > 0.0) {
        hdr.rgb += textureLod(bloom, uv, 0.0).rgb * tone.bloom;
    }
    float exposure = tone.exposure;
    if ((tone.flags & 2u) != 0u) {
        exposure *= adapted.value;
    }
    vec3 c = max(hdr.rgb * exposure, 0.0);
    if (tone.mapper == 1u) {
        c = c / (1.0 + c);
    } else if (tone.mapper == 2u) {
        // Narkowicz 2015 fit of the ACES filmic curve.
        c = (c * (2.51 * c + 0.03)) / (c * (2.43 * c + 0.59) + 0.14);
    }
    c = clamp(c, 0.0, 1.0);
    float luma = dot(c, vec3(0.2126, 0.7152, 0.0722));
    c = max(mix(vec3(luma), c, tone.saturation), 0.0);
    if (tone.contrast != 1.0) {
        // Contrast in log space around linear mid grey.
        c = 0.18 * pow(c / 0.18, vec3(tone.contrast));
    }
    c *= mix(tone.shadows.rgb, tone.highlights.rgb, smoothstep(0.0, 1.0, luma));
    if (tone.vignette > 0.0) {
        vec2 centered = gl_FragCoord.xy * tone.inv_extent - 0.5;
        c *= 1.0 - tone.vignette * smoothstep(0.25, 0.75, length(centered));
    }
    if (tone.lut > 0.0) {
        // Look up in sRGB, as the strip was authored; the texture decodes
        // back to linear. Blue blends two neighboring squares.
        float n = float(textureSize(lut_strip, 0).y);
        vec3 encoded = mix(12.92 * c, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055,
            step(0.0031308, c));
        vec3 at = clamp(encoded, 0.0, 1.0) * (n - 1.0);
        float square = min(floor(at.b), n - 2.0);
        vec2 texel = 1.0 / vec2(n * n, n);
        vec2 uv = (vec2(at.r + square * n, at.g) + 0.5) * texel;
        vec3 graded = mix(
            textureLod(lut_strip, uv, 0.0).rgb,
            textureLod(lut_strip, uv + vec2(n * texel.x, 0.0), 0.0).rgb,
            at.b - square
        );
        c = mix(c, graded, tone.lut);
    }
    uint frame = uint(tone.tape.w);
    if (tone.film.z > 0.0) {
        // Every other pixel row darkens.
        c *= 1.0 - tone.film.z * 0.6 * float(uint(gl_FragCoord.y) & 1u);
    }
    if (tone.tape.x > 0.0) {
        // A band of static an eighth of the height, rolling down once
        // every four seconds.
        float row = gl_FragCoord.y * tone.inv_extent.y;
        float away = abs(fract(row - tone.tape.z * 0.25 + 0.5) - 0.5);
        float band = tone.tape.x * (1.0 - smoothstep(0.0, 0.0625, away));
        float hiss = noise(uvec2(gl_FragCoord.xy) / uvec2(2u, 1u), frame + 7u);
        c = mix(c, vec3(hiss), band * 0.7);
    }
    if (tone.film.x > 0.0) {
        c += tone.film.x * 0.3 * (noise(uvec2(gl_FragCoord.xy), frame) - 0.5);
    }
    post(PostInput(gl_FragCoord.xy * tone.inv_extent, gl_FragCoord.xy,
        tone.inv_extent, tone.tape.z), c);
    f_color = vec4(clamp(c, 0.0, 1.0), hdr.a);
}