rein 0.1.1

rein 3D Rendering Library
Documentation
// FXAA (Fast Approximate Anti-Aliasing) shader
// Based on NVIDIA FXAA 3.11 Quality preset

struct VertexInput {
    @location(0) position: vec3<f32>,
    @location(1) uv: vec4<f32>,
};

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
};

struct FxaaUniform {
    // x: width, y: height, z: 1/width, w: 1/height
    texture_size: vec4<f32>,
};

@group(0) @binding(0)
var input_texture: texture_2d<f32>;
@group(0) @binding(1)
var input_sampler: sampler;
@group(0) @binding(2)
var<uniform> params: FxaaUniform;

// FXAA quality settings
const FXAA_EDGE_THRESHOLD: f32 = 0.166;
const FXAA_EDGE_THRESHOLD_MIN: f32 = 0.0833;
const FXAA_SUBPIX: f32 = 0.75;

// Convert RGB to luma (perceived brightness)
fn rgb_to_luma(rgb: vec3<f32>) -> f32 {
    return dot(rgb, vec3<f32>(0.299, 0.587, 0.114));
}

@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
    var output: VertexOutput;
    output.position = vec4<f32>(input.position.xy, 0.0, 1.0);
    output.uv = input.uv.xy;
    return output;
}

@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
    let uv = input.uv;
    let texel_size = params.texture_size.zw;

    // Sample center and neighboring pixels
    let center = textureSample(input_texture, input_sampler, uv);
    let luma_center = rgb_to_luma(center.rgb);

    // Sample the 4 direct neighbors
    let luma_n = rgb_to_luma(textureSample(input_texture, input_sampler, uv + vec2<f32>(0.0, -texel_size.y)).rgb);
    let luma_s = rgb_to_luma(textureSample(input_texture, input_sampler, uv + vec2<f32>(0.0, texel_size.y)).rgb);
    let luma_e = rgb_to_luma(textureSample(input_texture, input_sampler, uv + vec2<f32>(texel_size.x, 0.0)).rgb);
    let luma_w = rgb_to_luma(textureSample(input_texture, input_sampler, uv + vec2<f32>(-texel_size.x, 0.0)).rgb);

    // Find the maximum and minimum luma around the current pixel
    let luma_min = min(luma_center, min(min(luma_n, luma_s), min(luma_e, luma_w)));
    let luma_max = max(luma_center, max(max(luma_n, luma_s), max(luma_e, luma_w)));

    // Compute the contrast (delta)
    let luma_range = luma_max - luma_min;

    // If the contrast is lower than a threshold, don't apply AA
    if luma_range < max(FXAA_EDGE_THRESHOLD_MIN, luma_max * FXAA_EDGE_THRESHOLD) {
        return center;
    }

    // Sample the 4 corner neighbors
    let luma_nw = rgb_to_luma(textureSample(input_texture, input_sampler, uv + vec2<f32>(-texel_size.x, -texel_size.y)).rgb);
    let luma_ne = rgb_to_luma(textureSample(input_texture, input_sampler, uv + vec2<f32>(texel_size.x, -texel_size.y)).rgb);
    let luma_sw = rgb_to_luma(textureSample(input_texture, input_sampler, uv + vec2<f32>(-texel_size.x, texel_size.y)).rgb);
    let luma_se = rgb_to_luma(textureSample(input_texture, input_sampler, uv + vec2<f32>(texel_size.x, texel_size.y)).rgb);

    // Combine the four edges lumas
    let luma_ns = luma_n + luma_s;
    let luma_ew = luma_e + luma_w;

    // Same for corners
    let luma_nwne = luma_nw + luma_ne;
    let luma_swse = luma_sw + luma_se;

    // Compute horizontal and vertical gradients
    let edge_horz = abs(-2.0 * luma_w + luma_nwne) + abs(-2.0 * luma_center + luma_ns) * 2.0 + abs(-2.0 * luma_e + luma_swse);
    let edge_vert = abs(-2.0 * luma_n + (luma_nw + luma_ne)) + abs(-2.0 * luma_center + luma_ew) * 2.0 + abs(-2.0 * luma_s + (luma_sw + luma_se));

    // Is the edge horizontal or vertical?
    let is_horizontal = edge_horz >= edge_vert;

    // Select the two neighboring texels lumas in the opposite direction to the edge
    var luma1: f32;
    var luma2: f32;
    if is_horizontal {
        luma1 = luma_n;
        luma2 = luma_s;
    } else {
        luma1 = luma_w;
        luma2 = luma_e;
    }

    // Compute gradients in this direction
    let gradient1 = luma1 - luma_center;
    let gradient2 = luma2 - luma_center;

    // Which direction is the steepest?
    let is_1_steepest = abs(gradient1) >= abs(gradient2);

    // Gradient in the corresponding direction
    let gradient_scaled = 0.25 * max(abs(gradient1), abs(gradient2));

    // Average luma in the correct direction
    var luma_local_average: f32;
    if is_1_steepest {
        luma_local_average = 0.5 * (luma1 + luma_center);
    } else {
        luma_local_average = 0.5 * (luma2 + luma_center);
    }

    // Compute subpixel offset
    let luma_average_all = (1.0 / 12.0) * (2.0 * luma_ns + 2.0 * luma_ew + luma_nwne + luma_swse);
    let subpixel_offset1 = clamp(abs(luma_average_all - luma_center) / luma_range, 0.0, 1.0);
    let subpixel_offset2 = (-2.0 * subpixel_offset1 + 3.0) * subpixel_offset1 * subpixel_offset1;
    let subpixel_offset_final = subpixel_offset2 * subpixel_offset2 * FXAA_SUBPIX;

    // Step size
    var step_length: f32;
    if is_horizontal {
        step_length = texel_size.y;
    } else {
        step_length = texel_size.x;
    }

    // Correct for direction
    if is_1_steepest {
        step_length = -step_length;
    }

    // Shifted UV
    var uv_offset = uv;
    if is_horizontal {
        uv_offset.y += step_length * subpixel_offset_final;
    } else {
        uv_offset.x += step_length * subpixel_offset_final;
    }

    // Sample at the offset position
    let final_color = textureSample(input_texture, input_sampler, uv_offset);

    return final_color;
}