2 vaos -> 1 for update and 1 for rendering
4 vbos -> 2 position and 2 velocities
1 connection vbo
1 indices vbo
2 textures -> for two texture buffer objects attached to the position VBO.
VBO/VAO Setup
The code for setting this up is repetetive. This is how the VBOs and VAOs are setup.
// create buffer object
glGenVertexArrays(2, vaoUpdateID);
glGenVertexArrays(2, vaoRenderID);
glGenBuffers( 2, vboID_Pos);
glGenBuffers( 2, vboID_Vel);
glGenBuffers( 1, &vboID_Con);
glGenBuffers(1, &vboIndices);
glGenTextures(2, texPosID);
//set update vao
for(int i=0;i<2;i++) {
glBindVertexArray(vaoUpdateID[i]);
glBindBuffer( GL_ARRAY_BUFFER, vboID_Pos[i]);
glBufferData( GL_ARRAY_BUFFER, sizeof(position_mass_data), &(position_mass_data[0]), GL_DYNAMIC_COPY);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer( GL_ARRAY_BUFFER, vboID_Vel[i]);
glBufferData( GL_ARRAY_BUFFER, sizeof(velocity_data), &(velocity_data[0]), GL_DYNAMIC_COPY);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0,0);
glBindBuffer( GL_ARRAY_BUFFER, vboID_Con);
if(i==0)
glBufferData( GL_ARRAY_BUFFER, sizeof(connection_data), &(connection_data[0]), GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribIPointer(2, 4, GL_INT, 0,0);
}
//set render vao
for(int i=0;i<2;i++) {
glBindVertexArray(vaoRenderID[i]);
glBindBuffer( GL_ARRAY_BUFFER, vboID_Pos[i]);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndices);
if(i==0)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices[0], GL_STATIC_DRAW);
}
glBindVertexArray(0);
//setup texture buffers
for(int i=0;i<2;i++) {
glBindTexture( GL_TEXTURE_BUFFER, texPosID[i]);
glTexBuffer( GL_TEXTURE_BUFFER, GL_RGBA32F, vboID_Pos[i]);
}
Setting up transform feedback
For transform feedback, I have output two shader attributes out_position_mass and out_velocity as follows,
const char* varying_names[]={"out_position_mass", "out_velocity"};
glTransformFeedbackVaryings(massSpringShader.GetProgram(), 2, varying_names, GL_SEPARATE_ATTRIBS);
glLinkProgram(massSpringShader.GetProgram());
Transform feedback
The shader is first assigned and then the uniforms are sent to it. Finally the transform feedback buffer is bound to the position and velocity VBOs. I use basic ping pong technique to swap read and write buffers on each render.
//attach shader and pass uniforms
massSpringShader.Use();
glUniformMatrix4fv(massSpringShader("MVP"), 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
glUniform1f(massSpringShader("t"), 0.1f);
glUniform1f(massSpringShader("k"), k);
glUniform1f(massSpringShader("c"), c);
glUniform1f(massSpringShader("rest_length"), l);
//attach texture buffers
glActiveTexture( GL_TEXTURE0);
glBindTexture( GL_TEXTURE_BUFFER, texPosID[writeID]);
//attach the vao for updating the positions/velocities
glBindVertexArray( vaoUpdateID[writeID]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, vboID_Pos[readID]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1, vboID_Vel[readID]);
glEnable(GL_RASTERIZER_DISCARD); // disable rasterization
glBeginTransformFeedback(GL_POINTS);
glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);
glDrawArrays(GL_POINTS, 0, MAX_MASSES);
glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
glEndTransformFeedback();
glDisable(GL_RASTERIZER_DISCARD);
glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitives_written);
massSpringShader.UnUse();
Rendering of the mesh and masses
After the transform feedback, the rendering is carried out using the render VAO.
glBindVertexArray(vaoRenderID[writeID]);
renderShader.Use();
glUniformMatrix4fv(renderShader("MVP"), 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(indices[0]) , GL_UNSIGNED_SHORT,0);
renderShader.UnUse();
particleShader.Use();
glUniform1i(particleShader("selected_index"), selected_index);
glUniformMatrix4fv(particleShader("MV"), 1, GL_FALSE, transformPipeline.GetModelViewMatrix());
glUniformMatrix4fv(particleShader("MVP"), 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
//draw the masses last
glDrawArrays(GL_POINTS, 0, MAX_MASSES);
particleShader.UnUse();
glBindVertexArray( 0);
//swap the read/write buffers for next frame
int tmp = readID;
readID=writeID;
writeID = tmp;
Updating positions on modification
Handling update of positions when user selects a mass and uses arrow keys to move it is quite trivial. There are three ways to do it
1) Use glBufferSubData to put the new positions again. While this approach is the most naive, its a waste of GPU resources. This can be achieved by the following call. (4*total_masses floats to be copied again)
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(position_mass_data), position_mass_data);
2) Use glBufferSubData to only set the current position of the mass to the new value (4 floats to be copied again).
glBufferSubData(GL_ARRAY_BUFFER, selected_index*4*sizeof(float) , 4*sizeof(GLfloat), &position_mass_data[selected_index*4]);
3) Use glBufferSubData to only set the single (current) value i.e the y position of the mass to the new value (only 1 float to be copied again). This is the approach I use here,
switch(key) {
case GLUT_KEY_UP: position_mass_data[selected_index*4+1]+= 0.1f; break;
case GLUT_KEY_DOWN: position_mass_data[selected_index*4+1]-= 0.1f; break;
}
glBindVertexArray(vaoRenderID[readID]);
glBindBuffer(GL_ARRAY_BUFFER, vboID_Pos[writeID]);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(float)*(selected_index*4+1) , sizeof(GLfloat), &position_mass_data[selected_index*4+1]);
glBindVertexArray(0);
Thats it. You have the mass spring model ready. Extending it for volume should be trivial.
Here is a snapshot from the demo.
Here is the VDO. You may also watch it on youtube The video shows how the individual masses may be modified and the effect passes on to the neighbours. In addition, I adjust the resting length of springs to show the effect it has on the simulation.
An update on this post. The code implementing a simple cloth is part of the OpenGL Insights book chapter 17. The code can be obtained from https://github.com/OpenGLInsights/OpenGLInsightsCode