In this recipe, we will learn how to apply a texture to all our particles using OpenGL point sprites and a GLSL Shader.
This method is optimized and allows for a large number of particles to be drawn at fast frame rates.
We will be using the particle system developed in the recipe Creating a particle system in 2D from Chapter 5, Building Particle Systems. So we will need to add the following files to your project:
Particle.h
ParticleSystem.h
We will also be loading an image to use as texture. The image's size must be a power of two, such as 256 x 256 or 512 x 512. Place the image inside the assets
folder and name it particle.png
.
We will create a GLSL shader and then enable OpenGL point sprites to draw textured particles.
shader.frag
shader.vert
Add them to the assets
folder.
shader.frag
in your IDE of choice and declare a uniform sampler2D
:uniform sampler2D tex;
main
function we use the texture to define the fragment color. Add the following code:void main (void) { gl_FragColor = texture2D(tex, gl_TexCoord[0].st) * gl_Color; }
shader.vert
file and create float attribute
to store the particle's radiuses. In the main
method we define the position, color, and point size attributes. Add the following code:attribute float particleRadius; void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_PointSize = particleRadius; gl_FrontColor = gl_Color; }
#include "cinder/gl/Texture.h" #include "cinder/ImageIo.h" #include "cinder/Rand.h" #include "cinder/gl/GlslProg.h" #include "ParticleSystem.h"
ParticleSystem mParticleSystem; int mNumParticles; Vec2f *mPositions; float *mRadiuses;
setup
method, let's initialize mNumParticles
to 1000
and allocate the arrays. We will also create the random particles.mNumParticles = 1000; mPositions = new Vec2f[ mNumParticles ]; mRadiuses = new float[ mNumParticles ]; for( int i=0; i<mNumParticles; i++ ){ float x = randFloat( 0.0f, getWindowWidth() ); float y = randFloat( 0.0f, getWindowHeight() ); float radius = randFloat( 5.0f, 50.0f ); Particle *particle = new Particle( Vec2f( x, y ), radius, 1.0f, 0.9f ); mParticleSystem.addParticle( particle ); } mParticleSystem.addParticle( particle );
update
method, we will update mParticleSystem
and the mPositions
and mRadiuses
arrays. Add the following code to the update
method:mParticleSystem.update(); for( int i=0; i<mNumParticles; i++ ){ mPositions[i] = mParticleSystem.particles[i]->position; mRadiuses[i] = mParticleSystem.particles[i]->radius; }
gl::Texture mTexture; gl::GlslProg mShader;
setup
method:mTexture = loadImage( loadAsset( "particle.png" ) ); mShader = gl::GlslProg( loadAsset( "shader.vert"), loadAsset( "shader.frag" ) );
draw
method, we will start by clearing the background with black, set the window's matrices, enable the additive blend, and bind the shader.gl::clear( Color( 0, 0, 0 ) ); gl::setMatricesWindow( getWindowWidth(), getWindowHeight() ); gl::enableAdditiveBlending(); mShader.bind();
particleRadius
attribute in the Vertex
shader. Enable vertex attribute arrays and set the pointer to mRadiuses
.GLint particleRadiusLocation = mShader.getAttribLocation( "particleRadius" ); glEnableVertexAttribArray(particleRadiusLocation); glVertexAttribPointer(particleRadiusLocation, 1, GL_FLOAT, false, 0, mRadiuses);
glEnable(GL_POINT_SPRITE); glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE); glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
mPositions
.glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_FLOAT, 0, mPositions);
mTexture.enableAndBind(); glDrawArrays( GL_POINTS, 0, mNumParticles ); mTexture.unbind();
glDisableClientState(GL_VERTEX_ARRAY); glDisableVertexAttribArrayARB(particleRadiusLocation); mShader.unbind();
1000
random particles with the applied texture.Point sprites is a nice feature of OpenGL that allows for the application of an entire texture to a single point. It is extremely useful when drawing particle systems and is quite optimized, since it reduces the amount of information sent to the graphics card and performs most of the calculations on the GPU.
In the recipe we also created a GLSL shader, a high-level programming language, that allows more control over the programming pipeline, to define individual point sizes for each particle.
In the update
method we updated the Positions
and Radiuses
arrays, so that if the particles are animated the arrays will represent the correct values.
Point sprites allow us to texturize points in 3D space. To draw the particle system in 3D do the following:
Particle
class described in the There's more… section of the recipe Creating a Particle system in 2D from Chapter 5, Building Particle Systems.mPositions
as a ci::Vec3f
array.draw
method, indicate that the vertex pointer contains 3D information by applying the following change:glVertexPointer(2, GL_FLOAT, 0, mPositions);
Change the previous code line to:
glVertexPointer(3, GL_FLOAT, 0, mPositions);
shader.vert
file would need to read the following code:attribute float particleRadius; void main(void) { vec4eyeCoord = gl_ModelViewMatrix * gl_Vertex; gl_Position = gl_ProjectionMatrix * eyeCoord; float distance = sqrt(eyeCoord.x*eyeCoord.x + eyeCoord.y*eyeCoord.y + eyeCoord.z*eyeCoord.z); float attenuation = 3000.0 / distance; gl_PointSize = particleRadius * attenuation; gl_FrontColor = gl_Color; }
3.143.0.85