nightshade 0.8.2

A cross-platform data-oriented game engine.
Documentation
struct Uniforms {
    view: mat4x4<f32>,
    projection: mat4x4<f32>,
    camera_position: vec4<f32>,
    num_lights: vec4<u32>,
    ambient_light: vec4<f32>,
    light_view_projection: mat4x4<f32>,
    shadow_bias: f32,
    shadows_enabled: f32,
    global_unlit: f32,
    shadow_normal_bias: f32,
    snap_resolution: vec2<f32>,
    snap_enabled: u32,
    affine_enabled: u32,
    fog_color: vec3<f32>,
    fog_enabled: u32,
    fog_start: f32,
    fog_end: f32,
    cascade_count: u32,
    directional_light_size: f32,
    cascade_view_projections: array<mat4x4<f32>, 4>,
    cascade_split_distances: vec4<f32>,
    cascade_atlas_offsets: array<vec4<f32>, 4>,
    cascade_atlas_scale: vec4<f32>,
    time: f32,
    pbr_debug_mode: u32,
    texture_debug_stripes: u32,
    texture_debug_stripes_speed: f32,
    directional_light_direction: vec4<f32>,
    ibl_blend_factor: f32,
};

struct PackedSpriteData {
    position: vec4<f32>,
    rs_col0: vec2<f32>,
    rs_col1: vec2<f32>,
    uv_min: vec2<f32>,
    uv_max: vec2<f32>,
    color: vec4<f32>,
    texture_slot: u32,
    texture_slot2: u32,
    blend_factor: f32,
    _padding: f32,
};

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
    @location(1) @interpolate(flat) texture_slot: u32,
    @location(2) @interpolate(flat) texture_slot2: u32,
    @location(3) blend_factor: f32,
    @location(4) sprite_color: vec4<f32>,
};

@group(0) @binding(0)
var<uniform> uniforms: Uniforms;

@group(1) @binding(0)
var<storage, read> sprite_data: array<PackedSpriteData>;

@group(1) @binding(1)
var sprite_atlas: texture_2d<f32>;

@group(1) @binding(2)
var sprite_sampler: sampler;

@group(1) @binding(3)
var<uniform> sprite_atlas_info: vec4<f32>;

@group(1) @binding(4)
var<storage, read> visible_indices: array<u32>;

const QUAD_POSITIONS = array<vec2<f32>, 4>(
    vec2(-0.5, -0.5),
    vec2(0.5, -0.5),
    vec2(0.5, 0.5),
    vec2(-0.5, 0.5),
);

const QUAD_UVS = array<vec2<f32>, 4>(
    vec2(0.0, 1.0),
    vec2(1.0, 1.0),
    vec2(1.0, 0.0),
    vec2(0.0, 0.0),
);

@vertex
fn vs_main(
    @builtin(vertex_index) vertex_index: u32,
    @builtin(instance_index) instance_index: u32,
) -> VertexOutput {
    var out: VertexOutput;

    let sprite_index = visible_indices[instance_index];
    let s = sprite_data[sprite_index];

    let local = QUAD_POSITIONS[vertex_index];
    let tex_coords = QUAD_UVS[vertex_index];

    let world_xy = vec2<f32>(
        local.x * s.rs_col0.x + local.y * s.rs_col1.x + s.position.x,
        local.x * s.rs_col0.y + local.y * s.rs_col1.y + s.position.y,
    );
    let world_pos = vec4<f32>(world_xy, s.position.z, 1.0);

    let view_projection = uniforms.projection * uniforms.view;
    out.position = view_projection * world_pos;

    out.uv = mix(s.uv_min, s.uv_max, tex_coords);
    out.texture_slot = s.texture_slot;
    out.texture_slot2 = s.texture_slot2;
    out.blend_factor = s.blend_factor;
    out.sprite_color = s.color;

    return out;
}

fn sample_atlas_slot(slot: u32, uv: vec2<f32>) -> vec4<f32> {
    let slots_per_row = u32(sprite_atlas_info.x);
    let slot_uv_size = sprite_atlas_info.y;

    let row = slot / slots_per_row;
    let col = slot % slots_per_row;

    let atlas_uv = vec2<f32>(
        f32(col) * slot_uv_size + uv.x * slot_uv_size,
        f32(row) * slot_uv_size + uv.y * slot_uv_size,
    );

    return textureSampleLevel(sprite_atlas, sprite_sampler, atlas_uv, 0.0);
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    var tex_color = sample_atlas_slot(in.texture_slot, in.uv);

    if in.blend_factor > 0.0 {
        let tex_color2 = sample_atlas_slot(in.texture_slot2, in.uv);
        tex_color = mix(tex_color, tex_color2, in.blend_factor);
    }

    let final_color = tex_color * in.sprite_color;

    if final_color.a < 0.001 {
        discard;
    }

    return vec4<f32>(final_color.rgb * final_color.a, final_color.a);
}