viewport-lib 0.19.0

3D viewport rendering library
Documentation
// BRDF integration LUT for split-sum specular approximation.
//
// Output is a 128x128 Rgba16Float texture. R = a (scale on F0), G = b (bias on F0).
// One invocation per destination texel; 1024 Hammersley samples each.
//
// This is the GPU equivalent of `generate_brdf_lut` in environment.rs. The LUT
// is scene-independent so this dispatch only ever runs once per renderer.

const PI: f32 = 3.14159265358979;
const NUM_SAMPLES: u32 = 1024u;

@group(0) @binding(0) var dst_tex: texture_storage_2d<rgba16float, write>;

fn radical_inverse_vdc(b: u32) -> f32 {
    var bits = b;
    bits = (bits << 16u) | (bits >> 16u);
    bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
    bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
    bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
    bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
    return f32(bits) * 2.3283064e-10;
}

fn hammersley(i: u32, n: u32) -> vec2<f32> {
    return vec2<f32>(f32(i) / f32(n), radical_inverse_vdc(i));
}

fn importance_sample_ggx(xi: vec2<f32>, n: vec3<f32>, a: f32) -> vec3<f32> {
    let phi = 2.0 * PI * xi.x;
    let cos_theta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
    let sin_theta = sqrt(1.0 - cos_theta * cos_theta);
    let h_ts = vec3<f32>(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta);

    var up: vec3<f32>;
    if (abs(n.z) < 0.999) {
        up = vec3<f32>(0.0, 0.0, 1.0);
    } else {
        up = vec3<f32>(1.0, 0.0, 0.0);
    }
    let tangent = normalize(cross(up, n));
    let bitangent = cross(n, tangent);
    return normalize(h_ts.x * tangent + h_ts.y * bitangent + h_ts.z * n);
}

fn geometry_smith(n_dot_v: f32, n_dot_l: f32, roughness: f32) -> f32 {
    let k = (roughness * roughness) / 2.0;
    let g1v = n_dot_v / (n_dot_v * (1.0 - k) + k);
    let g1l = n_dot_l / (n_dot_l * (1.0 - k) + k);
    return g1v * g1l;
}

fn integrate_brdf(n_dot_v: f32, roughness: f32) -> vec2<f32> {
    let v = vec3<f32>(sqrt(1.0 - n_dot_v * n_dot_v), 0.0, n_dot_v);
    let n = vec3<f32>(0.0, 0.0, 1.0);
    let a = roughness * roughness;

    var a_out: f32 = 0.0;
    var b_out: f32 = 0.0;

    for (var i: u32 = 0u; i < NUM_SAMPLES; i = i + 1u) {
        let xi = hammersley(i, NUM_SAMPLES);
        let h = importance_sample_ggx(xi, n, a);
        let l = normalize(2.0 * dot(v, h) * h - v);
        let n_dot_l = max(l.z, 0.0);
        let n_dot_h = max(h.z, 0.0);
        let v_dot_h = max(dot(v, h), 0.0);

        if (n_dot_l > 0.0) {
            let g = geometry_smith(n_dot_v, n_dot_l, roughness);
            let g_vis = (g * v_dot_h) / max(n_dot_h * n_dot_v, 0.001);
            let fc = pow(1.0 - v_dot_h, 5.0);
            a_out += (1.0 - fc) * g_vis;
            b_out += fc * g_vis;
        }
    }
    let inv = 1.0 / f32(NUM_SAMPLES);
    return vec2<f32>(a_out * inv, b_out * inv);
}

@compute @workgroup_size(8, 8)
fn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {
    let dims = textureDimensions(dst_tex);
    if (gid.x >= dims.x || gid.y >= dims.y) {
        return;
    }

    let n_dot_v = max((f32(gid.x) + 0.5) / f32(dims.x), 0.001);
    let roughness = max((f32(gid.y) + 0.5) / f32(dims.y), 0.01);

    let ab = integrate_brdf(n_dot_v, roughness);
    textureStore(dst_tex, vec2<i32>(gid.xy), vec4<f32>(ab, 0.0, 1.0));
}