Index: shaders/explosion.frag
===================================================================
--- shaders/explosion.frag	(revision aa7707debaf679f9726a89d6342e9788e9f385ed)
+++ shaders/explosion.frag	(revision aa7707debaf679f9726a89d6342e9788e9f385ed)
@@ -0,0 +1,10 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in float opacity;
+
+layout(location = 0) out vec4 frag_color;
+
+void main() {
+   frag_color = vec4(1.0, opacity * opacity, 0.0, opacity);
+}
Index: shaders/explosion.vert
===================================================================
--- shaders/explosion.vert	(revision aa7707debaf679f9726a89d6342e9788e9f385ed)
+++ shaders/explosion.vert	(revision aa7707debaf679f9726a89d6342e9788e9f385ed)
@@ -0,0 +1,61 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+struct Object {
+   mat4 model;
+   float explosion_start_time;
+   float explosion_duration;
+   bool deleted;
+};
+
+layout (binding = 0) uniform UniformBufferObject {
+   mat4 view;
+   mat4 proj;
+   float cur_time;
+} ubo;
+
+layout(binding = 1) readonly buffer StorageBufferObject {
+   Object objects[];
+} sbo;
+
+layout(location = 0) in vec3 particle_start_velocity;
+layout(location = 1) in float particle_start_time;
+layout(location = 2) in uint obj_index;
+
+layout(location = 0) out float opacity;
+
+void main() {
+   mat4 model = sbo.objects[obj_index].model;
+   float explosion_start_time = sbo.objects[obj_index].explosion_start_time;
+   float explosion_duration = sbo.objects[obj_index].explosion_duration;
+   bool deleted = sbo.objects[obj_index].deleted;
+
+   float t = ubo.cur_time - explosion_start_time - particle_start_time;
+
+   if (t < 0.0) {
+      opacity = 0.0;
+   } else {
+      // Need to find out the last time this particle was at the origin
+      // If that is greater than the duration, hide the particle
+      float cur = floor(t / explosion_duration);
+      float end = floor((explosion_duration - particle_start_time) / explosion_duration);
+      if (cur > end) {
+         opacity = 0.0;
+      } else {
+         opacity = 1.0 - (t / explosion_duration);
+      }
+   }
+
+   if (deleted) {
+      gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
+   } else {
+      //  this is the center of the explosion
+      vec3 p = vec3(0.0, 0.0, 0.0);
+
+      // allow time to loop around so particle emitter keeps going
+      p += normalize(particle_start_velocity) * mod(t, explosion_duration) / explosion_duration * 0.3;
+
+      gl_Position = ubo.proj * ubo.view * model * vec4(p, 1.0);
+   }
+   gl_PointSize = 10.0; // size in pixels
+}
