#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); }