nightshade 0.8.2

A cross-platform data-oriented game engine.
Documentation
struct CullingUniforms {
    view_projection: mat4x4<f32>,
    camera_position: vec4<f32>,
    lod_distances: vec4<f32>,
    lod_density_scales: vec4<f32>,
    total_instances: u32,
    max_visible: u32,
    time: f32,
    _padding: f32,
}

struct GrassInstance {
    position: vec3<f32>,
    rotation: f32,
    height: f32,
    width: f32,
    species_index: u32,
    lod: u32,
    base_color: vec4<f32>,
    tip_color: vec4<f32>,
    bend: vec2<f32>,
    _padding: vec2<f32>,
}

struct DrawIndirect {
    vertex_count: u32,
    instance_count: atomic<u32>,
    first_vertex: u32,
    first_instance: u32,
}

@group(0) @binding(0) var<uniform> uniforms: CullingUniforms;
@group(0) @binding(1) var<storage, read> instances: array<GrassInstance>;
@group(0) @binding(2) var<storage, read_write> visible_indices: array<u32>;
@group(0) @binding(3) var<storage, read_write> draw_command: DrawIndirect;

fn compute_density_scale(distance: f32) -> f32 {
    let d0 = uniforms.lod_distances.x;
    let d1 = uniforms.lod_distances.y;
    let d2 = uniforms.lod_distances.z;
    let d3 = uniforms.lod_distances.w;

    let s0 = uniforms.lod_density_scales.x;
    let s1 = uniforms.lod_density_scales.y;
    let s2 = uniforms.lod_density_scales.z;
    let s3 = uniforms.lod_density_scales.w;

    if distance < d0 {
        return s0;
    } else if distance < d1 {
        let t = (distance - d0) / (d1 - d0);
        return mix(s0, s1, t);
    } else if distance < d2 {
        let t = (distance - d1) / (d2 - d1);
        return mix(s1, s2, t);
    } else if distance < d3 {
        let t = (distance - d2) / (d3 - d2);
        return mix(s2, s3, t);
    }
    return 0.0;
}

fn density_cull(instance_index: u32, density_scale: f32) -> bool {
    if density_scale >= 1.0 {
        return false;
    }
    if density_scale <= 0.0 {
        return true;
    }

    let hash = fract(sin(f32(instance_index) * 12.9898) * 43758.5453);
    return hash > density_scale;
}

@compute @workgroup_size(256)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let instance_index = global_id.x;

    if instance_index >= uniforms.total_instances {
        return;
    }

    let instance = instances[instance_index];

    if instance.height <= 0.0 {
        return;
    }

    let blade_center = instance.position + vec3<f32>(0.0, instance.height * 0.5, 0.0);
    let blade_radius = max(instance.height, instance.width);

    let clip = uniforms.view_projection * vec4<f32>(blade_center, 1.0);

    let w = clip.w;
    if w <= 0.001 {
        return;
    }

    let expand = blade_radius * 1.5;
    let padded_w = w + expand;

    if clip.x < -padded_w || clip.x > padded_w {
        return;
    }
    if clip.y < -padded_w || clip.y > padded_w {
        return;
    }
    if clip.z < -expand || clip.z > padded_w {
        return;
    }

    let distance = length(blade_center - uniforms.camera_position.xyz);

    let density_scale = compute_density_scale(distance);

    if density_cull(instance_index, density_scale) {
        return;
    }

    let slot = atomicAdd(&draw_command.instance_count, 1u);

    if slot >= uniforms.max_visible {
        atomicSub(&draw_command.instance_count, 1u);
        return;
    }

    visible_indices[slot] = instance_index;
}

@compute @workgroup_size(1)
fn reset_draw_command() {
    atomicStore(&draw_command.instance_count, 0u);
}