5. Particle System
Example Particle System simulating 1024 x 1024 = 1.048.576 particles. A Compute Shader is used to simulate the particles.
Compute Shader:
#version 450 core
layout (local_size_x = 1024, local_size_y = 1, local_size_z = 1) in;
struct Particle {
vec4 Position;
vec4 Velocity;
vec4 Color;
};
layout (std430, binding = 1) buffer InputBlock { Particle in_particles[]; };
layout (std430, binding = 2) buffer OutputBlock { Particle out_particles[]; };
uniform float timestep = 0.016f;
void main() {
uint index = gl_GlobalInvocationID.x;
vec4 position = in_particles[index].Position + in_particles[index].Velocity * timestep;
vec4 velocity = in_particles[index].Velocity;
// example: reflect particles at a sphere of radius 3.0f
float distance = length(position.xyz);
if (distance > 3.0f) {
position *= 3.0f / distance;
velocity = vec4(reflect(velocity.xyz, -position.xyz / distance), 0);
}
out_particles[index].Position = position;
out_particles[index].Velocity = velocity;
}
In this example we subdivide the "local size" of each "workgroup":
layout (local_size_x = 1024, local_size_y = 1, local_size_z = 1) in;
Complete Source Code:
Main.cpp
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <ctime>
#include "Shader.h"
using namespace std;
using namespace glm;
#define MAX_WORKGROUPS 1024
#define MAX_PARTICLES 1024 * MAX_WORKGROUPS
// main functions
void Initialize();
void Render();
void CleanUp();
int RandomValue(int min, int max);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void cursor_position_callback(GLFWwindow* window, double xpos, double ypos);
GLFWwindow* window = nullptr;
unsigned int program_compute = 0;
unsigned int computeshader = 0;
unsigned int program_render = 0;
unsigned int vertexshader = 0;
unsigned int fragmentshader = 0;
unsigned int vertexarrays[2] = { 0 };
unsigned int vertexbuffers[2] = { 0 };
string vertexshader_source = {
"#version 450 core\n"
"in layout (location = 0) vec4 in_position;"
"in layout (location = 1) vec4 in_color;"
"uniform mat4 MVP = mat4(1);"
"out vec4 color;"
"void main() {"
"gl_Position = MVP * vec4(in_position.xyz, 1);"
"color = in_color;"
"}"
};
string fragmentshader_source = {
"#version 450 core\n"
"in vec4 color;"
"out layout (location = 0) vec4 out_color;"
"void main() {"
"out_color = color;"
"}"
};
string computeshader_source = {
"#version 450 core\n"
"layout (local_size_x = " + to_string(MAX_WORKGROUPS) + ", local_size_y = 1, local_size_z = 1) in;"
"struct Particle { vec4 Position; vec4 Velocity; vec4 Color; };"
"layout (std430, binding = 1) buffer InputBlock { Particle in_particles[]; };"
"layout (std430, binding = 2) buffer OutputBlock { Particle out_particles[]; };"
"uniform float timestep = 0.016f;"
"void main() {"
"uint index = gl_GlobalInvocationID.x;"
"vec4 position = in_particles[index].Position + in_particles[index].Velocity * timestep;"
"vec4 velocity = in_particles[index].Velocity;"
// example: reflect particles at a sphere of radius 3.0f
"float distance = length(position.xyz);"
"if (distance > 3.0f) {"
"position *= 3.0f / distance;"
"velocity = vec4(reflect(velocity.xyz, -position.xyz / distance), 0);"
"}"
"out_particles[index].Position = position;"
"out_particles[index].Velocity = velocity;"
"}"
};
struct Particle {
vec4 Position;
vec4 Velocity;
vec4 Color;
};
struct {
vec3 Position{ 0, 0, 5 };
vec3 Forward{ 0, 0, -1 };
vec3 Up{ 0, 1, 0 };
} Camera;
int main(void)
{
/* Initialize the library */
if (!glfwInit())
return -1;
/* Create a windowed mode window and its OpenGL context */
window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
/* Make the window's context current */
glfwMakeContextCurrent(window);
/* Set callback functions */
glfwSetScrollCallback(window, scroll_callback);
glfwSetCursorPosCallback(window, cursor_position_callback);
/* Initialize GLEW */
if (glewInit() != GLEW_OK)
{
glfwTerminate();
return -1;
}
Initialize();
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
/* Render here */
Render();
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
CleanUp();
glfwTerminate();
return 0;
}
void Initialize()
{
// settings
//------------------------------------------------------------------------------------------------------------------------
glEnable(GL_DEPTH_TEST);
//------------------------------------------------------------------------------------------------------------------------
// create all objects
//------------------------------------------------------------------------------------------------------------------------
program_compute = glCreateProgram();
program_render = glCreateProgram();
vertexshader = glCreateShader(GL_VERTEX_SHADER);
fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);
computeshader = glCreateShader(GL_COMPUTE_SHADER);
glGenVertexArrays(2, vertexarrays);
glGenBuffers(2, vertexbuffers);
//------------------------------------------------------------------------------------------------------------------------
// setup program
//------------------------------------------------------------------------------------------------------------------------
CompileShader(computeshader, computeshader_source);
LinkProgram(program_compute, { computeshader });
CompileShader(vertexshader, vertexshader_source);
CompileShader(fragmentshader, fragmentshader_source);
LinkProgram(program_render, { vertexshader, fragmentshader });
//------------------------------------------------------------------------------------------------------------------------
// setup vertex arrays
//------------------------------------------------------------------------------------------------------------------------
for (unsigned int i = 0; i < 2; i++)
{
glBindVertexArray(vertexarrays[i]);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffers[i]);
glVertexAttribPointer(0, 4, GL_FLOAT, false, sizeof(Particle), (void*)(0));
glVertexAttribPointer(1, 4, GL_FLOAT, false, sizeof(Particle), (void*)(sizeof(vec4) + sizeof(vec4)));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindVertexArray(0);
}
//------------------------------------------------------------------------------------------------------------------------
// setup vertex buffers
//------------------------------------------------------------------------------------------------------------------------
vector<Particle> particles(MAX_PARTICLES);
for (auto& particle : particles)
{
particle.Position.x = RandomValue(-100, 100) / 100.0f;
particle.Position.y = RandomValue(-100, 100) / 100.0f;
particle.Position.z = RandomValue(-100, 100) / 100.0f;
particle.Velocity.x = RandomValue(-100, 100) / 100.0f;
particle.Velocity.y = RandomValue(-100, 100) / 100.0f;
particle.Velocity.z = RandomValue(-100, 100) / 100.0f;
int random = RandomValue(0, 6);
if (random == 1) particle.Color = vec4(1, 0, 0, 1);
else if (random == 2) particle.Color = vec4(0, 1, 0, 1);
else if (random == 3) particle.Color = vec4(0, 0, 1, 1);
else if (random == 4) particle.Color = vec4(0, 1, 1, 1);
else if (random == 5) particle.Color = vec4(1, 0, 1, 1);
else if (random == 6) particle.Color = vec4(1, 1, 0, 1);
else particle.Color = vec4(0.5f, 0.5f, 0.5f, 1);
}
for (unsigned int i = 0; i < 2; i++)
{
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffers[i]);
glBufferData(GL_ARRAY_BUFFER, sizeof(Particle) * particles.size(), particles.data(), GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
//------------------------------------------------------------------------------------------------------------------------
}
void Render()
{
// simulate particles
//------------------------------------------------------------------------------------------------------------------------
static unsigned int index = 0;
index = !index;
// swap vertex buffers
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, vertexbuffers[1 - index]);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, vertexbuffers[index]);
glUseProgram(program_compute);
glDispatchCompute(MAX_WORKGROUPS, 1, 1);
glUseProgram(0);
glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT);
//------------------------------------------------------------------------------------------------------------------------
// draw particles
//------------------------------------------------------------------------------------------------------------------------
// set camera
mat4 MVP =
perspective(radians(45.0f), 1.33f, 0.1f, 100.0f) *
lookAt(Camera.Position, Camera.Position + Camera.Forward, Camera.Up);
int location = glGetUniformLocation(program_render, "MVP");
glProgramUniformMatrix4fv(program_render, location, 1, false, value_ptr(MVP));
glClearColor(0.3f, 0.4f, 0.7f, 0); // background = skyblue
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(program_render);
glBindVertexArray(vertexarrays[index]);
glDrawArrays(GL_POINTS, 0, MAX_PARTICLES);
glBindVertexArray(0);
glUseProgram(0);
//------------------------------------------------------------------------------------------------------------------------
}
void CleanUp()
{
glDeleteProgram(program_compute);
glDeleteProgram(program_render);
glDeleteShader(vertexshader);
glDeleteShader(fragmentshader);
glDeleteShader(computeshader);
glDeleteVertexArrays(2, vertexarrays);
glDeleteBuffers(2, vertexbuffers);
}
int RandomValue(int min, int max)
{
static bool init(false);
if (!init) {
init = true;
srand(time(NULL));
}
if (min > max) std::swap(min, max);
if (min == max) return min;
return min + rand() % (max - min);
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
// scrolled up --> move forward
if (yoffset > 0)
Camera.Position += Camera.Forward * 0.5f;
// scrolled down --> move back
if (yoffset < 0)
Camera.Position -= Camera.Forward * 0.5f;
}
void cursor_position_callback(GLFWwindow* window, double xpos, double ypos)
{
static double xpos_old = xpos, ypos_old = ypos;
// cursor position difference
double xpos_delta = xpos - xpos_old;
double ypos_delta = ypos - ypos_old;
// rotate camera
float angle = xpos_delta * -0.03;
Camera.Forward = rotate(Camera.Forward, angle, Camera.Up);
xpos_old = xpos;
ypos_old = ypos;
}
Shader.h
#pragma once
#include <string>
#include <list>
// shader functions
std::string LoadTextFile(const std::string& filepath);
std::string ShaderTypeName(unsigned int shader);
bool CompileShader(unsigned int shader, const std::string& sourcecode);
std::string ShaderInfoLog(unsigned int shader);
bool LinkProgram(unsigned int program, const std::list<unsigned int>& shaderlist);
std::string ProgramInfoLog(unsigned int program);
Shader.cpp
#include "Shader.h"
#include <iostream>
#include <fstream>
#include <GL/glew.h>
std::string LoadTextFile(const std::string & filepath)
{
std::string result(""), line;
std::fstream f(filepath, std::ios::in);
while (f.good())
{
std::getline(f, line);
result += line + '\n';
}
return result;
}
std::string ShaderTypeName(unsigned int shader)
{
if (glIsShader(shader))
{
int type;
glGetShaderiv(shader, GL_SHADER_TYPE, &type);
if (type == GL_VERTEX_SHADER)
return "Vertex Shader";
if (type == GL_TESS_CONTROL_SHADER)
return "Tessellation Control Shader";
if (type == GL_TESS_EVALUATION_SHADER)
return "Tessellation Evaluation Shader";
if (type == GL_GEOMETRY_SHADER)
return "Geometry Shader";
if (type == GL_FRAGMENT_SHADER)
return "Fragment Shader";
if (type == GL_COMPUTE_SHADER)
return "Compute Shader";
}
return "invalid shader";
}
bool CompileShader(unsigned int shader, const std::string& sourcecode)
{
if (!glIsShader(shader))
{
std::cout << "ERROR: shader compilation failed, no valid shader specified" << std::endl;
return false;
}
if (sourcecode.empty())
{
std::cout << "ERROR: shader compilation failed, no source code specified (" << ShaderTypeName(shader) << ")" << std::endl;
return false;
}
// array of source code components
const char* sourcearray[] = { sourcecode.c_str() };
// set source code
glShaderSource(shader, 1, sourcearray, NULL);
// compile shaders
glCompileShader(shader);
// check compile status
int status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
// successfully compiled shader
if (status == GL_TRUE)
return true;
// show compile errors
std::cout << "ERROR: shader compilation failed (" << ShaderTypeName(shader) << ")" << std::endl << ShaderInfoLog(shader) << std::endl;
return false;
}
std::string ShaderInfoLog(unsigned int shader)
{
if (glIsShader(shader))
{
int logsize;
char infolog[1024] = { 0 };
glGetShaderInfoLog(shader, 1024, &logsize, infolog);
return std::string(infolog);
}
return "invalid shader";
}
bool LinkProgram(unsigned int program, const std::list<unsigned int>& shaderlist)
{
if (!glIsProgram(program))
{
std::cout << "ERROR: shader linking failed, no valid program specified" << std::endl;
return false;
}
// attach all shaders to the program
for (auto& shader : shaderlist)
{
if (glIsShader(shader))
glAttachShader(program, shader);
}
// link program
glLinkProgram(program);
// detach all shaders again
for (auto& shader : shaderlist)
{
if (glIsShader(shader))
glDetachShader(program, shader);
}
int status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
// successfully linked program
if (status == GL_TRUE)
return true;
// show link errors
std::cout << "ERROR: shader linking failed" << std::endl << ProgramInfoLog(program) << std::endl;
return false;
}
std::string ProgramInfoLog(unsigned int program)
{
if (glIsProgram(program))
{
int logsize;
char infolog[1024] = { 0 };
glGetProgramInfoLog(program, 1024, &logsize, infolog);
return std::string(infolog);
}
return "invalid program";
}