pub const PARTICLE_VERT_SRC: &str = r#"
#version 430 core
// Per-vertex (unit quad)
layout(location = 0) in vec2 v_pos; // [-0.5, 0.5]
layout(location = 1) in vec2 v_uv; // [0, 1]
// Particle SSBO (read-only in vertex shader)
struct Particle {
vec3 position;
float _pad0;
vec3 velocity;
float _pad1;
vec4 color;
float life;
float max_life;
float size;
uint engine_type;
float seed;
uint flags;
float _reserved0;
float _reserved1;
};
layout(std430, binding = 0) readonly buffer ParticleBuffer {
Particle particles[];
};
uniform mat4 u_view_proj;
uniform vec3 u_camera_right;
uniform vec3 u_camera_up;
uniform float u_time;
out vec2 f_uv;
out vec4 f_color;
out float f_age_frac;
out float f_emission;
void main() {
Particle p = particles[gl_InstanceID];
// Skip dead particles (alpha will be zero, but also move off-screen).
if (p.life <= 0.0) {
gl_Position = vec4(0.0, 0.0, -999.0, 1.0);
f_color = vec4(0.0);
f_uv = vec2(0.0);
f_age_frac = 1.0;
f_emission = 0.0;
return;
}
float age_frac = 1.0 - clamp(p.life / p.max_life, 0.0, 1.0);
// Size modulation over lifetime: starts at full, shrinks in last 20%.
float size_mod = 1.0;
if (age_frac > 0.8) {
size_mod = 1.0 - (age_frac - 0.8) * 5.0;
}
float particle_size = p.size * size_mod;
// Billboard: orient quad to face camera.
vec3 world_pos = p.position
+ u_camera_right * v_pos.x * particle_size
+ u_camera_up * v_pos.y * particle_size;
gl_Position = u_view_proj * vec4(world_pos, 1.0);
gl_Position.y = -gl_Position.y; // FBO Y inversion
f_uv = v_uv;
f_color = p.color;
f_age_frac = age_frac;
// Emission based on engine type: some engines glow more.
float base_emission = 0.0;
if (p.engine_type == 1u) base_emission = 0.5; // Lorenz
if (p.engine_type == 4u) base_emission = 0.4; // Rossler
if (p.engine_type == 7u) base_emission = 0.3; // Halvorsen
f_emission = base_emission * (1.0 - age_frac);
}
"#;Expand description
Vertex shader that reads particle data from SSBO and renders instanced quads.
Each particle is a camera-facing (billboard) quad. The vertex shader reads position/size/color from the SSBO via gl_InstanceID.