class REPart // Rectangular engine particle
{
public:
void InitParticle(float xmin, float xmax, float ymin, float ymax, float zc, float r, float g, float b);
void DrawParticle(void);
bool IsVisible(void);
private:
float x; // Horizontal Location
float y; // Vertical Location
float z;
float red;
float green;
float blue;
float alpha;
};
void REPart::InitParticle(float xmin, float xmax, float ymin, float ymax, float zc, float r, float g, float b)
{
float width = xmax - xmin;
width = fabs(width);
float half = (width / 2.0);
float height = ymax - ymin;
height = fabs(height);
float hhalf = (height / 2.0);
x = (((float)rand()/(float)RAND_MAX))*(xmin);
x+= (((float)rand()/(float)RAND_MAX))*(xmax);
y = (((float)rand()/(float)RAND_MAX))*(ymin);
y+= (((float)rand()/(float)RAND_MAX))*(ymax);
z = zc;
red = r;
green = g;
blue = b;
alpha = (((float)rand()/(float)RAND_MAX))*(1.0);
//alpha = 1.0-(fabs(x)/half);
//alpha -= (fabs(y)/half);
}
void REPart::DrawParticle(void)
{
float size = 0.001;
glColor4f(red, green, blue, alpha);
glBegin(GL_TRIANGLES);
glVertex3d(x, y, z);
glVertex3d(x+size, y, z);
glVertex3d(x+size, y+size, z);
glEnd();
z+=0.006;
alpha-=0.01;
}
bool REPart::IsVisible(void)
{
if(alpha > 0)
return true;
if(alpha <= 0)
return false;
return true;
}
class Engine : public Rotatable
{
public:
Engine()
{
};
~Engine()
{
};
virtual void Update()
{
}
protected:
float dx; // X location relative to parent
float dy; // Y location relative to parent
float dz; // Z location relative to parent
float pitch; // Pitch relative to parent (rotation about parent's X axis
float yaw; // Yaw relative to parent (rotation about parent's Y axis
float r;
float g;
float b;
float wid;
float len;
Rotatable *parent; // A reference to the parent ship, to facilitate easier updates.
//etc
};
class Rect_Engine : public Engine
{
public:
Rect_Engine()
{
};
Rect_Engine(Rotatable *par, float dispx, float dispy, float dispz, float length, float width, float rx, float ry, float red, float green, float blue)
{
int i;
dx = dispx;
dy = dispy;
dz = dispz;
pitch = rx;
yaw = ry;
wid = width;
len = length;
r = red;
g = green;
b = blue;
parent = par;
Position = parent->GetPos();
UpVector = parent->GetUp();
RightVector = parent->GetRight();
ViewDir = parent->GetView();
MoveForward(-dz);
StrafeRight(dx);
MoveUpward(dy);
RotateX(pitch);
RotateY(yaw);
for(i=0; i<MAX_ENGINE_PARTS; i++)
{
parts.InitParticle(dispx, width, dispy, length, dispz, red, green, blue);
}
}
void Update() // Updates position
{
int i;
Position = parent->GetPos();
UpVector = parent->GetUp();
RightVector = parent->GetRight();
ViewDir = parent->GetView();
MoveForward(-dz);
StrafeRight(dx);
MoveUpward(dy);
RotateX(pitch);
RotateY(yaw);
for(i=0; i<MAX_ENGINE_PARTS; i++)
{
if(parts.IsVisible() == false)
parts.InitParticle(dx, wid, dy, len, dz, r, g, b);
else
parts.DrawParticle();
}
// CODE FOR DRAWING THE BLOODY THING HERE
}
private:
REPart parts[MAX_ENGINE_PARTS];
};
🔒 Locked
🎮 Unity
Speeding up particle effect
Advertisement
Original Post
I am working on particle emitters for engine exhaust for the game I'm making, and I really need to optimize it. Right now it is taking a framerate cost of about 20fps. I would like to minimize that as much as possible. Basically, its a large number of triangular planar particles. Every update, their alpha level drops slightly (to create a fade-with-distance effect) and they move slightly backwards on the z axis. Is there a way to put particle drawing into a display list or something so that it draws faster (to the point of relatively minimal framerate cost)? I've used display lists before, but on stationary effects where nothing ever changes. here is the code to my engine-emitter subsystem: Thanks for your time, folks! [edit] I know the code there would make it very ugly. This is basically the first revision of it. I'm working on an algorithm to make the particle placement and coloring look better. [edit#2] Sorry, I just noticed I forgot the REPart definition. It has been added. [Edited by - medevilenemy on July 18, 2008 12:23:07 AM]
Don't use immediate mode (glBegin) and certainly don't draw your particles one at a time. Put them all into a big array and draw them via vertex arrays or VBOs.
I don't know how to do that. I've tried to figure out vertex arrays/VBOs before... but without any success. Could someone show a modification of the code that would work? Thanks for commenting.
For vertex arrays, you'd want to do something like...
I hope that helps.
float Triangle[3 * 3] = { -0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, };// http://opengl.org/documentation/specs/man_pages/hardcopy/GL/html/gl/enableclientstate.html// void glEnableClientState(GLenum cap)glEnableClientState(GL_VERTEX_ARRAY);/* You are probably going to want to enable GL_TEXTURE_COORD_ARRAY and GL_NORMAL_ARRAY. */// http://www.opengl.org/documentation/specs/man_pages/hardcopy/GL/html/gl/vertexpointer.html// void glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer )glVertexPointer(3, GL_FLOAT, 0, Triangle);/* There is also glNormalPointer() and glTexCoordPointer() */// glTexCoordPointer(): http://www.opengl.org/documentation/specs/man_pages/hardcopy/GL/html/gl/texcoordpointer.html// glNormalPointer(): http://www.opengl.org/documentation/specs/man_pages/hardcopy/GL/html/gl/normalpointer.html// http://www.opengl.org/documentation/specs/man_pages/hardcopy/GL/html/gl/drawarrays.html// void glDrawArrays(GLenum mode, GLint first, GLsizei count)glDrawArrays(GL_TRIANGLE, 0, 1); // count is number of primitives, not verticesI hope that helps.
How do I color, place, and move so many particles?
Apologies, but I don't know the first thing about how to use vertex arrays (though I keep trying to figure it out).
Another thing you can try which can give a great performance increase is point sprites. It essentially mimics having a textured quad as a particle but you need only supply a point.
check out the GL_ARB_point_sprite extension.
here is some sample code I took from a particle system I wrote back in the day.
check out the GL_ARB_point_sprite extension.
here is some sample code I took from a particle system I wrote back in the day.
const float quadratic[] = { 1.0f, 0.0f, 0.01f };glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, quadratic);glPointParameterfARB(GL_POINT_FADE_THRESHOLD_SIZE_ARB, 60.0f);glPointParameterfARB(GL_POINT_SIZE_MIN_ARB, 1.0f);glPointParameterfARB(GL_POINT_SIZE_MAX_ARB, MaxPointSize);glTexEnvf(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE);glEnable(GL_POINT_SPRITE_ARB);glPointSize( size ); glBegin(GL_POINTS); for(all particles){ glColor3f(particle->color.x, particle->color.y, particle->;color.z); glVertex3f(particle->position.x,particle->position.y,particle->position.z);}glEnd();glDisable(GL_POINT_SPRITE_ARB);
I'm trying it the point sprite way... just one more thing about that: how do I enable the extension?
Quote:
Original post by medevilenemy
I'm trying it the point sprite way... just one more thing about that: how do I enable the extension?
Enabling the extension involves checking your vid card for support of the extension. You can get a list of supported extensions with glGetString(GL_EXTENSIONS). Its sort of dirty on windows what you have to do. For example with the point sprite extension you need to check for "GL_ARB_point_sprite" and if your card supports it do something like this
PFNGLPOINTPARAMETERFVPROC glPointParameterfv = NULL;PFNGLPOINTPARAMETERFPROC glPointParameterf = NULL;glPointParameterfv = (PFNGLPOINTPARAMETERFVPROC) wglGetProcAddress("glPointParameterfv");glPointParameterf = (PFNGLPOINTPARAMETERFPROC) wglGetProcAddress("glPointParameterf");As I said it can get sort of dirty. Since this is probably not enough information, I suggest googling the topic of opengl extensions. There are many tutorials. There are also libraries that can help you load extensions. GLEW is a good one. If you get deep into opengl and use windows then extensions will be a part of your life so its nice to get familiar with them.
20 fps drop ? From how many fps ? 1000 or 100 ? Or 40 ? That
Using this method, you
s a huge difference.
OK, so youve got the particles rendered. Thats good. Whats even better is that you want that rendered faster, since youve got the slowest possible method of rendering particles. but at least it works.
But, no matter how efficiently you render them, you still have one major CPU bottleneck - CPU update of particles vertices. Get rid of that by calculating the vertex positions inside the vertex shader. Google these threads for more info - I believe there was an article on MVPs Robert Dunlops site about this (not sure if it was vertex-shader specific, though). Using this method, you
ll put the particles update from CPU to GPU, which is fine, since its extremely improbable youll use the vertex-transform muscle power of todays cards to their max, anyway.
You don't measure performance by FPS or by the change in FPS. You measure it by timing *how long* it takes to execute your code. Even more important is to get to know how occupied each stage of the pipeline is with respect to the other stages. Typically the stages you're interested in are something like CPU usage, dynamic per-frame CPU->GPU data bandwidth, GPU vertex and pixel processing overhead, framebuffer pixel fillrates.
It doesn't help anyone too much at all to see that the FPS dropped by 20, since it doesn't give a clue to the reason why that happens. Is it because CPU processing of the particles was too slow? or perhaps because transferring the particle vertices in VB is too slow? or perhaps because of the big particle sizes causing fillrate bottleneck, or a texture read bottleneck?
There's a few easy things to do to get started on the right track of profiling. First, get rid of the print(fps) and replace it with print(msecs in CPU logic per frame) and print(msecs in render calls per frame). To get a rough estimate whether you're CPU or GPU limited, measure the time spent in D3DDevice->Present(), (and Begin()) as any time spent there will tell you that the CPU is stalling to wait for GPU to finish rendering.
If you're GPU limited, you typically want to know what causes it. Too much dynamic data? Too big/many textures? Too much per-pixel fill? Too much arithmetic or shader complexity? The typical/quick way to evaluate between these is to reduce to smaller textures, simpler shaders, remove filling, etc.
Another way is to use pix or perfhud. My advice is quite DX-specific and you're using OGL, but I reckon perfhud works on OGL as well.
It doesn't help anyone too much at all to see that the FPS dropped by 20, since it doesn't give a clue to the reason why that happens. Is it because CPU processing of the particles was too slow? or perhaps because transferring the particle vertices in VB is too slow? or perhaps because of the big particle sizes causing fillrate bottleneck, or a texture read bottleneck?
There's a few easy things to do to get started on the right track of profiling. First, get rid of the print(fps) and replace it with print(msecs in CPU logic per frame) and print(msecs in render calls per frame). To get a rough estimate whether you're CPU or GPU limited, measure the time spent in D3DDevice->Present(), (and Begin()) as any time spent there will tell you that the CPU is stalling to wait for GPU to finish rendering.
If you're GPU limited, you typically want to know what causes it. Too much dynamic data? Too big/many textures? Too much per-pixel fill? Too much arithmetic or shader complexity? The typical/quick way to evaluate between these is to reduce to smaller textures, simpler shaders, remove filling, etc.
Another way is to use pix or perfhud. My advice is quite DX-specific and you're using OGL, but I reckon perfhud works on OGL as well.
Sorry about the poor representation of the slowdown... in the normal immediate mode version it drops from about 47-48fps to about 18-20. From experience with earlier rough code of mine, it is almost definitely the immediate-mode draw calls bottlenecking the CPU. glBegin() itself is rather slow, but glBegin() with three glVertex3f() and a glEnd() 10,000 times per frame is horrific. Ideally, I'd like to offload as much of this to the gpu as possible. Both VAs and Point sprites look like great ways to do this (especially point sprites since it would only need a third of the verts, and those would all be kicked off to the gpu and out of the immediate pipeline.
I'm working on trying to get the point sprite extension to work, currently trying to get over a couple errors (PFNGLPOINTPARAMETERFVPROC undefined, etc). Perhaps I have an outtdated version of glext.h
I'm working on trying to get the point sprite extension to work, currently trying to get over a couple errors (PFNGLPOINTPARAMETERFVPROC undefined, etc). Perhaps I have an outtdated version of glext.h
Just downloaded the newest version of glext.h, and changed the calls from glPointParameterfvARB and glPointParameterfARB to glPointParameterfv and glPointParameterf and it compiles and draws now. Tweaked a couple more things and I now have 10,000 particles with a hit of about 3fps from about 60 (sorry, I reported it wrong before). I'm working on tweaking some more to get that down farther (also, my dev machine is intentionally weaker than I expect most systems to run this will be. It only has intel integrated graphics).
Anyway, thanks for all the help, any more suggestions welcome!
Anyway, thanks for all the help, any more suggestions welcome!
Ok, I have it working fairly well now. Just a couple things I can't figure out how to tweak properly: I can't figure out how to get the particle size to change... glPointSize() doesn't seem to do anything, The particles fade to black as the camera moves towards them, and get brighter as you move away... I'm guessing that has something to do with the attenuation but I can't get that to do anything (it doesn't seem to have any obvious effect). Thanks again!
Perhaps my dev machine just doesn't support the extension? (it is supposed to have full Opengl 1.5, idk when this extension was introduced)
[Edited by - medevilenemy on July 18, 2008 1:38:03 PM]
Perhaps my dev machine just doesn't support the extension? (it is supposed to have full Opengl 1.5, idk when this extension was introduced)
[Edited by - medevilenemy on July 18, 2008 1:38:03 PM]
As already suggested, moving the glBegin and glEnd out of the draw function is very likely to give a significant performance boost without changing too much:
Note how I switched the if/else blocks to get rid of the == false.
You don't need a seperate step in calculating the absolute value of width and height. And since you seem to need half as a divisor, I suggest to calculate the inverse and multiply by that instead:
The generation of coordinates seems wrong. You start at a point between 0 and xmin, and then add a random number between 0 and xmax. So if you say "I want a point between 99 and 100", you'll get a point between 0 and 199. Not what you want, right? I think you want this instead:
BTW all this repeating random code is hard on the eye. Make a new inline function that does just that and call it. Then you can keep all changes in one place. For example, you can get rid of the division:
OK, on to your IsVisible member function. Another one of those if boolexp return true; else return false; examples :) You can make it a lot shorter:
One last thing: you are not programming in C++, you are programming in C with classes. For example, MAX_ENGINE_PARTS looks very suspicious (a C macro?). Manually looping with an integer and using parts is also not C++.
void REPart::DrawParticle(void){ float size = 0.001; glColor4f(red, green, blue, alpha); glVertex3d(x, y, z); glVertex3d(x+size, y, z); glVertex3d(x+size, y+size, z); z+=0.006; alpha-=0.01;}// ... void Update() // Updates position { // ... glBegin(GL_TRIANGLES); for(i = 0; i < MAX_ENGINE_PARTS; ++i) { if(parts.IsVisible()) parts.DrawParticle(); else parts.InitParticle(dx, wid, dy, len, dz, r, g, b); } glEnd(); // ... }Note how I switched the if/else blocks to get rid of the == false.
You don't need a seperate step in calculating the absolute value of width and height. And since you seem to need half as a divisor, I suggest to calculate the inverse and multiply by that instead:
float width = fabs(xmax - xmin); float ihalf = 2.0f / width; float height = fabs(ymax - ymin); float ihhalf = 2.0f / height; alpha = 1.0f - (fabs(x) * ihalf); alpha -= (fabs(y) * ihalf);The generation of coordinates seems wrong. You start at a point between 0 and xmin, and then add a random number between 0 and xmax. So if you say "I want a point between 99 and 100", you'll get a point between 0 and 199. Not what you want, right? I think you want this instead:
x = xmin + (((float)rand()/(float)RAND_MAX))*(width); y = ymin + (((float)rand()/(float)RAND_MAX))*(height);BTW all this repeating random code is hard on the eye. Make a new inline function that does just that and call it. Then you can keep all changes in one place. For example, you can get rid of the division:
inline float random(float interval){ const float irandmax = 1.0f / RAND_MAX; // evaluated at compile time return rand() * irandmax * interval; // no division at runtime}// ... x = xmin + random(width); y = ymin + random(height);OK, on to your IsVisible member function. Another one of those if boolexp return true; else return false; examples :) You can make it a lot shorter:
bool REPart::IsVisible() // no need for void inside the paranthesis{ return alpha > 0; // true if alpha is greater than zero, else false}One last thing: you are not programming in C++, you are programming in C with classes. For example, MAX_ENGINE_PARTS looks very suspicious (a C macro?). Manually looping with an integer and using parts is also not C++.
Sorry, a lot of that code has been changed now because of the attempted switch to point sprites. the IsVisible function no longer exists, the drawing routine has been completely rewritten, and doesn't work like the old code. I admit the coordinate generation code is kind of bad... actually, its some old hacked in code from a different project that I'm just using to get the rough effect down (it generates correctly, but in the wrong locations). I'll fix that soon.
I know I don't need to use == false (and at the moment I'm not since the thing works differently now) but I've just always liked to see that there. MAX_ENGINE_PARTS is a #define macro. I guess I'm just one of those few people that always kind of liked the preprocessor. using an integer and looping through parts is valid in this case since I'm actually using an array of known constant size of particle objects. Whenever a particle "dies" it isn't deleted, it is merely reinitialized. Admittedly, I have a rather odd style which is something of a mix of C and C++. Not "proper", I know, but its what I like and what works for me..
Right now, my focus is on figuring out why the point sprite extension doesn't seem to be working right. It looks like intel didn't include it in its implementation of OpenGL. I may need to switch to a different way to speed up the code. I don't know yet, but since I may need perhaps 20 of these effects on the screen at once (at different distances from the camera, of course) I definitely need it as efficient as possible. (and hopefully as simple as possible, so I can tweak it)
I know I don't need to use == false (and at the moment I'm not since the thing works differently now) but I've just always liked to see that there. MAX_ENGINE_PARTS is a #define macro. I guess I'm just one of those few people that always kind of liked the preprocessor. using an integer and looping through parts is valid in this case since I'm actually using an array of known constant size of particle objects. Whenever a particle "dies" it isn't deleted, it is merely reinitialized. Admittedly, I have a rather odd style which is something of a mix of C and C++. Not "proper", I know, but its what I like and what works for me..
Right now, my focus is on figuring out why the point sprite extension doesn't seem to be working right. It looks like intel didn't include it in its implementation of OpenGL. I may need to switch to a different way to speed up the code. I don't know yet, but since I may need perhaps 20 of these effects on the screen at once (at different distances from the camera, of course) I definitely need it as efficient as possible. (and hopefully as simple as possible, so I can tweak it)
Just tried it on my main PC (Athlon 64 X2 4800+, 2gb ddr2, Radeon HD 2600pro) and it looks the same... something doesn't seem quite right. I tried using the opengl extensions viewer to find GL_POINT_SPRITE_ARB and it doesn't seem to exist. There is, however, a GL_ARB_POINT_SPRITE. Maybe that has something to do with it?
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement