scena 1.7.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
struct VertexOut {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
};

struct PostUniform {
    viewport: vec4<f32>,
    config: vec4<f32>,
};

@group(0) @binding(0)
var source_texture: texture_2d<f32>;

@group(0) @binding(1)
var<uniform> post: PostUniform;

@vertex
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOut {
    var positions = array<vec2<f32>, 3>(
        vec2<f32>(-1.0, -3.0),
        vec2<f32>(-1.0, 1.0),
        vec2<f32>(3.0, 1.0),
    );
    let position = positions[vertex_index];
    var out: VertexOut;
    out.position = vec4<f32>(position, 0.0, 1.0);
    out.uv = position * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5, 0.5);
    return out;
}

@fragment
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
    let dims = vec2<i32>(textureDimensions(source_texture));
    let coord = clamp(vec2<i32>(in.uv * vec2<f32>(dims)), vec2<i32>(0), dims - vec2<i32>(1));
    let source = textureLoad(source_texture, coord, 0);

    let height = max(1.0, post.viewport.y);
    let horizon_y = clamp(post.config.z * height, 1.0, height - 2.0);
    if f32(coord.y) < horizon_y {
        return source;
    }

    let strength = clamp(post.config.x, 0.0, 1.0);
    if strength <= 0.0 {
        return source;
    }
    let floor_height = max(1.0, height - horizon_y);
    let distance = clamp((f32(coord.y) - horizon_y) / floor_height, 0.0, 1.0);
    let fade = clamp(1.0 - distance * clamp(post.config.w, 0.0, 1.0), 0.0, 1.0);
    let floor_mask = clamp(1.0 - luma(source.rgb), 0.0, 1.0);
    let alpha = clamp(strength * fade * floor_mask, 0.0, 1.0);
    if alpha <= 0.0 {
        return source;
    }

    let mirrored_y = i32(clamp(2.0 * horizon_y - f32(coord.y), 0.0, height - 1.0));
    let radius = i32(clamp(round(post.config.y * 8.0), 0.0, 8.0));
    let reflected = reflected_sample(coord.x, mirrored_y, radius, dims);
    return vec4<f32>(mix(source.rgb, reflected, alpha), source.a);
}

fn reflected_sample(x: i32, y: i32, radius: i32, dims: vec2<i32>) -> vec3<f32> {
    if radius <= 0 {
        return textureLoad(source_texture, vec2<i32>(x, y), 0).rgb;
    }
    var sum = vec3<f32>(0.0);
    var weight_sum = 0.0;
    for (var dy = -8; dy <= 8; dy = dy + 1) {
        for (var dx = -8; dx <= 8; dx = dx + 1) {
            if abs(dx) > radius || abs(dy) > radius {
                continue;
            }
            let distance = length(vec2<f32>(f32(dx), f32(dy)));
            let weight = max(0.0, 1.0 - distance / (f32(radius) + 1.0));
            if weight <= 0.0 {
                continue;
            }
            let sample_coord = clamp(vec2<i32>(x + dx, y + dy), vec2<i32>(0), dims - vec2<i32>(1));
            sum = sum + textureLoad(source_texture, sample_coord, 0).rgb * weight;
            weight_sum = weight_sum + weight;
        }
    }
    if weight_sum <= 0.0 {
        return textureLoad(source_texture, vec2<i32>(x, y), 0).rgb;
    }
    return sum / weight_sum;
}

fn luma(rgb: vec3<f32>) -> f32 {
    return dot(rgb, vec3<f32>(0.299, 0.587, 0.114));
}