Skip to main content

KERNEL_PARTICLE_EMIT

Constant KERNEL_PARTICLE_EMIT 

Source
pub const KERNEL_PARTICLE_EMIT: &str = r#"
// Particle emission kernel
// Uses an atomic counter to allocate slots in the particle buffer.

layout(local_size_x = 64) in;

struct Particle {
    vec4 pos_age;
    vec4 vel_life;
};

layout(std430, binding = 1) writeonly buffer ParticlesOut {
    Particle particles[];
};

layout(binding = 0, offset = 0) uniform atomic_uint u_alive_count;

uniform uint u_emit_count;
uniform uint u_max_particles;
uniform vec3 u_emitter_pos;
uniform float u_emitter_radius;
uniform float u_speed_min;
uniform float u_speed_max;
uniform vec3 u_direction;
uniform float u_spread;
uniform float u_life_min;
uniform float u_life_max;
uniform float u_time;
uniform uint u_seed;
uniform vec4 u_color_start;
uniform vec4 u_color_end;

// PCG random number generator
uint pcg(uint state) {
    uint s = state * 747796405u + 2891336453u;
    uint word = ((s >> ((s >> 28u) + 4u)) ^ s) * 277803737u;
    return (word >> 22u) ^ word;
}

float rand01(inout uint seed) {
    seed = pcg(seed);
    return float(seed) / 4294967295.0;
}

vec3 random_direction(inout uint seed, vec3 dir, float spread) {
    float phi = rand01(seed) * 6.283185307;
    float cos_theta = 1.0 - rand01(seed) * spread;
    float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta));

    vec3 random_dir = vec3(
        sin_theta * cos(phi),
        sin_theta * sin(phi),
        cos_theta
    );

    // Rotate random_dir to align with dir
    vec3 up = abs(dir.y) < 0.999 ? vec3(0, 1, 0) : vec3(1, 0, 0);
    vec3 right = normalize(cross(up, dir));
    up = cross(dir, right);

    return right * random_dir.x + up * random_dir.y + dir * random_dir.z;
}

vec3 random_sphere(inout uint seed, float radius) {
    float phi = rand01(seed) * 6.283185307;
    float cos_theta = rand01(seed) * 2.0 - 1.0;
    float sin_theta = sqrt(1.0 - cos_theta * cos_theta);
    float r = pow(rand01(seed), 1.0 / 3.0) * radius;
    return r * vec3(sin_theta * cos(phi), sin_theta * sin(phi), cos_theta);
}

void main() {
    uint idx = gl_GlobalInvocationID.x;
    if (idx >= u_emit_count) return;

    // Allocate a slot
    uint slot = atomicCounterIncrement(u_alive_count);
    if (slot >= u_max_particles) return;

    // Seed RNG
    uint seed = u_seed + idx * 1973u + uint(u_time * 1000.0) * 7919u;

    // Random position within emitter sphere
    vec3 pos = u_emitter_pos + random_sphere(seed, u_emitter_radius);

    // Random direction with spread
    vec3 dir = random_direction(seed, normalize(u_direction), u_spread);

    // Random speed
    float speed = mix(u_speed_min, u_speed_max, rand01(seed));

    // Random lifetime
    float life = mix(u_life_min, u_life_max, rand01(seed));

    particles[slot].pos_age = vec4(pos, 0.0);
    particles[slot].vel_life = vec4(dir * speed, life);
}
"#;
Expand description

Particle emission kernel: spawn new particles using atomic counter.