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.

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++) {
glBindBuffer( GL_ARRAY_BUFFER, vboID_Pos[i]);
glBufferData( GL_ARRAY_BUFFER, sizeof(position_mass_data), &(position_mass_data[0]), GL_DYNAMIC_COPY);
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);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0,0);

glBindBuffer( GL_ARRAY_BUFFER, vboID_Con);
glBufferData( GL_ARRAY_BUFFER, sizeof(connection_data), &(connection_data[0]), GL_STATIC_DRAW);

glVertexAttribIPointer(2, 4, GL_INT, 0,0);

//set render vao
for(int i=0;i<2;i++) {
glBindBuffer( GL_ARRAY_BUFFER, vboID_Pos[i]);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndices);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices[0], GL_STATIC_DRAW);

//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);

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
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
glDrawArrays(GL_POINTS, 0, MAX_MASSES);
glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitives_written);

Rendering of the mesh and masses
After the transform feedback, the rendering is carried out using the render VAO.

glUniformMatrix4fv(renderShader("MVP"), 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(indices[0]) , GL_UNSIGNED_SHORT,0);

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);
glBindVertexArray( 0);

//swap the read/write buffers for next frame
int tmp = readID;
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;
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]);

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

OpenGL 3.0 and above deprecated func. and their replacement

Here i am writing a quick reference list for deprecated functionality in OpenGL3.3 and its replacements. I will update this list regularly.

Deprecated functionalityReplacement in OpenGL3.3
Fixed function pipelineShaders
/glMatrixMode/glLoadMatrix/glMultMatrix etc.
Use either a custom matrix library or public libraries like glm.
glTexEnvfCombine textures in anyway using fragment shaders.
gluBuild2DMipmaps Use either GL_GENERATE_MIPMAP texture parameter or the glGenerateMipmap function. Set the max mipmap level and the base mipmap level using
glEnable(GL_POINT_SMOOTH)Use custom shader
glEnable(GL_TEXTURE_2D)No need. Ue glActiveTexture(GL_TEXTUREn) and bind the texture to the nth texture unit.
glVertex*/glColor*/glNormal*/glTexCoord*Use vbo and vao to maintain the vbo states. Drawing is done using glDraw[Arrays,Elements, RangeElements, InterleavedElements etc.] functions.
glPolygonMode(GL_FRONT, GL_LINE);glPolygonMode(GL_BACK, GL_LINE); In OpenGL 3.0 and above core profile, only the combined front and back face enumerant is valid, single enumerant GL_FRONT or GL_BACK raises INVALID_OPERATION. So the only valid call is glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

[to be contd.]

Point sprites as spheres in OpenGL3.3

Someone asked on opengl.org forums to generate sphere without any loop. It is possible to do so using particle sprites. The bulk of the stuff goes in the fragment shader which is as follows,

#version 330
out vec4 vFragColor;

uniform vec3 Color;
uniform vec3 lightDir;

void main(void)
// calculate normal from texture coordinates
vec3 N;
N.xy = gl_PointCoord* 2.0 - vec2(1.0);
float mag = dot(N.xy, N.xy);
if (mag > 1.0) discard; // kill pixels outside circle
N.z = sqrt(1.0-mag);

// calculate lighting
float diffuse = max(0.0, dot(lightDir, N));

vFragColor = vec4(Color,1) * diffuse;

which first gets the normal from the texture coordinates. Then, it does a dot product of the normal with the light vector. Adding specular component should be trivial. This gives the following output. You may download the source code from here
Point sprite as spheres

Adding in the specular term gives even better result. Have a look at this image.
Point sprite as spheres with specular

This image was generated using the following shader.

#version 330
out vec4 vFragColor;

uniform vec3 Color;
uniform vec3 lightDir;
float Ns = 250;
vec4 mat_specular=vec4(1);
vec4 light_specular=vec4(1);
void main(void)
// calculate normal from texture coordinates
vec3 N;
N.xy = gl_PointCoord* 2.0 - vec2(1.0);
float mag = dot(N.xy, N.xy);
if (mag > 1.0) discard; // kill pixels outside circle
N.z = sqrt(1.0-mag);

// calculate lighting
float diffuse = max(0.0, dot(lightDir, N));

vec3 eye = vec3 (0.0, 0.0, 1.0);
vec3 halfVector = normalize( eye + lightDir);
float spec = max( pow(dot(N,halfVector), Ns), 0.);
vec4 S = light_specular*mat_specular* spec;
vFragColor = vec4(Color,1) * diffuse + S;

The spheres rendered this way will not change their size when we zoom in and out. I will show in a later blog entry how to do that by setting the point size in the vertex shader.

Popular Posts

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