Files
QuaternionEngine/shaders/particles_update.comp

159 lines
4.1 KiB
Plaintext

#version 450
// v1 particle update: one SSBO, per-system dispatch.
// Particles store "remaining life" in pos_age.w and total lifetime in vel_life.w.
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
struct Particle
{
vec4 pos_age; // xyz = local position, w = remaining life (seconds)
vec4 vel_life; // xyz = local velocity, w = lifetime (seconds)
vec4 color; // rgba
vec4 misc; // x=size, y=seed, z=unused, w=flags
};
layout(std430, set = 0, binding = 0) buffer ParticlePool
{
Particle particles[];
} pool;
layout(push_constant) uniform Push
{
uvec4 header; // x=base, y=count, z=reset
vec4 sim; // x=dt, y=time, z=drag, w=gravity (m/s^2, pulls down -Y)
vec4 origin_delta; // xyz origin delta (local)
vec4 emitter_pos_radius;// xyz emitter pos (local), w=spawn radius
vec4 emitter_dir_cone; // xyz emitter dir (local), w=cone angle radians (<=0 => sphere)
vec4 ranges; // x=minSpeed, y=maxSpeed, z=minLife, w=maxLife
vec4 size_range; // x=minSize, y=maxSize
vec4 color; // rgba
} pc;
uint hash_u32(uint x)
{
x ^= x >> 16;
x *= 0x7feb352du;
x ^= x >> 15;
x *= 0x846ca68bu;
x ^= x >> 16;
return x;
}
float rand01(inout uint state)
{
state = hash_u32(state);
// 0..1 (exclusive 1)
return float(state) * (1.0 / 4294967296.0);
}
vec3 random_unit_vector(inout uint state)
{
float z = rand01(state) * 2.0 - 1.0;
float a = rand01(state) * 6.28318530718;
float r = sqrt(max(0.0, 1.0 - z * z));
return vec3(r * cos(a), z, r * sin(a));
}
vec3 random_in_sphere(inout uint state)
{
vec3 dir = random_unit_vector(state);
float u = rand01(state);
float radius = pow(u, 1.0 / 3.0);
return dir * radius;
}
vec3 random_in_cone(inout uint state, vec3 axis, float cone_angle)
{
axis = normalize(axis);
float cos_max = clamp(cos(cone_angle), -1.0, 1.0);
float cos_t = mix(cos_max, 1.0, rand01(state));
float sin_t = sqrt(max(0.0, 1.0 - cos_t * cos_t));
float phi = rand01(state) * 6.28318530718;
vec3 local_dir = vec3(cos(phi) * sin_t, sin(phi) * sin_t, cos_t);
vec3 up = (abs(axis.y) < 0.999) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
vec3 u = normalize(cross(up, axis));
vec3 v = cross(axis, u);
return u * local_dir.x + v * local_dir.y + axis * local_dir.z;
}
void respawn(uint idx)
{
uint seed = idx ^ (hash_u32(floatBitsToUint(pc.sim.y)) * 1664525u) ^ 1013904223u;
float life = mix(pc.ranges.z, pc.ranges.w, rand01(seed));
life = max(life, 0.01);
float speed = mix(pc.ranges.x, pc.ranges.y, rand01(seed));
speed = max(speed, 0.0);
float size = mix(pc.size_range.x, pc.size_range.y, rand01(seed));
size = max(size, 0.001);
vec3 spawn_pos = pc.emitter_pos_radius.xyz;
float radius = max(pc.emitter_pos_radius.w, 0.0);
if (radius > 0.0)
{
spawn_pos += random_in_sphere(seed) * radius;
}
vec3 dir;
float cone = pc.emitter_dir_cone.w;
if (cone > 0.0)
{
dir = random_in_cone(seed, pc.emitter_dir_cone.xyz, cone);
}
else
{
dir = random_unit_vector(seed);
}
Particle p;
p.pos_age = vec4(spawn_pos, life);
p.vel_life = vec4(dir * speed, life);
p.color = pc.color;
p.misc = vec4(size, rand01(seed), 0.0, 0.0);
pool.particles[idx] = p;
}
void main()
{
uint i = gl_GlobalInvocationID.x;
if (i >= pc.header.y) return;
uint idx = pc.header.x + i;
Particle p = pool.particles[idx];
// Keep particles stable under floating-origin recenters.
p.pos_age.xyz -= pc.origin_delta.xyz;
bool reset = (pc.header.z != 0u);
bool dead = (p.pos_age.w <= 0.0) || (p.vel_life.w <= 0.0);
if (reset || dead)
{
respawn(idx);
return;
}
float dt = pc.sim.x;
float drag = max(pc.sim.z, 0.0);
float gravity = pc.sim.w;
vec3 vel = p.vel_life.xyz;
vel += vec3(0.0, -gravity, 0.0) * dt;
vel *= exp(-drag * dt);
p.pos_age.xyz += vel * dt;
p.vel_life.xyz = vel;
p.pos_age.w -= dt;
pool.particles[idx] = p;
}