Tuesday, January 25, 2011

Mass spring system in OpenGL 3.3

Finally, I was able to code the mass spring shader using the vertex shader and info. given in Chapter 11 of OpenGL superbible fifth edition. The demo was missing from the opengl super bible 5th ed. svn as well as the book website. Basically, we need

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.
Mass sprin system OpenGL3.3


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

11 comments:

cyberalejo17 said...

great!

but... please, can you tell me how can I desing and write the code fot this simulation:

Image of the simulation.....
http://www.lomasdeterciopelo.co.cr/brutipedia/sites/default/files/image/ondas/masa_resorte.png

I want tbis, but whit motion.

MMMovania said...

Hi,
You can use the same thing only that on the shown image, you will only use a single spring. This should be trivial to achieve.

Nick said...

Excellent post,

Do you think that it's possible to implement a basic fluid solver with this method using vertex shaders?

MMMovania said...

Hi Nikos,
Yes I think it is possible to do a basic fluid sover using this approach. In fact, any iterative algorithm/solver should be easy to do using this approach.
I will see if I can do a fluid solver using this approach and post it here so stay connected.

Nick said...

Cool,
I'm looking forward to your post.

chuchorro said...

Hi men, i need please the code of this simulation, if you can send me to andres.rock80s@hotmail.com
thanks bro!!

chuchorro said...

please please bro! is extremly urgent is for a job at my college in colombia!! thanks!!!

Techer Renaud said...

thank you

MMMovania said...

You are welcome Techer.

Unknown said...
This comment has been removed by the author.
Unknown said...

Hi, great post. I'm newbie in OpenGL, and I'm trying to use spring mass-model for flags and I want to learn and use it. Can you send me the sourcecode of this please ? thanks.
mail: oguzhanyildizgame@gmail.com

Popular Posts

Copyright (C) 2011 - Movania Muhammad Mobeen. Awesome Inc. theme. Powered by Blogger.