Particle Stretch

Started by
5 comments, last by Phy 19 years ago
I'm creating a 3D particle system, and now I want to add the ability for a sprite to stretch in the direction of it's velocity. I've seen lots of games incorporate this ability, but I can't find anything on the web about how you acheive this effect. Anyone know anything?
Advertisement
I can think about two ways of making such an effect :

1) Motion blur your particles.
2) Keep track of their 'n' lastest positions and display them with increasing alpha. This will work only if your particles have slow motion. For high motion particles sample positions non-linearly along negative actual velocity vector. Or may be a combination of the two.

The second seems better but can consume a lot of memory if you have a lot of particles. I've never tried to do this but it seems an interesting challenge, I'm intreseted about your results.

ptl
There are sveral articles out there on how you do billboarding along an axis. Do this, with the axis being your velocity vector.
¨@_
Snaily, is correct. I have used a particle system with his feature.
we called them "velocity aligned".. The particles rotated around the velocity vector axis.



We then had a stretch factor, this value determined how much
the particle stretches based on its velocity. So fast moving particles
would stretch out further than slow moving.

Obviously if you start making them too long realitive to their speed
they can look a bit weird with large changes of direction.

The next step up from this is more like ribbons, with more polys -
doom3 is a good place to look at an example of this more advanced version.
The built in particle editor allows you to mess with it.
I've implemented a 'billboarding' effect I beleive in my particle system. It looks nice and the particle system already was very robust =D

I'll post some pictures for the guy who wanted to see the results.

This is a picture of the particle system with another effect I already created.


This shows how it draws the quads.


This is another texture I made in 2 minutes. I wasn't going for a blood look, but thats what it looks like ;)


All the code for the effect is in Render(), Update() has the collision code.
phy:
nice work, how are your editing your particle systems?

also, are you going to try the more advanced ribbon type?
The particles are all in a linked list, to create an effect I call a function (there are several) to initialize how many particles the effect needs and thats about it. Render() and Update() treat all particles equally based on their flags, of which I'm starting to run out of at the moment ;) I'm using 14/16 ( 2 bytes ). I'll post the particle structures and how the blending effect is created.

Straight out of 'r_particle.h'.
#define PRT_MF_NONE	0#define PRT_MF_LINEAR	1#define PRT_MF_GRAVITY	2#define PRT_MF_COLLIDE	4#define PRT_MF_BOUNCE	8#define PRT_MF_REMOVE	16	// remove on collision#define PRT_MF_COLLIDED	128	// DoCollision sets this (handled in Update) so don't mess with it!#define PRT_RF_NONE	0#define PRT_RF_ROTATE	1#define PRT_RF_TWINKLE	2	// second sprite at inverse rotation#define PRT_RF_FRAMES	4	// multiple frames#define PRT_RF_NOLERP	8	// no color lerp#define PRT_RF_LERPMODE	16	// 0-InvLinear 1-InvSqr#define PRT_RF_ADDITIVE	32	// glBlendFunc( GL_SRC_COLOR, GL_ONE )#define PRT_RF_STRETCH	64	// stretch along velocity#define PRT_RF_STFIXED	128	// stretch length is fixed (8 * size)struct lParticle{	byte			rf;	byte			mf;	GLuint			tex;	Vertex3f		pos;	Vertex3f		vel;	short			nframes;	float			fps;	Color4f			scol;	Color4f			ecol;	float			rot;	float			rotvel;	float			size;	float			bounce;	float			birth;	float			death;	lParticle*	nextParticle;};



This is the entire Render(), It runs pretty fast, I can have thousands of particles on screen with no noticable lag. Usually Update() is eating the CPU with particles that have collision, I'd say about 5-6ms per call with a couple hundred particles using collision (testing about 300 triangles).
//Player positionextern Vertex3f vPosition;void prtParticleManager::Render( void ){	int viewport[4];	glGetIntegerv( GL_VIEWPORT, viewport );	float mv[16];	glGetFloatv( GL_MODELVIEW_MATRIX, mv );		Matrix_3x3 mModelView( mv[0], mv[1], mv[2], mv[4], mv[5], mv[6], mv[8], mv[9], mv[10] );	float fTime = g_GetCurrentTime();	glEnable(GL_BLEND);	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );	bool bAlphaFunc = true;		glDisable( GL_LIGHTING );	glEnable( GL_TEXTURE_2D );	glEnable( GL_DEPTH_TEST );	// Incorrect, but quick and easy	glDepthMask( GL_FALSE ); // Disable depth writing	lParticle *ptcl = BaseParticle;	GLuint	ctex = 0;	while ( ptcl )	{		int frame = 0;		if ( ptcl->rf & PRT_RF_FRAMES )		{			float delta = fTime - ptcl->birth;			delta = (float)fmod( delta, ptcl->fps );			delta = delta / ptcl->fps * ptcl->nframes;			frame = (int)delta;			if ( frame == ptcl->nframes )				frame = 0;		}		if ( ctex != ptcl->tex + frame )		{			ctex = ptcl->tex + frame;			glBindTexture( GL_TEXTURE_2D, ctex );		}		if ( ptcl->rf & PRT_RF_ADDITIVE )		{			if ( bAlphaFunc )			{				glBlendFunc( GL_SRC_ALPHA, GL_ONE );				bAlphaFunc = false;			}		}		else		{			if ( !bAlphaFunc )			{				glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );				bAlphaFunc = true;			}		}		// Removed this because I'm not using it and it's to slow,//  I also needed another space in the render flags ;)		// FIXME: This is WAAAYYY too slow and it doesn't work half the time.		//  I'd bet that I could use traces and it would run faster and work beter/*		if ( ptcl->rf & PRT_RF_GLOW )		{			if ( bIsDepthTest )			{				glDisable( GL_DEPTH_TEST );				bIsDepthTest = false;			}			int screenpos[2];			float tpos[4];			float ppos[4];			float mtx[16];			glGetFloatv( GL_MODELVIEW_MATRIX, mtx );			tpos[0] = mtx[0]*ptcl->pos[0] + mtx[4]*ptcl->pos[1] + mtx[8]*ptcl->pos[2] + mtx[12];			tpos[1] = mtx[1]*ptcl->pos[0] + mtx[5]*ptcl->pos[1] + mtx[9]*ptcl->pos[2] + mtx[13];			tpos[2] = mtx[2]*ptcl->pos[0] + mtx[6]*ptcl->pos[1] + mtx[10]*ptcl->pos[2] + mtx[14];			tpos[3] = mtx[3]*ptcl->pos[0] + mtx[7]*ptcl->pos[1] + mtx[11]*ptcl->pos[2] + mtx[15];			glGetFloatv( GL_PROJECTION_MATRIX, mtx );			ppos[0] = mtx[0]*tpos[0] + mtx[4]*tpos[1] + mtx[8]*tpos[2] + mtx[12]*tpos[3];			ppos[1] = mtx[1]*tpos[0] + mtx[5]*tpos[1] + mtx[9]*tpos[2] + mtx[13]*tpos[3];			ppos[2] = mtx[2]*tpos[0] + mtx[6]*tpos[1] + mtx[10]*tpos[2] + mtx[14]*tpos[3];			ppos[3] = mtx[3]*tpos[0] + mtx[7]*tpos[1] + mtx[11]*tpos[2] + mtx[15]*tpos[3];			ppos[0] /= ppos[3];			ppos[1] /= ppos[3];			ppos[2] /= ppos[3];			ppos[3] = 1.0f;			screenpos[0] = viewport[0] + (int)((1.0f + ppos[0]) * (float)viewport[2] / 2.0f);			screenpos[1] = viewport[1] + (int)((1.0f + ppos[1]) * (float)viewport[3] / 2.0f);			float fBuffValue;			glReadPixels( screenpos[0], screenpos[1], 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &fBuffValue );			if ( fBuffValue < ppos[2] )			{				ptcl = ptcl->nextParticle;				if ( ptcl )					goto Render_while;				else					goto Render_wend;			}		}		else		{			if ( !bIsDepthTest )			{				glEnable( GL_DEPTH_TEST );				bIsDepthTest = true;			}		}*/		Vertex3f points[4];		if ( ptcl->rf & PRT_RF_STRETCH )		{			Vertex3f forward;			float fVelSqrd = ptcl->vel.Sqr();			if ( !(ptcl->rf & PRT_RF_STFIXED) && (fVelSqrd * 0.1 >= ptcl->size*ptcl->size) )			{				forward = ptcl->vel * 0.1f;			}			else			{				float	fInvSqrt = FastInvSqrt( fVelSqrd );				if ( ptcl->rf & PRT_RF_STFIXED )				{					forward = ptcl->vel * ((8 * ptcl->size) * fInvSqrt);				}				else				{					Vertex3f newvel = ptcl->vel;					newvel *= fInvSqrt;					forward = newvel; 				}			}			Vertex3f right = vPosition - ptcl->pos;			Vertex3f up;			CrossProduct( &up, &forward, &right );			up *= FastInvSqrt( up.Sqr() ) * ptcl->size;			Vertex3f offset = forward * FastInvSqrt( forward.Sqr() ) * ptcl->size;			points[0] = ptcl->pos + (forward + offset) + up;			points[1] = ptcl->pos + (forward + offset) - up;			points[2] = ptcl->pos - (forward - offset) - up;			points[3] = ptcl->pos - (forward - offset) + up;		}		else		{            points[0].Set( -ptcl->size,  ptcl->size,  0.0f );			points[1].Set( -ptcl->size, -ptcl->size,  0.0f );			points[2].Set(  ptcl->size, -ptcl->size,  0.0f );			points[3].Set(  ptcl->size,  ptcl->size,  0.0f );			Matrix_3x3 mRotation;			Angle3f aSpriteAngle( 0.0f, ptcl->rot, 0.0f  );			AngleMatrix( aSpriteAngle, mRotation );			mRotation *= mModelView;			// Rotate Points and add Position			points[0] *= mRotation;			points[1] *= mRotation;			points[2] *= mRotation;			points[3] *= mRotation;			points[0] += ptcl->pos;			points[1] += ptcl->pos;			points[2] += ptcl->pos;			points[3] += ptcl->pos;		}		// Caclulate colors		Color4f	col;		if ( ptcl->rf & PRT_RF_NOLERP )		{			col = ptcl->scol;		}		else		{			float delta = (fTime - ptcl->birth) / (ptcl->death - ptcl->birth);			if ( ptcl->rf & PRT_RF_LERPMODE )				delta = delta * delta;			LerpColor4f( &col, &ptcl->scol, &ptcl->ecol, delta );		}		//FIXME? This might benefit from multitexture, but the way it works right now wont		glColor4fv( (float*)&col );		glBegin( GL_QUADS );		  glTexCoord2f( 1.0f, 0.0f );		  glVertex3fv( &points[0][0] );		  glTexCoord2f( 0.0f, 0.0f );		  glVertex3fv( &points[1][0] );		  glTexCoord2f( 0.0f, 1.0f );		  glVertex3fv( &points[2][0] );		  glTexCoord2f( 1.0f, 1.0f );		  glVertex3fv( &points[3][0] );		if ( ptcl->rf & PRT_RF_TWINKLE && var->IGetBool( var_particles_twinkle ) )		{			points[0].Set( -ptcl->size,  ptcl->size,  0.0f );			points[1].Set( -ptcl->size, -ptcl->size,  0.0f );			points[2].Set(  ptcl->size, -ptcl->size,  0.0f );			points[3].Set(  ptcl->size,  ptcl->size,  0.0f );			Matrix_3x3 mRotation;			Angle3f aSpriteAngle( 0.0f, /*360.0f*/ -ptcl->rot, 0.0f );			AngleMatrix( aSpriteAngle, mRotation );			mRotation *= mModelView;			points[0] *= mRotation;			points[1] *= mRotation;			points[2] *= mRotation;			points[3] *= mRotation;			points[0] += ptcl->pos;			points[1] += ptcl->pos;			points[2] += ptcl->pos;			points[3] += ptcl->pos;			//glBegin( GL_QUADS );			glTexCoord2f( 1.0f, 0.0f );			glVertex3fv( &points[0][0] );			glTexCoord2f( 0.0f, 0.0f );			glVertex3fv( &points[1][0] );			glTexCoord2f( 0.0f, 1.0f );			glVertex3fv( &points[2][0] );			glTexCoord2f( 1.0f, 1.0f );			glVertex3fv( &points[3][0] );			//glEnd();		}		glEnd();		ptcl = ptcl->nextParticle;	}	// Reset state machine	glDepthMask( GL_TRUE );	glDisable( GL_BLEND );	glEnable( GL_DEPTH_TEST );}


Ribbons can come next, but I'm not sure how I will do them. I have to edit the particle struct to add data for them, and combining the beams... I might be able to add the offsets on each point and renormalize them, but that might be too slow.

This topic is closed to new replies.

Advertisement