myth_render 0.3.0

Core rendering system for the Myth engine (render graph, pipeline, GPU management).
Documentation
{$ include 'core/full_screen_vertex' $}

{{ struct_definitions }}

@group(0) @binding(0) var t_scene_color: texture_2d<f32>;
@group(0) @binding(1) var t_clean_indirect: texture_2d<f32>;
@group(0) @binding(2) var t_material_data: texture_2d<f32>;
@group(0) @binding(3) var t_depth: texture_depth_2d;
@group(0) @binding(4) var t_normal: texture_2d<f32>;
@group(0) @binding(5) var s_linear: sampler;
@group(0) @binding(6) var s_point: sampler;
@group(0) @binding(7) var<uniform> u_ssgi: SsgiUniforms;

fn saturate(v: f32) -> f32 {
    return clamp(v, 0.0, 1.0);
}

fn unpack_view_normal(packed: vec4<f32>) -> vec3<f32> {
    let raw = packed.xyz * 2.0 - 1.0;
    return normalize(select(vec3<f32>(0.0, 0.0, 1.0), raw, dot(raw, raw) > 1e-5));
}

fn linearize_depth(z: f32) -> f32 {
    return u_ssgi.temporal_params.z / max(z, 0.0001);
}

fn resolve_full_uv(half_pixel: vec2<u32>) -> vec2<f32> {
    if (u_ssgi.frame_params.z == 0u) {
        return (vec2<f32>(half_pixel) + vec2<f32>(0.5, 0.5)) * u_ssgi.half_resolution.zw;
    }

    let frame = u_ssgi.frame_params.x;
    let base = vec2<f32>(half_pixel * 2u);
    let offset_x = f32((frame & 1u) ^ ((frame & 2u) >> 1u));
    let offset_y = f32((frame & 2u) >> 1u);
    let offset = vec2<f32>(offset_x + 0.5, offset_y + 0.5);
    return (base + offset) * u_ssgi.full_resolution.zw;
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    let scene_color = textureSampleLevel(t_scene_color, s_point, in.uv, 0.0);
    let material_data = textureSampleLevel(t_material_data, s_point, in.uv, 0.0);
    let albedo = material_data.rgb;

    let full_depth = textureSampleLevel(t_depth, s_point, in.uv, 0u);
    let full_normal_packed = textureSampleLevel(t_normal, s_point, in.uv, 0.0);
    if (full_depth <= 0.0 || full_normal_packed.a < 0.5) {
        return scene_color;
    }

    let full_linear = linearize_depth(full_depth);
    let full_normal = unpack_view_normal(full_normal_packed);
    let half_extent = vec2<i32>(textureDimensions(t_clean_indirect));
    let max_coord = half_extent - vec2<i32>(1, 1);
    let half_pos = in.uv * u_ssgi.half_resolution.xy - vec2<f32>(0.5, 0.5);
    let base_coord = vec2<i32>(floor(half_pos));
    let frac = fract(half_pos);

    var sum_color = vec3<f32>(0.0);
    var sum_weight = 0.0;

    for (var y: i32 = 0; y <= 1; y++) {
        for (var x: i32 = 0; x <= 1; x++) {
            let sample_coord = clamp(base_coord + vec2<i32>(x, y), vec2<i32>(0, 0), max_coord);
            let sample_indirect = textureLoad(t_clean_indirect, sample_coord, 0);
            if (sample_indirect.a <= 0.0) {
                continue;
            }

            let sample_uv = resolve_full_uv(vec2<u32>(sample_coord));
            let sample_depth = textureSampleLevel(t_depth, s_point, sample_uv, 0u);
            let sample_normal_packed = textureSampleLevel(t_normal, s_point, sample_uv, 0.0);
            if (sample_depth <= 0.0 || sample_normal_packed.a < 0.5) {
                continue;
            }

            let sample_linear = linearize_depth(sample_depth);
            let sample_normal = unpack_view_normal(sample_normal_packed);
            let bilinear_weight_x = select(1.0 - frac.x, frac.x, x == 1);
            let bilinear_weight_y = select(1.0 - frac.y, frac.y, y == 1);
            let spatial_weight = bilinear_weight_x * bilinear_weight_y;
            let depth_weight = exp(-abs(full_linear - sample_linear) * u_ssgi.merge_params.x);
            let normal_weight = pow(
                saturate(dot(full_normal, sample_normal)),
                u_ssgi.merge_params.y
            );
            let weight = spatial_weight * depth_weight * normal_weight;

            sum_color += sample_indirect.rgb * weight;
            sum_weight += weight;
        }
    }

    if (sum_weight <= 1e-4) {
        let nearest_coord = clamp(
            vec2<i32>(floor(half_pos + vec2<f32>(0.5, 0.5))),
            vec2<i32>(0, 0),
            max_coord
        );
        let nearest_indirect = textureLoad(t_clean_indirect, nearest_coord, 0);
        if (nearest_indirect.a <= 0.0) {
            return scene_color;
        }

        let contribution = nearest_indirect.rgb * albedo * u_ssgi.ray_params.x;
        return vec4<f32>(scene_color.rgb + contribution, scene_color.a);
    }

    let indirect = sum_color / sum_weight;
    let contribution = indirect * albedo * u_ssgi.ray_params.x;
    return vec4<f32>(scene_color.rgb + contribution, scene_color.a);
}