Distance based Particle Reduction
by Davis Ray Sickmon, Jr · 03/01/2003 (4:50 pm) · 11 comments
For Trajectory Zone, we use particles for just about everything. Heck, I keep expecting to start making the terrain out of particles one o' these days ;-)
One of the major disadvantages I ran into when it comes to using particles so heavily is Torque really reacts badly to having too many particles up close and personal. That's a major issue, since the goal in Trajectory Zone is to bomb the heck outta your opponent with really big explosives!
I submitted a client-side reduction code earlier, but, that wasn't enough. Talking with someone over at the TEP forums, we were discussing strategies for particle reduction (he's planning on replacing the entire particle engine). I realized a bit o' the strategy we discussed would work for the existing engine.
--- updated: added "$pref"s to make it client side tweakable, and added performance enhancement, thanks to two posts below :-)
open /fps/client/prefs.cs, and add the following 4 lines:
Those are the LoD ranges - anything that falls below the first number means no particles at this distance, minimal between LoD1 and LoD2, etc.
Here's what it does: 1) It give particles one more entry (all work is in particleEngine.cc):
2) When a particle is created, it is assigned a random number from 1 - 4. (in ParticleEmitter::addParticle)
3) Then, in ParticleEmitter::renderObject, we check for the distance from the camera, and start reducing appropriate particles:
Now, one problem with this - obviously we're performing more operations per loop now. It doesn't seem for affect things too much, but, YMMV. If someone sees how to speed this up a bit more (as mentioned in another thread, I am not an Uber-hacker. I just get by ;-) post it :-) (Ask, and ye shall recieve - thanks guys! Now uses considerably less operations per loop :-)
Right now, it's basically got 5 levels of detail, ranging from viewing all (anything greater than 1024) to none (at less than 64). Depending on how many particles are on-screen you may or may not notice the 'popping' of particles as you cross the boundries.
I assigned random numbers for a reason - I figured out that any incremental way of doing it (ie, just i++ from 1 to 4) can produce some really funky results that look bad - the more particles there are, the less likely it is to happen. The randomness also gives a little more character to stuff that falls in the range of the LoD system - not all explosions look identical, etc.
Hope this turns out to be of help for someone else :-)
(PS: if someone happens to know an easy way to snag the profile information, I'll add a reduction based off of fps to this, and see how much impact it has, and throw a $pref in there so clients can control it.)
One of the major disadvantages I ran into when it comes to using particles so heavily is Torque really reacts badly to having too many particles up close and personal. That's a major issue, since the goal in Trajectory Zone is to bomb the heck outta your opponent with really big explosives!
I submitted a client-side reduction code earlier, but, that wasn't enough. Talking with someone over at the TEP forums, we were discussing strategies for particle reduction (he's planning on replacing the entire particle engine). I realized a bit o' the strategy we discussed would work for the existing engine.
--- updated: added "$pref"s to make it client side tweakable, and added performance enhancement, thanks to two posts below :-)
open /fps/client/prefs.cs, and add the following 4 lines:
$pref::particles::LoD1 = 64; $pref::particles::LoD2 = 128; $pref::particles::LoD3 = 256; $pref::particles::LoD4 = 1024;
Those are the LoD ranges - anything that falls below the first number means no particles at this distance, minimal between LoD1 and LoD2, etc.
Here's what it does: 1) It give particles one more entry (all work is in particleEngine.cc):
struct Particle
{
Point3F pos; // current instantaneous position
Point3F vel; // " " velocity
Point3F acc; // Constant acceleration
Point3F orientDir; // direction particle should go if using oriented particles
[b]S32 LoD; // used for LoD particle reduction for extreme closeup. [/b](When you see lines in bold, that's the part ya' need to add, of course).2) When a particle is created, it is assigned a random number from 1 - 4. (in ParticleEmitter::addParticle)
F32 initialVel = mDataBlock->ejectionVelocity; initialVel += (mDataBlock->velocityVariance * 2.0f * sgRandom.randF()) - mDataBlock->velocityVariance; [b]pNew->LoD = (S32)(sgRandom.randF(1, 4));[/b] pNew->pos = pos + (ejectionAxis * mDataBlock->ejectionOffset); pNew->vel = ejectionAxis * initialVel; pNew->orientDir = ejectionAxis;
3) Then, in ParticleEmitter::renderObject, we check for the distance from the camera, and start reducing appropriate particles:
[b] bool showParticle;
S32 levelLoD1 = Con::getIntVariable("$pref::particles::LoD1", 32000);
S32 levelLoD2 = Con::getIntVariable("$pref::particles::LoD2", 32000);
S32 levelLoD3 = Con::getIntVariable("$pref::particles::LoD3", 32000);
S32 levelLoD4 = Con::getIntVariable("$pref::particles::LoD4", 32000);[/b]
Point3F cameraPos = state->getCameraPosition();[/b]
for (U32 i = 0; i < orderedVector.size(); i++) {
Particle* particle = orderedVector[i].p;
[b]S32 distFromCam = mPow(particle->pos.x - cameraPos.x,2) + mPow(particle->pos.y - cameraPos.y,2) + mPow(particle->pos.z - cameraPos.z,2);
// Point3F distFromCam = particle->pos - cameraPos;
showParticle = true;
if(distFromCam < levelLoD4 && particle->LoD > 1)
showParticle = false;
else if(distFromCam < levelLoD3 && particle->LoD > 2)
showParticle = false;
else if(distFromCam < levelLoD2 && particle->LoD > 3)
showParticle = false;
else if(distFromCam < levelLoD1)
showParticle = false;
if(showParticle)
{[/b]
if( particle->dataBlock->animateTexture )
{
U32 texNum = particle->currentAge * (1.0/1000.0) * particle->dataBlock->framesPerSec;
texNum %= particle->dataBlock->numFrames;
glBindTexture(GL_TEXTURE_2D, particle->dataBlock->textureList[texNum].getGLName());
}
else
{
glBindTexture(GL_TEXTURE_2D, particle->dataBlock->textureList[0].getGLName());
}
if( mDataBlock->orientParticles )
{
renderOrientedParticle( *particle, state->getCameraPosition() );
}
else
{
renderBillboardParticle( *particle, basePoints, camView, spinFactor );
}
[b] }[/b]
}Now, one problem with this - obviously we're performing more operations per loop now. It doesn't seem for affect things too much, but, YMMV. If someone sees how to speed this up a bit more (as mentioned in another thread, I am not an Uber-hacker. I just get by ;-) post it :-) (Ask, and ye shall recieve - thanks guys! Now uses considerably less operations per loop :-)
Right now, it's basically got 5 levels of detail, ranging from viewing all (anything greater than 1024) to none (at less than 64). Depending on how many particles are on-screen you may or may not notice the 'popping' of particles as you cross the boundries.
I assigned random numbers for a reason - I figured out that any incremental way of doing it (ie, just i++ from 1 to 4) can produce some really funky results that look bad - the more particles there are, the less likely it is to happen. The randomness also gives a little more character to stuff that falls in the range of the LoD system - not all explosions look identical, etc.
Hope this turns out to be of help for someone else :-)
(PS: if someone happens to know an easy way to snag the profile information, I'll add a reduction based off of fps to this, and see how much impact it has, and throw a $pref in there so clients can control it.)
#2
You would also want to reverse the order of the ifs so that it checked the lowest distance first:
if(distFromCam < 64)
showParticle = false;
else if(distFromCam < 256 && particle->LoD > 3)
showParticle = false;
else if(distFromCam < 512 && particle->LoD > 2)
showParticle = false;
else if(distFromCam < 1024 && particle->LoD > 1)
showParticle = false;
03/01/2003 (5:07 pm)
One way to lower the number of operations there would be to use else if.You would also want to reverse the order of the ifs so that it checked the lowest distance first:
if(distFromCam < 64)
showParticle = false;
else if(distFromCam < 256 && particle->LoD > 3)
showParticle = false;
else if(distFromCam < 512 && particle->LoD > 2)
showParticle = false;
else if(distFromCam < 1024 && particle->LoD > 1)
showParticle = false;
#3
Blake - good catch :-) I'll change my code - don't know why I didn't think of using it this way. However, I think I would change the order - the norm is for particles to be outside of the LoD distances, so it would probably be better to short circut faster for particles outside of range. I'll probably test it both ways, just to make sure ;-)
03/02/2003 (8:44 am)
Thanks Tim :-)Blake - good catch :-) I'll change my code - don't know why I didn't think of using it this way. However, I think I would change the order - the norm is for particles to be outside of the LoD distances, so it would probably be better to short circut faster for particles outside of range. I'll probably test it both ways, just to make sure ;-)
#4
03/02/2003 (1:03 pm)
Yeah, your probably right.
#5
03/03/2003 (11:19 am)
Just a quick comment. Might I suggest that you remove the magic numbers and either use constants, or preferrably config variables? This opens it up for easy tuning.
#6
03/03/2003 (1:04 pm)
Good idea - I'll test it when I get the chance, then update this version.
#7
03/04/2003 (9:28 am)
@Davis - one thing you might consider -- something I added to Orbz and ThinkTanks -- is a particle near distance. Particles closer to the camera than a certain distance don't get drawn. This gets rid of the particles that appear real big on the screen and cause a lot of overdraw issues. Won't help your average framerate, but it will your worst case framerate tremendously. Sometime after GDC I can send you a code snippet if you like...
#8
03/04/2003 (9:38 am)
Nod - that's what the closest LoD setting I have in there does, unless I'm not understanding you correctly. Anything that is closer than LoD1 distance is simply not drawn. (Unless I'm missing what you are saying, which is entirely possible :-)
#9
if(distFromCam < levelLoD4 && particle->LoD > 1)
showParticle = false;
else if(distFromCam < levelLoD3 && particle->LoD > 2)
showParticle = false;
else if(distFromCam < levelLoD2 && particle->LoD > 3)
showParticle = false;
else if(distFromCam < levelLoD1)
showParticle = false;
Becomes:
showParticle == (((distFromCam < levelLoD4) && (particle->LoD == 1)) ||
((distFromCam < levelLoD3) && (particle->LoD <= 2)) ||
((distFromCam < levelLoD2) && (particle->LoD <= 3))) &&
(distFromCam > levelLoD1);
If LoD2, 3 and 4 are all power of 2's from LoD 1, this can be spead up:
showParticle == (distFromCam > levelLoD1) &&
(distFromCam <= (levelLoD1 << particle->LoD));
Additionally, you probably want to filter all particles past a certain distance:
showParticle == (distFromCam > ParticleNearClipPlane) &&
(distFromCam <= ParticleFarClipPlane) &&
(distFromCam <= (ParticleFullLoD << particle->LoD));
Note: ParticleFullLoD is the setting for the distance where all particles should be drawn, ParticleNearClipPlane is the setting for the distance where no particles should be drawn (because they are too near to the camera), and ParticleFarClipPlane is the setting for where no particles should be drawn (because they are too far from the camera).
The later check will speed up processing any time there are particles that are too far away to be worthwhile drawing.
Edit: As the original code stood when I posted this comment, anything past LoD4 is always drawn; you probably want a far clip plane check if nothing else -- if it is over a certain distance (LoD4), showParticle needs to always be false.
03/05/2003 (10:23 am)
You can eliminate the if statements entirely. This will eliminate branches in the loop, what you're looking for can be done purely mathmatically.if(distFromCam < levelLoD4 && particle->LoD > 1)
showParticle = false;
else if(distFromCam < levelLoD3 && particle->LoD > 2)
showParticle = false;
else if(distFromCam < levelLoD2 && particle->LoD > 3)
showParticle = false;
else if(distFromCam < levelLoD1)
showParticle = false;
Becomes:
showParticle == (((distFromCam < levelLoD4) && (particle->LoD == 1)) ||
((distFromCam < levelLoD3) && (particle->LoD <= 2)) ||
((distFromCam < levelLoD2) && (particle->LoD <= 3))) &&
(distFromCam > levelLoD1);
If LoD2, 3 and 4 are all power of 2's from LoD 1, this can be spead up:
showParticle == (distFromCam > levelLoD1) &&
(distFromCam <= (levelLoD1 << particle->LoD));
Additionally, you probably want to filter all particles past a certain distance:
showParticle == (distFromCam > ParticleNearClipPlane) &&
(distFromCam <= ParticleFarClipPlane) &&
(distFromCam <= (ParticleFullLoD << particle->LoD));
Note: ParticleFullLoD is the setting for the distance where all particles should be drawn, ParticleNearClipPlane is the setting for the distance where no particles should be drawn (because they are too near to the camera), and ParticleFarClipPlane is the setting for where no particles should be drawn (because they are too far from the camera).
The later check will speed up processing any time there are particles that are too far away to be worthwhile drawing.
Edit: As the original code stood when I posted this comment, anything past LoD4 is always drawn; you probably want a far clip plane check if nothing else -- if it is over a certain distance (LoD4), showParticle needs to always be false.
#10
It does seem simple enough--even I think I understand it. But every time I've tried to implement it it doesn't seem to make any difference. I even get fps drag when my player isn't facing the particles (which I guess may not be relevant.
Anyway, if anyone has any tips, I'd appreciate it! I, too, am a particleophile :-) But I've got some pretty serious performance issues with some that I would love to use.
By the way, the forum is showing the bold bbs code on the last line of one section of bold changes--a little disconcerting and let me astray for a bit, since it didn't look like added code.
04/04/2008 (4:38 pm)
I realize it's been five years (and thanks, by the way), but I was wondering if I should expect to not see particles which are very far away with this code? I'm asking because I've tried it several times over the past year or so and I can always see particles no matter how far away I am. So I'm wondering if I have bad assumptions!It does seem simple enough--even I think I understand it. But every time I've tried to implement it it doesn't seem to make any difference. I even get fps drag when my player isn't facing the particles (which I guess may not be relevant.
Anyway, if anyone has any tips, I'd appreciate it! I, too, am a particleophile :-) But I've got some pretty serious performance issues with some that I would love to use.
By the way, the forum is showing the bold bbs code on the last line of one section of bold changes--a little disconcerting and let me astray for a bit, since it didn't look like added code.
#11
Hope that helps.......
08/02/2008 (10:30 am)
I kinda did the opposite in the sense that up close particles were fine and that particles being rendered far away were pointless to render so I changed this:S32 distFromCam = mPow(particle->pos.x - cameraPos.x,2) + mPow(particle->pos.y - cameraPos.y,2) + mPow(particle->pos.z - cameraPos.z,2); showParticle = true; // just use one of the levelLoDX's and use that for distance in your scripts // notice the Author used the < instead of > so if particles are greater than a particular distance, // don't render! if(distFromCam > levelLoD5) showParticle = false;
Hope that helps.......

Torque Owner Tim Gift