nightshade 0.13.0

A cross-platform data-oriented game engine.
Documentation
struct Particle {
    position: vec4<f32>,
    velocity: vec4<f32>,
    color: vec4<f32>,
    size_lifetime: vec4<f32>,
    emitter_data: vec4<f32>,
    physics: vec4<f32>,
    color_start: vec4<f32>,
    color_end: vec4<f32>,
}

struct CameraUniforms {
    view: mat4x4<f32>,
    projection: mat4x4<f32>,
    view_projection: mat4x4<f32>,
    camera_position: vec4<f32>,
    camera_right: vec4<f32>,
    camera_up: vec4<f32>,
}

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
    @location(1) color: vec4<f32>,
    @location(2) emissive: f32,
    @location(3) world_position: vec3<f32>,
    @location(4) @interpolate(flat) emitter_type: u32,
    @location(5) @interpolate(flat) texture_index: u32,
}

@group(0) @binding(0)
var<uniform> camera: CameraUniforms;

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

@group(0) @binding(2)
var<storage, read> alive_indices: array<u32>;

@group(1) @binding(0)
var particle_texture_array: texture_2d_array<f32>;

@group(1) @binding(1)
var particle_sampler: sampler;

const QUAD_VERTICES: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
    vec2<f32>(-0.5, -0.5),
    vec2<f32>(0.5, -0.5),
    vec2<f32>(0.5, 0.5),
    vec2<f32>(-0.5, -0.5),
    vec2<f32>(0.5, 0.5),
    vec2<f32>(-0.5, 0.5),
);

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

@vertex
fn vs_main(
    @builtin(vertex_index) vertex_index: u32,
    @builtin(instance_index) instance_index: u32,
) -> VertexOutput {
    let particle_index = alive_indices[instance_index];
    let particle = particles[particle_index];

    let quad_vertex = QUAD_VERTICES[vertex_index];
    let quad_uv = QUAD_UVS[vertex_index];

    let size = particle.emitter_data.y;

    let right = camera.camera_right.xyz;
    let up = camera.camera_up.xyz;

    let world_offset = right * quad_vertex.x * size + up * quad_vertex.y * size;
    let world_position = particle.position.xyz + world_offset;

    let clip_position = camera.view_projection * vec4<f32>(world_position, 1.0);

    let emissive_strength = particle.emitter_data.z;
    let emitter_type = u32(particle.emitter_data.w);
    let tex_index = u32(particle.emitter_data.x);

    var output: VertexOutput;
    output.position = clip_position;
    output.uv = quad_uv;
    output.color = particle.color;
    output.emissive = emissive_strength;
    output.world_position = world_position;
    output.emitter_type = emitter_type;
    output.texture_index = tex_index;

    return output;
}

fn soft_circle(uv: vec2<f32>, radius: f32, softness: f32) -> f32 {
    let center = vec2<f32>(0.5, 0.5);
    let dist = length(uv - center);
    return 1.0 - smoothstep(radius - softness, radius, dist);
}

fn glow_circle(uv: vec2<f32>) -> f32 {
    let center = vec2<f32>(0.5, 0.5);
    let dist = length(uv - center);

    let core = exp(-dist * dist * 80.0);
    let inner_glow = exp(-dist * dist * 20.0) * 0.7;
    let mid_glow = exp(-dist * dist * 8.0) * 0.4;
    let outer_glow = exp(-dist * dist * 3.0) * 0.2;

    return core + inner_glow + mid_glow + outer_glow;
}

fn firework_glow(uv: vec2<f32>) -> f32 {
    let center = vec2<f32>(0.5, 0.5);
    let dist = length(uv - center);

    let core = exp(-dist * dist * 120.0);
    let bright_core = exp(-dist * dist * 40.0) * 0.9;
    let inner_glow = exp(-dist * dist * 15.0) * 0.6;
    let mid_glow = exp(-dist * dist * 6.0) * 0.35;
    let outer_glow = exp(-dist * dist * 2.5) * 0.15;

    return core + bright_core + inner_glow + mid_glow + outer_glow;
}

fn star_shape(uv: vec2<f32>, points: f32, sharpness: f32) -> f32 {
    let center = vec2<f32>(0.5, 0.5);
    let p = uv - center;
    let dist = length(p);
    let angle = atan2(p.y, p.x);

    let star = cos(angle * points) * 0.5 + 0.5;
    let star_factor = mix(1.0, star, sharpness);

    let core = exp(-dist * dist * 100.0);
    let glow = exp(-dist * dist * 20.0 / star_factor) * 0.8;
    let outer = exp(-dist * dist * 5.0) * 0.3;

    return core + glow + outer;
}

fn fire_shape(uv: vec2<f32>) -> f32 {
    let center = vec2<f32>(0.5, 0.5);
    var adjusted_uv = uv - center;
    adjusted_uv.y *= 0.65;
    let dist = length(adjusted_uv);

    let core = exp(-dist * dist * 60.0);
    let flame = exp(-dist * dist * 15.0) * 0.75;
    let outer = exp(-dist * dist * 5.0) * 0.35;

    return core + flame + outer;
}

fn smoke_shape(uv: vec2<f32>) -> f32 {
    let center = vec2<f32>(0.5, 0.5);
    let dist = length(uv - center);
    return exp(-dist * dist * 4.0) * 0.85;
}

fn spark_shape(uv: vec2<f32>) -> f32 {
    let center = vec2<f32>(0.5, 0.5);
    let dist = length(uv - center);

    let core = exp(-dist * dist * 200.0);
    let bright = exp(-dist * dist * 60.0) * 0.9;
    let glow = exp(-dist * dist * 15.0) * 0.5;

    return core + bright + glow;
}

fn get_shape_for_type(uv: vec2<f32>, emitter_type: u32) -> f32 {
    switch emitter_type {
        case 0u: { return firework_glow(uv); }
        case 1u: { return fire_shape(uv); }
        case 2u: { return smoke_shape(uv); }
        case 3u: { return spark_shape(uv); }
        case 4u: { return firework_glow(uv); }
        default: { return firework_glow(uv); }
    }
}

fn sample_particle_texture(uv: vec2<f32>, tex_index: u32) -> vec4<f32> {
    let layer = i32(tex_index - 1u);
    return textureSampleLevel(particle_texture_array, particle_sampler, uv, layer, 0.0);
}

@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
    if (input.texture_index > 0u) {
        let tex_color = sample_particle_texture(input.uv, input.texture_index);
        if (tex_color.a < 0.01) {
            discard;
        }
        var color = input.color * tex_color;
        let emissive_boost = max(input.emissive - 1.0, 0.0);
        if (emissive_boost > 0.0) {
            color = vec4<f32>(color.rgb * (1.0 + emissive_boost * 0.8), color.a);
        }
        return color;
    }

    let shape = get_shape_for_type(input.uv, input.emitter_type);

    if (shape < 0.005) {
        discard;
    }

    var color = input.color;
    color.a *= shape;

    let emissive_boost = max(input.emissive - 1.0, 0.0);
    if (emissive_boost > 0.0) {
        color = vec4<f32>(color.rgb * (1.0 + emissive_boost * 0.8), color.a);
    }

    return color;
}

@fragment
fn fs_main_additive(input: VertexOutput) -> @location(0) vec4<f32> {
    if (input.texture_index > 0u) {
        let tex_color = sample_particle_texture(input.uv, input.texture_index);
        if (tex_color.a < 0.01) {
            discard;
        }
        var color = input.color * tex_color;
        let intensity = color.a;
        let emissive_boost = max(input.emissive - 1.0, 0.0);
        let brightness = 1.0 + emissive_boost * 1.2;
        let hdr_color = color.rgb * intensity * brightness;
        let boosted = hdr_color + hdr_color * hdr_color * 0.3;
        return vec4<f32>(boosted, intensity);
    }

    let shape = get_shape_for_type(input.uv, input.emitter_type);

    if (shape < 0.005) {
        discard;
    }

    var color = input.color;
    let intensity = shape * color.a;

    if (input.emitter_type == 2u) {
        let smoke_color = color.rgb * intensity * 1.5;
        return vec4<f32>(smoke_color, intensity * 0.8);
    }

    let emissive_boost = max(input.emissive - 1.0, 0.0);
    let brightness = 1.0 + emissive_boost * 1.2;

    let hdr_color = color.rgb * intensity * brightness;
    let boosted = hdr_color + hdr_color * hdr_color * 0.3;

    return vec4<f32>(boosted, intensity);
}

@fragment
fn fs_main_fire(input: VertexOutput) -> @location(0) vec4<f32> {
    if (input.texture_index > 0u) {
        let tex_color = sample_particle_texture(input.uv, input.texture_index);
        if (tex_color.a < 0.01) {
            discard;
        }
        var color = input.color * tex_color;
        let intensity = color.a;
        let emissive_boost = max(input.emissive - 1.0, 0.0);
        let brightness = 1.0 + emissive_boost * 1.5;
        let hdr_color = color.rgb * intensity * brightness;
        let boosted = hdr_color + hdr_color * hdr_color * 0.5;
        return vec4<f32>(boosted, intensity);
    }

    let shape = fire_shape(input.uv);

    if (shape < 0.005) {
        discard;
    }

    var color = input.color;
    let intensity = shape * color.a;

    let emissive_boost = max(input.emissive - 1.0, 0.0);
    let brightness = 1.0 + emissive_boost * 1.5;

    let hdr_color = color.rgb * intensity * brightness;
    let boosted = hdr_color + hdr_color * hdr_color * 0.5;

    return vec4<f32>(boosted, intensity);
}

@fragment
fn fs_main_smoke(input: VertexOutput) -> @location(0) vec4<f32> {
    if (input.texture_index > 0u) {
        let tex_color = sample_particle_texture(input.uv, input.texture_index);
        if (tex_color.a < 0.01) {
            discard;
        }
        var color = input.color * tex_color;
        color.a *= 0.7;
        return color;
    }

    let shape = smoke_shape(input.uv);

    if (shape < 0.005) {
        discard;
    }

    var color = input.color;
    color.a *= shape * 0.7;

    return color;
}

@fragment
fn fs_main_spark(input: VertexOutput) -> @location(0) vec4<f32> {
    if (input.texture_index > 0u) {
        let tex_color = sample_particle_texture(input.uv, input.texture_index);
        if (tex_color.a < 0.01) {
            discard;
        }
        var color = input.color * tex_color;
        let intensity = color.a;
        let emissive_boost = max(input.emissive - 1.0, 0.0);
        let brightness = 1.0 + emissive_boost * 2.0;
        let hdr_color = color.rgb * intensity * brightness;
        let boosted = hdr_color + hdr_color * hdr_color * 0.8;
        return vec4<f32>(boosted, intensity);
    }

    let shape = spark_shape(input.uv);

    if (shape < 0.005) {
        discard;
    }

    var color = input.color;
    let intensity = shape * color.a;

    let emissive_boost = max(input.emissive - 1.0, 0.0);
    let brightness = 1.0 + emissive_boost * 2.0;

    let hdr_color = color.rgb * intensity * brightness;
    let boosted = hdr_color + hdr_color * hdr_color * 0.8;

    return vec4<f32>(boosted, intensity);
}