I am working on a particle system using OpenGL 4.0 with C++.
Currently, a particle in my program consist of 12 float components:
[vec4 position, vec4 color, vec3 velocity, lifetime] = [xPos, yPos, zPos, wPos, r, g, b, a, xVel, yVel, zVel, lifetime]
My current approach is to populate one array (called vertexData) with all particles, such that the structure of the array will be: [particle, particle, particle, ...], where each particle has the layout presented above.
My plan is to simply provide this initial information to the vertex shader, and then let it calculate position offsets and color based on the initial information. I want most computation to be done on the GPU.
I want to pass data to the shader by putting all the data in a single buffer object and then use offsets to fetch the correct values to pass to the vertex shader.
The problem: I can't seem to get the right data. I feel like I have misunderstood the whole buffer offset thing, and that the program fetches random values from my data... I get working handles to the attributes of the shader, but the data is definitely not right. With the example Particle you will see in the first code block below, only the position seems right. The color is purple (should be white), the lifetime is 0 (should be 5). It's hard to debug shader code. I've just made a check that paints the dot another color if the lifetime isn't what it should be.
Do I need to arrange the data so it is like this: [(all particle positions), (all particle colors), (all particle velocities), (all particle lifetimes)]? (Like what is done here: http://arcsynthesis.org/gltut/Basics/Tut02%20Vertex%20Attributes.html )
Or perhaps I need to create a different buffer for each type of particle data, so one for location, one for color, etc?
Code: Now for some code of how I populate the data, create buffer object, render, and shader code. The array initalization and population is done like this:
const int PARTICLE_COUNT = 1;
const int PARTICLE_COMPONENTS_COUNT = 12;
...
GLfloat* vertexData;
...
void InitVertexData(){
vertexData = new GLfloat[PARTICLE_COUNT * PARTICLE_COMPONENTS_COUNT];
for (int i = 0; i < PARTICLE_COUNT; i++)
{
vertexData[i * PARTICLE_COMPONENTS_COUNT + 0] = 0.5f; //0: posX
vertexData[i * PARTICLE_COMPONENTS_COUNT + 1] = 0.5f; //1: posY
vertexData[i * PARTICLE_COMPONENTS_COUNT + 2] = 0.0f; //2: posZ
vertexData[i * PARTICLE_COMPONENTS_COUNT + 3] = 1.0f; //3: posW
vertexData[i * PARTICLE_COMPONENTS_COUNT + 4] = 1.0f; //4: red
vertexData[i * PARTICLE_COMPONENTS_COUNT + 5] = 1.0f; //5: green
vertexData[i * PARTICLE_COMPONENTS_COUNT + 6] = 1.0f; //6: blue
vertexData[i * PARTICLE_COMPONENTS_COUNT + 7] = 1.0f; //7: alpha
vertexData[i * PARTICLE_COMPONENTS_COUNT + 8] = 0.0f; //8: velX
vertexData[i * PARTICLE_COMPONENTS_COUNT + 9] = 0.0f; //9: velY
vertexData[i * PARTICLE_COMPONENTS_COUNT + 10] = 0.0f; //10: velZ
vertexData[i * PARTICLE_COMPONENTS_COUNT + 11] = 5.0f; //11: lifetime
}
}
Note: The values for the particle are just hardcoded for now. I haven't implemented the functions to calculate initial values.
Then, I generate one buffer which I want to hold all of the data for all particles. The following function is called once when initialising:
bool CreateBufferObject()
{
//VertexShader attribute variable handles.
GLint positionLocation = glGetAttribLocation(programId, "position");
GLint colorLocation = glGetAttribLocation(programId, "color");
GLint velocityLocation = glGetAttribLocation(programId, "velocity");
GLint lifetimeLocation = glGetAttribLocation(programId, "lifetime");
//Number of components (floats) in a particle element (such as position, color etc).
GLint positionComponentCount = 4; //posX, posY, posZ, posW
GLint colorComponentCount = 4; //r, g, b, a
GLint velocityComponentCount = 3; //velX, velY, velZ
GLint lifetimeComponentCount = 1; //lifetime (in seconds)
//Size (in bytes) of the vertexData array and the elements inside.
GLsizeiptr sizeofVertexDataArray = sizeof(GLfloat) * PARTICLE_COUNT * PARTICLE_COMPONENTS_COUNT; //4 * 2 * 12 = 96
GLsizeiptr sizeofPosition = sizeof(GLfloat) * positionComponentCount; //4*4 = 16
GLsizeiptr sizeofColor = sizeof(GLfloat) * colorComponentCount; //4*4 = 16
GLsizeiptr sizeofVelocity = sizeof(GLfloat) * velocityComponentCount; //4*3 = 12
GLsizeiptr sizeofLifetime = sizeof(GLfloat) * lifetimeComponentCount; //4*1 = 4
//Generate one buffer in GPU memory and get handle.
glGenBuffers(1, &vertexDataBufferId);
//Generate VAO, a descriptor of vertex data (not the actual object), and get handle.
glGenVertexArrays(1, &vaoId);
glBindVertexArray(vaoId);
//Bind the generated buffer to VAO as the current target.
glBindBuffer(GL_ARRAY_BUFFER, vertexDataBufferId);
//Copy the actual data from the vertexData array to GPU memory. TODO: Not sure what to pick instead of GL_STREAM_DRAW
glBufferData(GL_ARRAY_BUFFER, sizeofVertexDataArray, vertexData, GL_STREAM_DRAW);
//Guide Vertex Shader to where attributes are at in the memory, giving size and offset (in bytes) of the different elements of a particle.
glVertexAttribPointer(positionLocation, positionComponentCount, GL_FLOAT, GL_FALSE, sizeofPosition, 0); //offset 0
glVertexAttribPointer(colorLocation, colorComponentCount, GL_FLOAT, GL_FALSE, sizeofColor, (void*)positionComponentCount); //ofset 4
glVertexAttribPointer(velocityLocation, velocityComponentCount, GL_FLOAT, GL_FALSE, sizeofVelocity, (void*)(positionComponentCount + colorComponentCount)); //offset 8
glVertexAttribPointer(lifetimeLocation, lifetimeComponentCount, GL_FLOAT, GL_FALSE, sizeofLifetime, (void*)(positionComponentCount + colorComponentCount + velocityComponentCount)); //offset 11
glEnableVertexAttribArray(positionLocation);
glEnableVertexAttribArray(colorLocation);
glEnableVertexAttribArray(velocityLocation);
glEnableVertexAttribArray(lifetimeLocation);
//Error checks and debug prints omitted
}
My understanding is the following: In glBufferData, I specify the size of the entire data set (all particles), and copy the data to the GPU memory.
With glVertexAttribPointer, I specify with which offsets and counts OpenGL should use to fill the attribute variables of the vertex shader.
In my Render function, I then call (this is where I also think I might be going wrong):
void Render()
{
...
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GLuint elapsedTimeUniformLoc = glGetUniformLocation(programId, "time");
glUniform1f(elapsedTimeUniformLoc, glutGet(GLUT_ELAPSED_TIME) / 1000.0f); //Divide by 1000 to get time in seconds.
glDrawArrays(GL_POINTS, 0, PARTICLE_COUNT)
glutSwapBuffers();
glutPostRedisplay();
}
Since a point consists of 1 vertex, I ask it to draw as many points as PARTICLE_COUNT is big (currently 1)
Shaders
const GLchar* VertexShader2 =
{
"#version 400\n"\
"uniform float time;\n"\
"in vec4 position;\n"\
"in vec4 color;\n"\
"in vec3 velocity;\n"\
"in float lifetime;\n"\
"out vec4 myColor;\n"\
"void main()\n"\
"{\n"\
" float dummy = velocity.x;\n"
" float timeScale = 0.1f;\n"\
" float currentAge = mod(time, lifetime);\n"\
" vec4 accumulatedOffset = vec4(timeScale*currentAge, 0.0f, 0.0f, 0.0f);\n"
" gl_Position = position + accumulatedOffset;\n"\
" if(lifetime ==0) { color.y = 1.0f; }\n"\
" //color.w = 1.0f - ((1.0f/lifetime) * currentAge)\n"\
" color.w = 1.0f - (0.2f * currentAge);\n"\
" myColor = color;\n"\
"}\n"\
};
const GLchar* FragmentShader =
{
"#version 400\n"\
"in vec4 myColor;\n"\
"out vec4 outColor;\n"\
"void main()\n"\
"{\n"\
" outColor = myColor;\n"\
"}\n"\
};
Note: Since values for velocity isn't correct, I use the dummy variable just to make sure that the shader compiler doesn't discard the attribute.
If you need to see any more code, I'll update the post. I hope I made my problem and my situation clear.
Response to @Ben Voight Okay, so from your comment I take that the stride argument means the offset in the data for the next value for this attribute. So it has to be
PARTICLE_COMPONENTS_COUNT * sizeof(GLfloat)
for all glVertexAttribPointer
It still gets me the wrong data, however. With a particle consisting of
[-0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 5.0],
I expect a position of (-0.5, 0.5, 0.5), a color of white and a lifetime of 5. (Disregarding velocity like before)
I have my vertex shader change the color if lifetime == 0, and it reports that lifetime is indeed 0. I don't understand where it gets 0 from, it's not even in the data. The color is also wrong. Only the position seems to be correct. In fact, it works exactly like before.
Solved Turns out I passed the wrong values for stride and offset in glVertexAttribPointer.
Possibly duplicate of: Opengl Vertex attribute stride