← Build a viewer

src/workshop.wgsl

Repository source snapshot · Plain text · GitHub

1// ANCHOR: layout2struct Uniforms { view_projection: mat4x4<f32>, viewport_stage: vec4<f32> };3struct Instance { center_alpha: vec4<f32>, scale_pad: vec4<f32>, color_pad: vec4<f32> };4@group(0) @binding(0) var<uniform> globals: Uniforms;5@group(0) @binding(1) var<storage, read> instances: array<Instance>;6struct Out {7    @builtin(position) clip: vec4<f32>,8    @location(0) local: vec2<f32>,9    @location(1) rgba: vec4<f32>,10};11// END: layout1213fn corner(i: u32) -> vec2<f32> {14    let corners = array<vec2<f32>, 6>(vec2(-1., -1.), vec2(1., -1.), vec2(-1., 1.), vec2(-1., 1.), vec2(1., -1.), vec2(1., 1.));15    return corners[i];16}1718// ANCHOR: jacobian19fn project_axis(center: vec4<f32>, axis: vec3<f32>) -> vec2<f32> {20    let delta = globals.view_projection * vec4(axis, 0.0);21    // Quotient rule for (clip.xy / clip.w), then NDC -> pixels.22    return (delta.xy * center.w - center.xy * delta.w) / (center.w * center.w) * globals.viewport_stage.xy * 0.5;23}24// END: jacobian2526@vertex fn vs_main(@builtin(vertex_index) vertex: u32, @builtin(instance_index) index: u32) -> Out {27    let stage = u32(globals.viewport_stage.z);28    var out: Out;29    // ANCHOR: triangle30    if stage == 1u {31        let positions = array<vec2<f32>, 3>(vec2(-0.7, -0.6), vec2(0.7, -0.6), vec2(0.0, 0.7));32        let colors = array<vec3<f32>, 3>(vec3(1., 0., 0.), vec3(0., 1., 0.), vec3(0., 0., 1.));33        out.clip = vec4(positions[vertex], 0.5, 1.0);34        out.local = vec2(0.0);35        out.rgba = vec4(colors[vertex], 1.0);36        return out;37    }38    // END: triangle39    let splat = instances[index];40    let q = corner(vertex);41    out.local = q * 3.0;42    out.rgba = vec4(splat.color_pad.xyz, splat.center_alpha.w);43    if stage < 5u {44        out.clip = vec4(q * vec2(0.72, 0.62), 0.5, 1.0);45        return out;46    }47    // ANCHOR: covariance48    let center = globals.view_projection * vec4(splat.center_alpha.xyz, 1.0);49    let scale = splat.scale_pad.xyz;50    // Axis-aligned world ellipsoids here; the real pass also rotates these axes.51    let a0 = project_axis(center, vec3(scale.x, 0., 0.));52    let a1 = project_axis(center, vec3(0., scale.y, 0.));53    let a2 = project_axis(center, vec3(0., 0., scale.z));54    let a = a0.x*a0.x + a1.x*a1.x + a2.x*a2.x + 0.3;55    let b = a0.x*a0.y + a1.x*a1.y + a2.x*a2.y;56    let c = a0.y*a0.y + a1.y*a1.y + a2.y*a2.y + 0.3;57    let root = sqrt(max(0.0, (a-c)*(a-c) + 4.0*b*b));58    let large = max(0.1, 0.5*(a+c+root));59    let small = max(0.1, 0.5*(a+c-root));60    var axis = vec2(1.0, 0.0);61    if abs(b) > 0.00001 { axis = normalize(vec2(b, large-a)); }62    else if c > a { axis = vec2(0.0, 1.0); }63    let perpendicular = vec2(-axis.y, axis.x);64    let pixels = 3.0 * (q.x * sqrt(large) * axis + q.y * sqrt(small) * perpendicular);65    let ndc = pixels * 2.0 / globals.viewport_stage.xy;66    out.clip = center + vec4(ndc * center.w, 0.0, 0.0);67    // END: covariance68    return out;69}7071// ANCHOR: fragment72@fragment fn fs_main(input: Out) -> @location(0) vec4<f32> {73    let stage = u32(globals.viewport_stage.z);74    if stage <= 2u { return vec4(input.rgba.rgb, 1.0); }75    let weight = exp(-0.5 * dot(input.local, input.local));76    let alpha = min(0.99, input.rgba.a * weight);77    if alpha < 1.0 / 255.0 { discard; }78    return vec4(input.rgba.rgb * alpha, alpha);79}80// END: fragment81