[Not A Tutorial] FPS-Independent Particles

Post tutorials on how to do certain tasks within game or engine code here.
Post Reply
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

[Not A Tutorial] FPS-Independent Particles

Post by mh »

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
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Re: [Not A Tutorial] FPS-Independent Particles

Post by Spike »

/me learns how instancing works in gl. :s
Post Reply