ADD: Particle system
This commit is contained in:
26
shaders/particles.frag
Normal file
26
shaders/particles.frag
Normal file
@@ -0,0 +1,26 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec4 v_color;
|
||||
layout(location = 1) in vec2 v_uv;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Soft circular sprite.
|
||||
vec2 p = v_uv * 2.0 - 1.0;
|
||||
float r = length(p);
|
||||
float mask = smoothstep(1.0, 0.0, r);
|
||||
|
||||
vec4 c = v_color;
|
||||
c.rgb *= mask;
|
||||
c.a *= mask;
|
||||
|
||||
if (c.a <= 0.001)
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
outColor = c;
|
||||
}
|
||||
|
||||
66
shaders/particles.vert
Normal file
66
shaders/particles.vert
Normal file
@@ -0,0 +1,66 @@
|
||||
#version 450
|
||||
|
||||
layout(set = 0, binding = 0) uniform SceneData
|
||||
{
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
mat4 viewproj;
|
||||
} sceneData;
|
||||
|
||||
struct Particle
|
||||
{
|
||||
vec4 pos_age;
|
||||
vec4 vel_life;
|
||||
vec4 color;
|
||||
vec4 misc;
|
||||
};
|
||||
|
||||
layout(std430, set = 1, binding = 0) readonly buffer ParticlePool
|
||||
{
|
||||
Particle particles[];
|
||||
} pool;
|
||||
|
||||
layout(location = 0) out vec4 v_color;
|
||||
layout(location = 1) out vec2 v_uv;
|
||||
|
||||
vec2 quad_corner(uint vidx)
|
||||
{
|
||||
// Two triangles (6 verts) in a unit quad centered at origin.
|
||||
const vec2 corners[6] = vec2[6](
|
||||
vec2(-0.5, -0.5),
|
||||
vec2( 0.5, -0.5),
|
||||
vec2( 0.5, 0.5),
|
||||
vec2(-0.5, -0.5),
|
||||
vec2( 0.5, 0.5),
|
||||
vec2(-0.5, 0.5)
|
||||
);
|
||||
return corners[vidx % 6u];
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint particle_index = gl_InstanceIndex;
|
||||
Particle p = pool.particles[particle_index];
|
||||
|
||||
float life = max(p.vel_life.w, 1e-6);
|
||||
float remaining = clamp(p.pos_age.w, 0.0, life);
|
||||
float t = remaining / life; // remaining fraction
|
||||
|
||||
float fade_out = smoothstep(0.0, 0.15, t);
|
||||
float fade_in = smoothstep(0.0, 0.05, 1.0 - t);
|
||||
float fade = fade_in * fade_out;
|
||||
|
||||
vec2 corner = quad_corner(uint(gl_VertexIndex));
|
||||
v_uv = corner + vec2(0.5);
|
||||
|
||||
// Camera right/up in world-local space from view matrix rows.
|
||||
vec3 cam_right = vec3(sceneData.view[0][0], sceneData.view[1][0], sceneData.view[2][0]);
|
||||
vec3 cam_up = vec3(sceneData.view[0][1], sceneData.view[1][1], sceneData.view[2][1]);
|
||||
|
||||
float size = max(p.misc.x, 0.0);
|
||||
vec3 pos = p.pos_age.xyz + (cam_right * corner.x + cam_up * corner.y) * size;
|
||||
|
||||
v_color = vec4(p.color.rgb * fade, p.color.a * fade);
|
||||
gl_Position = sceneData.viewproj * vec4(pos, 1.0);
|
||||
}
|
||||
|
||||
158
shaders/particles_update.comp
Normal file
158
shaders/particles_update.comp
Normal file
@@ -0,0 +1,158 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user