Creating particles with compute shaders in openFrameworks

In openFrameworks it's easy to create particles system with compute shaders, that is, particles physics is updates using GPU. So, you may manipulate millions particles realtime, compared with hundred thousand using CPU.

The example to start with is examples/gl/computeShaderParticlesExample.

It's based on declaring Particle structure - just like normal CPU particle definition with position, speed and color, and defining array
vector<Particle> particles;
which holds all particles.

But, the next big step is using ofBufferObject which maps this vector to GPU memory.

particlesBuffer.allocate(particles,GL_DYNAMIC_DRAW);
(About GL_DYNAMIC_DRAW - see details here)

particlesBuffer2 is similar and is used as previous particles state during computations, and is filled with command
particlesBuffer.copyTo(particlesBuffer2);
The position and color is used in ofVbo vbo object
vbo.setVertexBuffer(particlesBuffer,4,sizeof(Particle));
vbo.setColorBuffer(particlesBuffer,sizeof(Particle),sizeof(ofVec4f)*2);.
- this vbo is actually drawn. It's like ofMesh, but works directly with GPU buffers.

The non-trivial parts for using buffers is setting targets:
particlesBuffer.bindBase(GL_SHADER_STORAGE_BUFFER, 0);
particlesBuffer2.bindBase(GL_SHADER_STORAGE_BUFFER, 1);
GL_SHADER_STORAGE_BUFFER -  Read-write storage for shaders)

Inside shader, the buffer is used as the following (compute1.glsl):

struct Particle{
    vec4 pos;
    vec4 vel;
    vec4 color;
};

layout(std140, binding=0) buffer particle{
    Particle p[];
};

layout(std140, binding=1) buffer particleBack{
    Particle p2[];
};


Here is several important things: 
1.  std140 - it's specifier of aligning for buffers, see https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL) and https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159

"4. If the member is an array of scalars or vectors, the base alignment and arraystride are set to match the base alignment of a single array element, accordingto rules (1), (2), and (3), and rounded up to the base alignment of a vec4."
That it is, buffers used std140 are (must be) aligned by 16 bytes, and, Particle consists of three vec4 fields, so its aligned well relative to std140!

2. binding identifier  0 and 1 - corresponds to the last argument in bindBase calling:
particlesBuffer.bindBase(GL_SHADER_STORAGE_BUFFER, 0);

Computations

Now talk about computations. In main() function of the shader, 
particles are referenced as p and p2 - as declared in buffers, and current processed particle's index is gl_GlobalInvocationID.x.

To control computing process, see the shader' line
layout(local_size_x = 1024, local_size_y = 1, local_size_z = 1) in;

It means that one of the shader runs 1024 times, and computations are in 1D "work group", because sizes y and z are 1, and indeed we can use gl_GlobalInvocationID.x for referencing to computed particle. 
Also this line affects how we run shader in CPU code:
compute.dispatchCompute((particles.size() + 1024 -1 )/1024, 1, 1);
- that is, we specify that we want to run roughly speaking, particles.size()/1024 work groups.

Please, see https://www.khronos.org/opengl/wiki/Compute_Shader on the details on this.

Rendering

The particles are rendered just as point sprites:
//vbo.enableColors();  //using colors in vbo
//vbo.disableColors();  //using colors in vbo
ofSetColor(255,70);
glPointSize(5);
vbo.draw(GL_POINTS,0,particles.size());

To render particles more delicately, please see examples/gl/pointsAsTexturesExample
where used fragment shader for this.

Random particles generation

For particles generation, we need to keep for each instance a state, randomly initialized on CPU, and then update the state, using corresponding functions, see Random number generator on GPU

Comments

Popular posts from this blog

Computing ray origin and direction from Model View Projection matrices for raymarching

Forward and backward alpha blending for raymarching

Forward, Deferred and Raytracing rendering in openFrameworks and web