pub const PARTICLE_INTEGRATE_GLSL: &str = r#"
#version 430 core
layout(local_size_x = 256) in;
struct Particle {
vec4 position; // xyz + lifetime
vec4 velocity; // xyz + age
vec4 color;
float size;
float mass;
uint flags;
uint attractor;
};
struct Attractor {
vec4 position; // xyz + strength
vec4 params; // type, falloff_start, falloff_end, rotation
vec4 color;
uint atype;
uint _pad[3];
};
layout(std430, binding = 0) buffer ParticleBuffer {
Particle particles[];
};
layout(std430, binding = 1) readonly buffer AttractorBuffer {
Attractor attractors[];
};
layout(std430, binding = 2) buffer DeadList {
uint dead_count;
uint dead_indices[];
};
layout(std140, binding = 0) uniform Params {
float dt;
float time;
vec3 gravity;
float drag;
uint num_particles;
uint num_attractors;
float emit_rate;
float _pad;
};
// Lorenz attractor vector field
vec3 lorenz(vec3 p, float sigma, float rho, float beta) {
return vec3(
sigma * (p.y - p.x),
p.x * (rho - p.z) - p.y,
p.x * p.y - beta * p.z
);
}
// Vortex force
vec3 vortex_force(vec3 particle_pos, vec3 center, float strength, float rotation) {
vec3 r = particle_pos - center;
float d = length(r) + 0.001;
vec3 tangent = cross(r, vec3(0.0, 1.0, 0.0)) / d;
return tangent * strength * rotation / (d * d + 1.0);
}
vec3 compute_attractor_force(Particle p, Attractor a) {
vec3 pos = p.position.xyz;
vec3 apos = a.position.xyz;
float strength = a.position.w;
float falloff_start = a.params.y;
float falloff_end = a.params.z;
vec3 delta = apos - pos;
float dist = length(delta) + 0.001;
// Falloff
float t = clamp((dist - falloff_start) / (falloff_end - falloff_start + 0.001), 0.0, 1.0);
float attenuation = 1.0 - t;
switch (a.atype) {
case 0: // Point attractor
return normalize(delta) * strength * attenuation / (dist * dist + 1.0);
case 1: // Vortex
return vortex_force(pos, apos, strength * attenuation, a.params.w);
case 2: // Lorenz field
return lorenz(pos * 0.1, 10.0, 28.0, 2.667) * strength * 0.01 * attenuation;
case 3: // Repulse
return -normalize(delta) * strength * attenuation / (dist * dist + 0.5);
default:
return vec3(0.0);
}
}
void main() {
uint idx = gl_GlobalInvocationID.x;
if (idx >= num_particles) return;
Particle p = particles[idx];
if ((p.flags & 1u) == 0u) return; // Skip dead particles
// Accumulate forces
vec3 force = gravity * p.mass;
for (uint i = 0; i < num_attractors; i++) {
force += compute_attractor_force(p, attractors[i]);
}
// Drag
force -= p.velocity.xyz * drag;
// Semi-implicit Euler integration
vec3 new_vel = p.velocity.xyz + (force / p.mass) * dt;
vec3 new_pos = p.position.xyz + new_vel * dt;
// Age
float new_age = p.velocity.w + dt;
float lifetime = p.position.w;
// Kill if expired
if (new_age >= lifetime) {
p.flags &= ~1u; // clear alive bit
uint dead_idx = atomicAdd(dead_count, 1u);
dead_indices[dead_idx] = idx;
} else {
p.position.xyz = new_pos;
p.velocity.xyz = new_vel;
p.velocity.w = new_age;
}
particles[idx] = p;
}
"#;Expand description
Particle integration compute shader (GLSL 4.30+).