// // Book: OpenGL(R) ES 2.0 Programming Guide // Authors: Aaftab Munshi, Dan Ginsburg, Dave Shreiner // ISBN-10: 0321502795 // ISBN-13: 9780321502797 // Publisher: Addison-Wesley Professional // URLs: http://safari.informit.com/9780321563835 // http://www.opengles-book.com // // ParticleSystem.c // // This is an example that demonstrates rendering a particle system // using a vertex shader and point sprites. // #include #include #include "esUtil.h" #define NUM_PARTICLES 1000 #define PARTICLE_SIZE 7 typedef struct { // Handle to a program object GLuint programObject; // Attribute locations GLint lifetimeLoc; GLint startPositionLoc; GLint endPositionLoc; // Uniform location GLint timeLoc; GLint colorLoc; GLint centerPositionLoc; GLint samplerLoc; // Texture handle GLuint textureId; // Particle vertex data float particleData[ NUM_PARTICLES * PARTICLE_SIZE ]; // Current time float time; } UserData; /// // Load texture from disk // GLuint LoadTexture ( char *fileName ) { int width, height; char *buffer = esLoadTGA ( fileName, &width, &height ); GLuint texId; if ( buffer == NULL ) { esLogMessage ( "Error loading (%s) image.\n", fileName ); return 0; } glGenTextures ( 1, &texId ); glBindTexture ( GL_TEXTURE_2D, texId ); glTexImage2D ( GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); free ( buffer ); return texId; } /// // Initialize the shader and program object // int Init ( ESContext *esContext ) { UserData *userData = esContext->userData; int i; GLbyte vShaderStr[] = "uniform float u_time; \n" "uniform vec3 u_centerPosition; \n" "attribute float a_lifetime; \n" "attribute vec3 a_startPosition; \n" "attribute vec3 a_endPosition; \n" "varying float v_lifetime; \n" "void main() \n" "{ \n" " if ( u_time <= a_lifetime ) \n" " { \n" " gl_Position.xyz = a_startPosition + \n" " (u_time * a_endPosition); \n" " gl_Position.xyz += u_centerPosition; \n" " gl_Position.w = 1.0; \n" " } \n" " else \n" " gl_Position = vec4( -1000, -1000, 0, 0 ); \n" " v_lifetime = 1.0 - ( u_time / a_lifetime ); \n" " v_lifetime = clamp ( v_lifetime, 0.0, 1.0 ); \n" " gl_PointSize = ( v_lifetime * v_lifetime ) * 40.0; \n" "}"; GLbyte fShaderStr[] = "precision mediump float; \n" "uniform vec4 u_color; \n" "varying float v_lifetime; \n" "uniform sampler2D s_texture; \n" "void main() \n" "{ \n" " vec4 texColor; \n" " texColor = texture2D( s_texture, gl_PointCoord ); \n" " gl_FragColor = vec4( u_color ) * texColor; \n" " gl_FragColor.a *= v_lifetime; \n" "} \n"; // Load the shaders and get a linked program object userData->programObject = esLoadProgram ( vShaderStr, fShaderStr ); // Get the attribute locations userData->lifetimeLoc = glGetAttribLocation ( userData->programObject, "a_lifetime" ); userData->startPositionLoc = glGetAttribLocation ( userData->programObject, "a_startPosition" ); userData->endPositionLoc = glGetAttribLocation ( userData->programObject, "a_endPosition" ); // Get the uniform locations userData->timeLoc = glGetUniformLocation ( userData->programObject, "u_time" ); userData->centerPositionLoc = glGetUniformLocation ( userData->programObject, "u_centerPosition" ); userData->colorLoc = glGetUniformLocation ( userData->programObject, "u_color" ); userData->samplerLoc = glGetUniformLocation ( userData->programObject, "s_texture" ); glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f ); // Fill in particle data array srand ( 0 ); for ( i = 0; i < NUM_PARTICLES; i++ ) { float *particleData = &userData->particleData[i * PARTICLE_SIZE]; // Lifetime of particle (*particleData++) = ( (float)(rand() % 10000) / 10000.0f ); // End position of particle (*particleData++) = ( (float)(rand() % 10000) / 5000.0f ) - 1.0f; (*particleData++) = ( (float)(rand() % 10000) / 5000.0f ) - 1.0f; (*particleData++) = ( (float)(rand() % 10000) / 5000.0f ) - 1.0f; // Start position of particle (*particleData++) = ( (float)(rand() % 10000) / 40000.0f ) - 0.125f; (*particleData++) = ( (float)(rand() % 10000) / 40000.0f ) - 0.125f; (*particleData++) = ( (float)(rand() % 10000) / 40000.0f ) - 0.125f; } // Initialize time to cause reset on first update userData->time = 1.0f; userData->textureId = LoadTexture ( "smoke.tga" ); if ( userData->textureId <= 0 ) { return FALSE; } return TRUE; } /// // Update time-based variables // void Update ( ESContext *esContext, float deltaTime ) { UserData *userData = esContext->userData; userData->time += deltaTime; if ( userData->time >= 1.0f ) { float centerPos[3]; float color[4]; userData->time = 0.0f; // Pick a new start location and color centerPos[0] = ( (float)(rand() % 10000) / 10000.0f ) - 0.5f; centerPos[1] = ( (float)(rand() % 10000) / 10000.0f ) - 0.5f; centerPos[2] = ( (float)(rand() % 10000) / 10000.0f ) - 0.5f; glUniform3fv ( userData->centerPositionLoc, 1, ¢erPos[0] ); // Random color color[0] = ( (float)(rand() % 10000) / 20000.0f ) + 0.5f; color[1] = ( (float)(rand() % 10000) / 20000.0f ) + 0.5f; color[2] = ( (float)(rand() % 10000) / 20000.0f ) + 0.5f; color[3] = 0.5; glUniform4fv ( userData->colorLoc, 1, &color[0] ); } // Load uniform time variable glUniform1f ( userData->timeLoc, userData->time ); } /// // Draw a triangle using the shader pair created in Init() // void Draw ( ESContext *esContext ) { UserData *userData = esContext->userData; // Set the viewport glViewport ( 0, 0, esContext->width, esContext->height ); // Clear the color buffer glClear ( GL_COLOR_BUFFER_BIT ); // Use the program object glUseProgram ( userData->programObject ); // Load the vertex attributes glVertexAttribPointer ( userData->lifetimeLoc, 1, GL_FLOAT, GL_FALSE, PARTICLE_SIZE * sizeof(GLfloat), userData->particleData ); glVertexAttribPointer ( userData->endPositionLoc, 3, GL_FLOAT, GL_FALSE, PARTICLE_SIZE * sizeof(GLfloat), &userData->particleData[1] ); glVertexAttribPointer ( userData->startPositionLoc, 3, GL_FLOAT, GL_FALSE, PARTICLE_SIZE * sizeof(GLfloat), &userData->particleData[4] ); glEnableVertexAttribArray ( userData->lifetimeLoc ); glEnableVertexAttribArray ( userData->endPositionLoc ); glEnableVertexAttribArray ( userData->startPositionLoc ); // Blend particles glEnable ( GL_BLEND ); glBlendFunc ( GL_SRC_ALPHA, GL_ONE ); // Bind the texture glActiveTexture ( GL_TEXTURE0 ); glBindTexture ( GL_TEXTURE_2D, userData->textureId ); glEnable ( GL_TEXTURE_2D ); // Set the sampler texture unit to 0 glUniform1i ( userData->samplerLoc, 0 ); glDrawArrays( GL_POINTS, 0, NUM_PARTICLES ); eglSwapBuffers ( esContext->eglDisplay, esContext->eglSurface ); } /// // Cleanup // void ShutDown ( ESContext *esContext ) { UserData *userData = esContext->userData; // Delete texture object glDeleteTextures ( 1, &userData->textureId ); // Delete program object glDeleteProgram ( userData->programObject ); } int main ( int argc, char *argv[] ) { ESContext esContext; UserData userData; esInitContext ( &esContext ); esContext.userData = &userData; esCreateWindow ( &esContext, TEXT("ParticleSystem"), 640, 480, ES_WINDOW_RGB ); if ( !Init ( &esContext ) ) return 0; esRegisterDrawFunc ( &esContext, Draw ); esRegisterUpdateFunc ( &esContext, Update ); esMainLoop ( &esContext ); ShutDown ( &esContext ); }