kiss3d 0.44.0

Keep it simple, stupid, 2D and 3D graphics engine for Rust.
Documentation
// Auxiliary-output (AOV) shaders for kiss3d's rasterizer.
//
// A single WGSL module with three fragment entry points, one per auxiliary
// render output. They share the vertex stage and uniform layout so the host
// can drive all three passes from the same scene-graph traversal.
//
// All passes render into non-multisampled targets so that read-back is exact.

// Bind group 0: per-frame uniforms (view, projection, mode flags).
struct FrameUniforms {
    view: mat4x4<f32>,
    proj: mat4x4<f32>,
    // x = 1.0 to emit camera-space normals, 0.0 for world-space normals.
    // Remaining components are reserved/padding.
    flags: vec4<f32>,
}

@group(0) @binding(0)
var<uniform> frame: FrameUniforms;

// Bind group 1: per-object uniforms (transform, scale, segmentation id).
struct ObjectUniforms {
    transform: mat4x4<f32>,
    scale: mat3x3<f32>,
    // x = segmentation id (bit-cast from u32). y/z/w reserved/padding.
    extra: vec4<u32>,
}

@group(1) @binding(0)
var<uniform> object: ObjectUniforms;

struct VertexInput {
    @location(0) position: vec3<f32>,
    @location(1) normal: vec3<f32>,
}

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    // World-space normal (object transform applied, ignoring non-uniform scale).
    @location(0) ws_normal: vec3<f32>,
    // Eye-space (camera-space) position; -z is the positive linear depth.
    @location(1) eye_pos: vec3<f32>,
}

@vertex
fn vs_main(vertex: VertexInput) -> VertexOutput {
    var out: VertexOutput;

    let scaled_pos = object.scale * vertex.position;
    let world_pos = object.transform * vec4<f32>(scaled_pos, 1.0);
    let eye_pos = frame.view * world_pos;

    out.clip_position = frame.proj * eye_pos;
    out.eye_pos = eye_pos.xyz;
    // Transform the normal by the object's rotation (the transform's upper-left
    // 3x3). Uniform meshes use this directly; this matches NormalsMaterial which
    // outputs object-local normals, but here we want world space for AOVs.
    let rot = mat3x3<f32>(
        object.transform[0].xyz,
        object.transform[1].xyz,
        object.transform[2].xyz,
    );
    out.ws_normal = rot * vertex.normal;

    return out;
}

// Linear (metric, eye-space) depth: positive distance in front of the camera.
@fragment
fn fs_depth(in: VertexOutput) -> @location(0) f32 {
    return -in.eye_pos.z;
}

// Surface normals, encoded from [-1, 1] into [0, 1]. World-space by default,
// camera-space when frame.flags.x is set.
@fragment
fn fs_normals(in: VertexOutput) -> @location(0) vec4<f32> {
    var n = normalize(in.ws_normal);
    if (frame.flags.x > 0.5) {
        // Rotate the world normal into camera space.
        let view_rot = mat3x3<f32>(
            frame.view[0].xyz,
            frame.view[1].xyz,
            frame.view[2].xyz,
        );
        n = normalize(view_rot * n);
    }
    let color = (n + vec3<f32>(1.0)) / 2.0;
    return vec4<f32>(color, 1.0);
}

// Segmentation: raw integer object id into an R32Uint target.
@fragment
fn fs_segmentation(in: VertexOutput) -> @location(0) u32 {
    return object.extra.x;
}