#define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII #define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII #define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII #include "glew.h" #include "freeglut.h" #include "glm.hpp" #include "../glm/gtx/norm.hpp" #include #include #include #include #include "Particle.h" GLuint billboard_vertex_buffer; GLuint particles_position_buffer; GLuint particles_color_buffer; GLuint particleTexture; static GLfloat* g_particule_position_size_data; static GLubyte* g_particule_color_data; struct ParticleSource { glm::vec3 pos; double amount; float spread; }; std::vector particleSources; int ParticlesCount = 0; double lastTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f; // CPU representation of a particle struct Particle { glm::vec3 pos, speed; unsigned char r, g, b, a; float size, angle, weight; float life; float cameradistance; bool operator<(const Particle& that) const { return this->cameradistance > that.cameradistance; } }; void Core::Particle::addParticleSource(glm::vec3 pos, double amount, float spread) { ParticleSource particleSource; particleSource.pos = pos; particleSource.amount = amount; particleSource.spread = spread; particleSources.push_back(particleSource); } const int MaxParticles = 100000; Particle ParticlesContainer[MaxParticles]; int LastUsedParticle = 0; // Finds a Particle in ParticlesContainer which isn't used yet. // (i.e. life < 0); int FindUnusedParticle() { for (int i = LastUsedParticle; i < MaxParticles; i++) { if (ParticlesContainer[i].life < 0) { LastUsedParticle = i; return i; } } for (int i = 0; i < LastUsedParticle; i++) { if (ParticlesContainer[i].life < 0) { LastUsedParticle = i; return i; } } return 0; } void SortParticles() { std::sort(&ParticlesContainer[0], &ParticlesContainer[MaxParticles]); } GLuint loadDDS(const char* imagepath) { unsigned char header[124]; FILE* fp; /* try to open the file */ fp = fopen(imagepath, "rb"); if (fp == NULL) { printf("%s could not be opened. Are you in the right directory ? Don't forget to read the FAQ !\n", imagepath); getchar(); return 0; } /* verify the type of file */ char filecode[4]; fread(filecode, 1, 4, fp); if (strncmp(filecode, "DDS ", 4) != 0) { fclose(fp); return 0; } /* get the surface desc */ fread(&header, 124, 1, fp); unsigned int height = *(unsigned int*)&(header[8]); unsigned int width = *(unsigned int*)&(header[12]); unsigned int linearSize = *(unsigned int*)&(header[16]); unsigned int mipMapCount = *(unsigned int*)&(header[24]); unsigned int fourCC = *(unsigned int*)&(header[80]); unsigned char* buffer; unsigned int bufsize; /* how big is it going to be including all mipmaps? */ bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize; buffer = (unsigned char*)malloc(bufsize * sizeof(unsigned char)); fread(buffer, 1, bufsize, fp); /* close the file pointer */ fclose(fp); unsigned int components = (fourCC == FOURCC_DXT1) ? 3 : 4; unsigned int format; switch (fourCC) { case FOURCC_DXT1: format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; case FOURCC_DXT3: format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; case FOURCC_DXT5: format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; default: free(buffer); return 0; } // Create one OpenGL texture GLuint textureID; glGenTextures(1, &textureID); // "Bind" the newly created texture : all future texture functions will modify this texture glBindTexture(GL_TEXTURE_2D, textureID); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16; unsigned int offset = 0; /* load the mipmaps */ for (unsigned int level = 0; level < mipMapCount && (width || height); ++level) { unsigned int size = ((width + 3) / 4) * ((height + 3) / 4) * blockSize; glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height, 0, size, buffer + offset); offset += size; width /= 2; height /= 2; // Deal with Non-Power-Of-Two textures. This code is not included in the webpage to reduce clutter. if (width < 1) width = 1; if (height < 1) height = 1; } free(buffer); return textureID; } void Core::Particle::initParticles() { particleTexture = loadDDS("textures/particle.dds"); g_particule_position_size_data = new GLfloat[MaxParticles * 4]; g_particule_color_data = new GLubyte[MaxParticles * 4]; for (int i = 0; i < MaxParticles; i++) { ParticlesContainer[i].life = -1.0f; ParticlesContainer[i].cameradistance = -1.0f; } // The VBO containing the 4 vertices of the particles. // Thanks to instancing, they will be shared by all particles. static const GLfloat g_vertex_buffer_data[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, -0.5f, 0.5f, 0.0f, 0.5f, 0.5f, 0.0f, }; glGenBuffers(1, &billboard_vertex_buffer); glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer); glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW); // The VBO containing the positions and sizes of the particles glGenBuffers(1, &particles_position_buffer); glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer); // Initialize with empty (NULL) buffer : it will be updated later, each frame. glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLfloat), NULL, GL_STREAM_DRAW); // The VBO containing the colors of the particles glGenBuffers(1, &particles_color_buffer); glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer); // Initialize with empty (NULL) buffer : it will be updated later, each frame. glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLubyte), NULL, GL_STREAM_DRAW); } void spawnParticles(double deltaTime, glm::vec3 sourcePosition, double amount, float spread) { int newparticles = (int)(deltaTime * amount); if (newparticles > (int)(0.016f * amount)) newparticles = (int)(0.016f * amount); for (int i = 0; i < newparticles; i++) { int particleIndex = FindUnusedParticle(); ParticlesContainer[particleIndex].life = 20.0f; ParticlesContainer[particleIndex].pos = sourcePosition; glm::vec3 maindir = glm::vec3(0.0f, 0.4f, 0.0f); glm::vec3 randomdir = glm::vec3( (rand() % 2000 - 1000.0f) / 1000.0f, (rand() % 2000 - 1000.0f) / 1000.0f, (rand() % 2000 - 1000.0f) / 1000.0f ); ParticlesContainer[particleIndex].speed = maindir + randomdir * spread; ParticlesContainer[particleIndex].r = 200; ParticlesContainer[particleIndex].g = 200; ParticlesContainer[particleIndex].b = 255; ParticlesContainer[particleIndex].a = (rand() % 32) / 3 + 100; ParticlesContainer[particleIndex].size = (rand() % 1000) / 100000.0f + 0.05f; } } void simulateParticles(glm::vec3 cameraPos, double deltaTime) { ParticlesCount = 0; for (int i = 0; i < MaxParticles; i++) { Particle& p = ParticlesContainer[i]; if (p.life > 0.0f) { // Decrease life p.life -= deltaTime; if (p.life > 0.0f) { // Simulate simple physics : gravity only, no collisions // decreased acceleration - bubbles were too fast p.speed += glm::vec3(0.0f, 1.0f, 0.0f) * (float)deltaTime * 0.5f; p.pos += p.speed * (float)deltaTime; p.cameradistance = glm::length2(p.pos - cameraPos); // Fill the GPU buffer g_particule_position_size_data[4 * ParticlesCount + 0] = p.pos.x; g_particule_position_size_data[4 * ParticlesCount + 1] = p.pos.y; g_particule_position_size_data[4 * ParticlesCount + 2] = p.pos.z; g_particule_position_size_data[4 * ParticlesCount + 3] = p.size; g_particule_color_data[4 * ParticlesCount + 0] = p.r; g_particule_color_data[4 * ParticlesCount + 1] = p.g; g_particule_color_data[4 * ParticlesCount + 2] = p.b; g_particule_color_data[4 * ParticlesCount + 3] = p.a; } else { p.cameradistance = -1.0f; } ParticlesCount++; } } SortParticles(); } void updateParticles() { glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer); glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLfloat), NULL, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, ParticlesCount * sizeof(GLfloat) * 4, g_particule_position_size_data); glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer); glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLubyte), NULL, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, ParticlesCount * sizeof(GLubyte) * 4, g_particule_color_data); } void bindParticles(GLuint programParticles, glm::vec3 cameraSide, glm::vec3 cameraVertical, glm::mat4 cameraMatrix, glm::mat4 perspectiveMatrix) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glUseProgram(programParticles); // Vertex shader GLuint CameraRight_worldspace_ID = glGetUniformLocation(programParticles, "CameraRight_worldspace"); GLuint CameraUp_worldspace_ID = glGetUniformLocation(programParticles, "CameraUp_worldspace"); GLuint ViewProjMatrixID = glGetUniformLocation(programParticles, "VP"); // fragment shader GLuint TextureID = glGetUniformLocation(programParticles, "myTextureSampler"); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, particleTexture); glUniform1i(TextureID, 0); glUniform3f(CameraRight_worldspace_ID, cameraSide.x, cameraSide.y, cameraSide.z); glUniform3f(CameraUp_worldspace_ID, cameraVertical.x, cameraVertical.y, cameraVertical.z); glm::mat4 transformation = perspectiveMatrix * cameraMatrix; glUniformMatrix4fv(ViewProjMatrixID, 1, GL_FALSE, (float*)&transformation); // vertices glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0); // positions of particles' centers glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer); glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)0); // particles' colors glEnableVertexAttribArray(2); glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer); glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, (void*)0); } void renderParticles() { glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, ParticlesCount); glVertexAttribDivisor(0, 0); // particles vertices : always reuse the same 4 vertices -> 0 glVertexAttribDivisor(1, 1); // positions : one per quad (its center) -> 1 glVertexAttribDivisor(2, 1); // color : one per quad -> 1 glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, ParticlesCount); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); glDisableVertexAttribArray(2); } void Core::Particle::handleAllParticleSources(glm::vec3 cameraPos, GLuint programParticles, glm::vec3 cameraSide, glm::vec3 cameraVertical, glm::mat4 cameraMatrix, glm::mat4 perspectiveMatrix) { double currentTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f; double delta = currentTime - lastTime; lastTime = currentTime; for (ParticleSource particleSource : particleSources) { spawnParticles(delta, particleSource.pos, particleSource.amount, particleSource.spread); } if ((int)currentTime % 1 == 0) { particleSources.clear(); } simulateParticles(cameraPos, delta); updateParticles(); bindParticles(programParticles, cameraSide, cameraVertical, cameraMatrix, perspectiveMatrix); renderParticles(); } void Core::Particle::shutdownParticles() { delete[] g_particule_position_size_data; glDeleteBuffers(1, &particles_color_buffer); glDeleteBuffers(1, &particles_position_buffer); glDeleteBuffers(1, &billboard_vertex_buffer); }