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";

}