Forum

[Not A Tutorial] FPS-Independent Particles

Post tutorials on how to do certain tasks within game or engine code here.

Moderator: InsideQC Admins

[Not A Tutorial] FPS-Independent Particles

Postby mh » Tue Jun 05, 2012 1:37 am

These code snippets are intended for those who already know what they're doing, and will give you framerate-independent particles. No more craziness when running too fast or too slow. They're also suitable for moving particle position/velocity updates to the GPU, and can serve as a first step towards a more generic particle system that's not constrained to the builtin types.
Code: Select all
ptime = cl.time - p->spawntime;

neworg[0] = p->org[0] + (p->vel[0] + (p->dvel[0] * ptime)) * ptime;
neworg[1] = p->org[1] + (p->vel[1] + (p->dvel[1] * ptime)) * ptime;
neworg[2] = p->org[2] + (p->vel[2] + (p->dvel[2] * ptime) + (ptime * grav * p->grav)) * ptime;

Then neworg becomes the origin that is further modified to produce your particle. All modifications of p->org and p->vel get removed.

There are a few new things here.

p->spawntime is just the value of cl.time when the particle is spawned.
p->dvel is normally 0 but can have values depending on the emitter (explosions, generally) - pull these from the old type handling (which can otherwise be mostly removed).
grav is sv_gravity.value * 0.05
p->grav is 1 for particles that go up (rocket/grenade trails), -1 for particles that go down (most types) or 0 for particles that are unaffected by gravity.

Full drawing code (GL 3.x+ needed):
Code: Select all
extern   cvar_t   sv_gravity;

typedef struct partinst_s
{
   float org[3];

   union
   {
      unsigned color;
      byte rgba[4];
   };
} partinst_t;

typedef struct partvert_s
{
   float st[2];
} partvert_t;


#define MAX_PART_INST   16384

partinst_t partinsts[MAX_PART_INST];
partvert_t partverts[4] = {{-1, -1}, {1, -1}, {-1, 1}, {1, 1}};

void R_DrawParticles (void)
{
   particle_t *p, *kill;
   float grav = sv_gravity.value * 0.05;
   float ptime;
   int numinst = 0;

   // kill any particles at the head of the list
   for (;;)
   {
      kill = active_particles;

      if (kill && kill->die < cl.time)
      {
         active_particles = kill->next;
         kill->next = free_particles;
         free_particles = kill;
         continue;
      }

      break;
   }

   if (!active_particles) return;

   GL_BindProgram (gl_particleprog);

   glUniform3fv (u_partrorigin, 1, r_origin);
   glUniform3fv (u_partvpn, 1, vpn);
   glUniform3fv (u_partvright, 1, vright);
   glUniform3fv (u_partvup, 1, vup);

   glEnable (GL_BLEND);
   glDepthMask (GL_FALSE);

   glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, sizeof (partinst_t), partinsts[0].org);
   glVertexAttribPointer (1, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof (partinst_t), partinsts[0].rgba);
   glVertexAttribPointer (2, 2, GL_FLOAT, GL_FALSE, sizeof (partvert_t), partverts->st);

   glVertexAttribDivisor (0, 1);
   glVertexAttribDivisor (1, 1);

   for (numinst = 0, p = active_particles; p; p = p->next, numinst++)
   {
      // kill any particles in the middle of the list
      for (;;)
      {
         kill = p->next;

         if (kill && kill->die < cl.time)
         {
            p->next = kill->next;
            kill->next = free_particles;
            free_particles = kill;
            continue;
         }

         break;
      }

      if (numinst == MAX_PART_INST)
      {
         glDrawArraysInstanced (GL_TRIANGLE_STRIP, 0, 4, numinst);
         c_draw_calls++;
         numinst = 0;
      }

      ptime = cl.time - p->spawntime;

      partinsts[numinst].org[0] = p->org[0] + (p->vel[0] + (p->dvel[0] * ptime)) * ptime;
      partinsts[numinst].org[1] = p->org[1] + (p->vel[1] + (p->dvel[1] * ptime)) * ptime;
      partinsts[numinst].org[2] = p->org[2] + (p->vel[2] + (p->dvel[2] * ptime) + (ptime * grav * p->grav)) * ptime;
      partinsts[numinst].color = d_8to24table[(int) p->color];

      if (p->baseramp)
      {
         float ramptime = p->ramp + ptime * p->ramptime;
         int ramp = (int) ramptime;

         if (ramp > 10 || p->baseramp[ramp] == 0xff)
            p->die = -1;
         else p->color = p->baseramp[ramp];
      }
   }

   if (numinst)
   {
      glDrawArraysInstanced (GL_TRIANGLE_STRIP, 0, 4, numinst);
      c_draw_calls++;
   }

   glVertexAttribDivisor (0, 0);
   glVertexAttribDivisor (1, 0);

   glDepthMask (GL_TRUE);
   glDisable (GL_BLEND);
}

Shaders:
Code: Select all
uniform vec3 r_origin;
uniform vec3 vpn;
uniform vec3 vright;
uniform vec3 vup;

layout(location = 0) in vec4 position;
layout(location = 1) in vec4 color;
layout(location = 2) in vec4 texcoord;

void main ()
{
   float scale = (1.0 + dot (position.xyz - r_origin, vpn) * 0.004) * 0.666;

   gl_Position = gl_ModelViewProjectionMatrix * vec4 (position.xyz + (vright * scale * texcoord.t) + (vup * scale * texcoord.s), 1.0);
   gl_FrontColor = color;
   gl_TexCoord[0] = texcoord;
}


void main ()
{
   vec4 color = gl_Color;
   color.a = (1.0 - dot (gl_TexCoord[0].st, gl_TexCoord[0].st)) * 1.5;
   gl_FragColor = color * gl_Color.a;
}
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
User avatar
mh
 
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Re: [Not A Tutorial] FPS-Independent Particles

Postby Spike » Tue Jun 05, 2012 5:59 pm

/me learns how instancing works in gl. :s
Spike
 
Posts: 2892
Joined: Fri Nov 05, 2004 3:12 am
Location: UK


Return to Programming Tutorials

Who is online

Users browsing this forum: No registered users and 1 guest