fidget-wgpu 0.5.0

WGPU backend for Fidget
Documentation
struct SsaoConfig {
    /// Image size, in voxels
    image_size: vec3u,
    radius: f32,
}

@group(0) @binding(0) var<uniform> config: SsaoConfig;
@group(0) @binding(1) var<storage, read> image: array<PackedVoxel>;
@group(0) @binding(2) var<storage, read_write> out: array<f32>;

@group(1) @binding(0) var<storage, read> kernel: array<array<f32, 3>>;
@group(1) @binding(1) var<storage, read> noise: array<vec2f>;

@compute @workgroup_size(8, 8)
fn ssao_main(
    @builtin(global_invocation_id) global_id: vec3u
) {
    // Clamp to image size
    if global_id.x >= config.image_size.x ||
       global_id.y >= config.image_size.y
    {
        return;
    }
    let i = global_id.x + global_id.y * config.image_size.x;

    // Early exit for unpopulated pixels
    //
    // Unlike the CPU shader, we use -1.0 as the empty marker (because NaN
    // handling is iffy on the GPU).
    let pixel = unpack(image[i]).pixel;
    if pixel.depth == 0u {
        out[i] = -1.0;
        return;
    }
    let n = pixel.normal;

    // Convert to [-1, 1] coordinate space
    let size_f32 = vec3<f32>(config.image_size);
    let pos_f32 = vec3<f32>(vec2<f32>(global_id.xy) + 0.5, f32(pixel.depth));

    let scale_min = f32(min(
        config.image_size.x,
        min(config.image_size.y, config.image_size.z)
    ));
    let scale_x = scale_min / f32(config.image_size.x);
    let scale_y = scale_min / f32(config.image_size.y);
    let scale_z = scale_min / f32(config.image_size.z);

    // See writeup in `fidget_raster` for details here
    let p = ((pos_f32 / size_f32) - 0.5) * 2.0;
    let rvec = vec3f(noise[pcg2d(global_id.xy).x % arrayLength(&noise)], 0.0);
    let tangent = normalize(rvec - n * dot(rvec, n));
    let bitangent = cross(n, tangent);
    let tbn = mat3x3(tangent, bitangent, n);

    var occlusion = 0.0;
    for (var j=0u; j < arrayLength(&kernel); j++) {
        // offset in world coordinates (with compensation for aspect ratio)
        let k = vec3f(kernel[j][0], kernel[j][1], kernel[j][2]);
        var offset = tbn * k * config.radius;
        offset.x *= scale_x;
        offset.y *= scale_y;
        offset.z *= scale_z;

        // position in world coordinates
        let sample_pos = p + offset;

        // XXX the implementation in `fidget_raster` says "this distorts samples
        // for non-square images"; is this true?
        let pos_voxels = vec3i((sample_pos / 2.0 + 0.5) * vec3f(config.image_size));

        // actual_h is the height from the heightmap image
        var actual_h = 0.0;
        if pos_voxels.x >= 0 &&
           pos_voxels.y >= 0 &&
           u32(pos_voxels.x) < config.image_size.x &&
           u32(pos_voxels.y) < config.image_size.y
        {
            let d = image[
                u32(pos_voxels.x) +
                u32(pos_voxels.y) * config.image_size.x
            ].depth;
            if d != 0 {
                actual_h = f32(d);
            }
        }

        let actual_z = ((f32(actual_h) / f32(config.image_size.z)) - 0.5) * 2.0;
        let dz = sample_pos.z - actual_z;
        if sample_pos.z <= actual_z {
            if dz < config.radius {
                occlusion += 1.0;
            } else if dz < config.radius * 2.0 {
                occlusion += pow((config.radius - (dz - config.radius)) / config.radius, 2);
            }
        }
    }
    out[i] = 1.0 - (occlusion / f32(arrayLength(&kernel)));
}

/// Hash function to mix X and Y integer values
///
/// Source: "Hash Functions for GPU Rendering" (Jarzynski & Olano, 2020)
/// https://jcgt.org/published/0009/03/02/paper.pdf
fn pcg2d(v_in: vec2u) -> vec2u {
    var v = v_in * 1664525u + 1013904223u;
    v.x += v.y * 1664525u;
    v.y += v.x * 1664525u;
    v = v ^ (v >> vec2u(16u));
    v.x += v.y * 1664525u;
    v.y += v.x * 1664525u;
    v = v ^ (v >> vec2u(16u));
    return v;
}