Modeling cloth using transform feedback

In this recipe we will use the transform feedback mechanism of the modern GPU to model cloth. Transform feedback is a special mode of modern GPU in which the vertex shader can directly output to a buffer object. This allows developers to do complex computations without affecting the rest of the rendering pipeline. We will elaborate how to use this mechanism to simulate cloth entirely on the GPU.

From the implementation point of view in modern OpenGL, transform feedback exists as an OpenGL object similar to textures. Working with transform feedback object requires two steps: first, generation of transform feedback with specification of shader outputs, and second, usage of the transform feedback for simulation and rendering. We generate it by calling the glGetTransformFeedbacks function and passing it the number of objects and the variable to store the returned IDs. After the object is created, it is bound to the current OpenGL context by calling glBindTransformFeedback, and its only parameter is the ID of the transform feedback object we are interested to bind.

Next, we need to register the vertex attributes that we want to record in a transform feedback buffer. This is done through the glTransformFeedbackVaryings function. The parameters this function requires are in the following order: the shader program object, the number of outputs from the shader, the names of the attributes, and the recording mode. Recording mode can be either GL_INTERLEAVED_ATTRIBS (which means that the attributes will all be stored in a single interleaved buffer object) or GL_SEPARATE_ATTRIBS (which means each attribute will be stored in its own buffer object). Note that the shader program has to be relinked after the shader output varyings are specified.

We also have to set up our buffer objects that are going to store the attributes' output through transform feedback. At the rendering stage, we first set up our shader and the required uniforms. Then, we bind out vertex array objects storing out buffer object binding. Next, we bind the buffer object for transform feedback by calling the glBindBufferBase function. The first parameter is the index and the second parameter is the buffer object ID, which will store the shader output attribute. We can bind as many objects as we need, but the total calls to this function must be at least equal to the total output attributes from the vertex shader. Once the buffers are bound, we can initiate transform feedback by issuing a call to glBeginTransformFeedback and the parameter to this function is the output primitive type. We then issue our glDraw* call and then call glEndTransformFeedback.

Tip

OpenGL 4.0 and above provide a very convenient function, glDrawTransformFeedback. We just give it out primitive type and it automatically renders our primitives based on the total number of outputs from the vertex shader. In addition, OpenGL 4.0 provides the ability to pause/resume the transform feedback object as well as outputting to multiple transform feedback streams.

For the cloth simulation implementation using transform feedback, this is how we proceed. We store the current and previous position of the cloth vertices into a pair of buffer objects. To have convenient access to the buffer objects, we store these into a pair of vertex array objects. Then in order to deform the cloth, we run a vertex shader that inputs the current and previous positions from the buffer objects. In the vertex shader, the internal and external forces are calculated for each pair of cloth vertices and then acceleration is calculated. Using Verlet integration, the new vertex position is obtained. The new and previous positions are output from the vertex shader, so they are written out to the attached transform feedback buffers. Since we have a pair of vertex array objects, we ping pong between the two. This process is continued and the simulation proceeds forward.

The whole process is well summarized by the following figure:

Modeling cloth using transform feedback

More details of the inner workings of this method are detailed in the reference in the See also section.

Getting ready

The code for this recipe is contained in the Chapter8/TransformfeedbackCloth folder.

How to do it…

Let us start the recipe by following these simple steps:

  1. Generate the geometry and topology for a piece of cloth by creating a set of points and their connectivity. Bind this data to a buffer object. The vectors X and X_last store the current and last position respectively, and the vector F stores the force for each vertex:
       
    vector<GLushort> indices; 
    vector<glm::vec4> X;  
    vector<glm::vec4> X_last; 
    vector<glm::vec3> F;
    
    indices.resize( numX*numY*2*3);  
       X.resize(total_points);
       X_last.resize(total_points); 
       F.resize(total_points);
       for(int j=0;j<=numY;j++) {     
          for(int i=0;i<=numX;i++) {   
             X[count] = glm::vec4( ((float(i)/(u-1)) *2-1)* hsize, 
             sizeX+1, ((float(j)/(v-1) )* sizeY),1);
             X_last[count] = X[count];
          count++;
          }
       } 
       GLushort* id=&indices[0];
       for (int i = 0; i < numY; i++) {        
          for (int j = 0; j < numX; j++) {            
             int i0 = i * (numX+1) + j;            
             int i1 = i0 + 1;            
             int i2 = i0 + (numX+1);            
             int i3 = i2 + 1;            
             if ((j+i)%2) {                
                *id++ = i0; *id++ = i2; *id++ = i1;                
                *id++ = i1; *id++ = i2; *id++ = i3;            
             } else {                
                *id++ = i0; *id++ = i2; *id++ = i3;                
                *id++ = i0; *id++ = i3; *id++ = i1;            
             }
          }
        }
        glGenVertexArrays(1, &clothVAOID);
       glGenBuffers (1, &clothVBOVerticesID);
       glGenBuffers (1, &clothVBOIndicesID);
       glBindVertexArray(clothVAOID);
       glBindBuffer (GL_ARRAY_BUFFER, clothVBOVerticesID);
       glBufferData (GL_ARRAY_BUFFER, sizeof(float)*4*X.size(),  
        &X[0].x, GL_STATIC_DRAW);
       glEnableVertexAttribArray(0);
       glVertexAttribPointer (0, 4, GL_FLOAT, GL_FALSE,0,0);
       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, clothVBOIndicesID);
       glBufferData(GL_ELEMENT_ARRAY_BUFFER, 
        sizeof(GLushort)*indices.size(), &indices[0], GL_STATIC_DRAW);
       glBindVertexArray(0);
  2. Create two pairs of vertex array objects (VAO), one pair for rendering and another pair for update of points. Bind two buffer objects (containing current positions and previous positions) to the update VAO, and one buffer object (containing current positions) to the render VAO. Also attach an element array buffer for geometry indices. Set the buffer object usage parameter as GL_DYNAMIC_COPY). This usage parameter hints to the GPU that the contents of the buffer object will be frequently changed, and it will be read in OpenGL or used as a source for GL commands:
       glGenVertexArrays(2, vaoUpdateID);
       glGenVertexArrays(2, vaoRenderID);
       glGenBuffers( 2, vboID_Pos);
       glGenBuffers( 2, vboID_PrePos);
      for(int i=0;i<2;i++) {
        glBindVertexArray(vaoUpdateID[i]);
        glBindBuffer( GL_ARRAY_BUFFER, vboID_Pos[i]);
        glBufferData( GL_ARRAY_BUFFER, X.size()* sizeof(glm::vec4), 
           &(X[0].x), GL_DYNAMIC_COPY);    
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0,  4, GL_FLOAT, GL_FALSE, 0, 0);
         glBindBuffer( GL_ARRAY_BUFFER, vboID_PrePos[i]);
        glBufferData( GL_ARRAY_BUFFER,  
          X_last.size()*sizeof(glm::vec4), &(X_last[0].x),  
          GL_DYNAMIC_COPY);  
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1,  4, GL_FLOAT, GL_FALSE, 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,    
          indices.size()*sizeof(GLushort), &indices[0], 
          GL_STATIC_DRAW);
      }
  3. For ease of access in the vertex shader, bind the current and previous position buffer objects to a set of buffer textures. The buffer textures are one dimensional textures that are created like normal OpenGL textures using the glGenTextures call, but they are bound to the GL_TEXTURE_BUFFER target. They provide read access to the entire buffer object memory in the vertex shader. The data is accessed in the vertex shader using the texelFetchBuffer function:
       for(int i=0;i<2;i++) {
          glBindTexture( GL_TEXTURE_BUFFER, texPosID[i]);
          glTexBuffer( GL_TEXTURE_BUFFER, GL_RGBA32F, vboID_Pos[i]);
          glBindTexture( GL_TEXTURE_BUFFER, texPrePosID[i]);
          glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, vboID_PrePos[i]);
       }
  4. Generate a transform feedback object and pass the attribute names that will be output from our deformation vertex shader. Make sure to relink the program.
      glGenTransformFeedbacks(1, &tfID);
      glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tfID); 
      const char* varying_names[]={"out_position_mass", 
       "out_prev_position"};  
      glTransformFeedbackVaryings(massSpringShader.GetProgram(), 2, 
       varying_names, GL_SEPARATE_ATTRIBS);    
      glLinkProgram(massSpringShader.GetProgram());
  5. In the rendering function, bind the cloth deformation shader (Chapter8/TransformFeedbackCloth/shaders/Spring.vert) and then run a loop. In each loop iteration, bind the texture buffers, and then bind the update vertex array object. At the same time, bind the previous buffer objects as the transform feedback buffers. These will store the output from the vertex shader. Disable the rasterizer, begin the transform feedback mode, and then draw the entire set of cloth vertices. Use the ping pong approach to swap the read/write pathways:
    massSpringShader.Use(); 
    glUniformMatrix4fv(massSpringShader("MVP"), 1, GL_FALSE, 
    glm::value_ptr(mMVP));    
    for(int i=0;i<NUM_ITER;i++) {
        glActiveTexture( GL_TEXTURE0);
        glBindTexture( GL_TEXTURE_BUFFER, texPosID[writeID]);
        glActiveTexture( GL_TEXTURE1);
        glBindTexture( GL_TEXTURE_BUFFER, texPrePosID[writeID]);
        glBindVertexArray( vaoUpdateID[writeID]);
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
        vboID_Pos[readID]);
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1,
        vboID_PrePos[readID]);
        glEnable(GL_RASTERIZER_DISCARD);    // disable rasrization
        glBeginQuery(GL_TIME_ELAPSED,t_query);
        glBeginTransformFeedback(GL_POINTS);
        glDrawArrays(GL_POINTS, 0, total_points);
        glEndTransformFeedback();
        glEndQuery(GL_TIME_ELAPSED);
        glFlush();
        glDisable(GL_RASTERIZER_DISCARD);
        int tmp = readID;
        readID=writeID;
        writeID = tmp;
    }
    glGetQueryObjectui64v(t_query, GL_QUERY_RESULT, &elapsed_time);     
    delta_time = elapsed_time / 1000000.0f;  
    massSpringShader.UnUse();
  6. After the loop is terminated, bind the render VAO that renders the cloth geometry and vertices:
       glBindVertexArray(vaoRenderID[writeID]);
       glDisable(GL_DEPTH_TEST);
       renderShader.Use();
       glUniformMatrix4fv(renderShader("MVP"), 1, GL_FALSE,    
       glm::value_ptr(mMVP));
       glDrawElements(GL_TRIANGLES, indices.size(), 
       GL_UNSIGNED_SHORT,0);
       renderShader.UnUse();
       glEnable(GL_DEPTH_TEST);
       if(bDisplayMasses) {
          particleShader.Use();
          glUniform1i(particleShader("selected_index"),   
          selected_index);
          glUniformMatrix4fv(particleShader("MV"), 1, GL_FALSE,    
          glm::value_ptr(mMV));  
          glUniformMatrix4fv(particleShader("MVP"), 1, GL_FALSE, 
          glm::value_ptr(mMVP));
          glDrawArrays(GL_POINTS, 0, total_points);    
          particleShader.UnUse();
       }
       glBindVertexArray( 0);
  7. In the vertex shader, obtain the current and previous position of the cloth vertex. If the vertex is a pinned vertex, set its mass to 0 so it would not be simulated; otherwise, add an external force based on gravity. Next loop through all neighbors of the current vertex by looking up the texture buffer and estimate the internal force:
       float m = position_mass.w;
       vec3 pos = position_mass.xyz;
       vec3 pos_old = prev_position.xyz;  
       vec3 vel = (pos - pos_old) / dt;
       float ks=0, kd=0;
       int index = gl_VertexID;
       int ix = index % texsize_x;
       int iy = index / texsize_x;
       if(index ==0 || index == (texsize_x-1))   
          m = 0;
       vec3 F = gravity*m + (DEFAULT_DAMPING*vel);
       for(int k=0;k<12;k++) {
          ivec2 coord = getNextNeighbor(k, ks, kd);
          int j = coord.x;
          int i = coord.y;    
          if (((iy + i) < 0) || ((iy + i) > (texsize_y-1)))
             continue;
          if (((ix + j) < 0) || ((ix + j) > (texsize_x-1)))
             continue;
          int index_neigh = (iy + i) * texsize_x + ix + j;
          vec3 p2 = texelFetchBuffer(tex_position_mass,   
          index_neigh).xyz;
          vec3 p2_last = texelFetchBuffer(tex_prev_position_mass,  
          index_neigh).xyz;
          vec2 coord_neigh = vec2(ix + j, iy + i)*step;
          float rest_length = length(coord*inv_cloth_size);
          vec3 v2 = (p2- p2_last)/dt;
          vec3 deltaP = pos - p2;  
          vec3 deltaV = vel - v2;   
          float dist = length(deltaP);
          float   leftTerm = -ks * (dist-rest_length);
          float  rightTerm = kd * (dot(deltaV, deltaP)/dist);    
          vec3 springForce = (leftTerm + rightTerm)* 
          normalize(deltaP);
          F +=  springForce;  
       }
  8. Using the combined force, calculate the acceleration and then estimate the new position using Verlet integration. Output the appropriate attribute from the shader:
      vec3 acc = vec3(0);
      if(m!=0)
         acc = F/m; 
      vec3 tmp = pos; 
      pos = pos * 2.0 - pos_old + acc* dt * dt;
      pos_old = tmp; 
      pos.y=max(0, pos.y); 
      out_position_mass = vec4(pos, m);  
      out_prev_position = vec4(pos_old,m);        
      gl_Position = MVP*vec4(pos, 1);

How it works…

There are two parts of this recipe, the generation of geometry and identifying output attributes for transform feedback buffers. We first generate the cloth geometry and then associate our buffer objects. To enable easier access of current and previous positions, we bind the position buffer objects as texture buffers.

To enable deformation, we first bind our deformation shader and the update VAO. Next, we specify the transform feedback buffers that receive the output from the vertex shader. We disable the rasterizer to prevent the execution of the rest of the pipeline. Next, we begin the transform feedback mode, render our vertices, and then end the transform feedback mode. This invokes one step of the integration. To enable more steps, we use a ping pong strategy by binding the currently written buffer object as the read point for the next iteration.

The actual deformation is carried out in the vertex shader (Chapter8/TransformFeedbackCloth/shaders/Spring.vert). We first determine the current and previous positions. The velocity is then determined. The current vertex ID (gl_VertexID) is used to determine the linear index of the current vertex. This is a unique index of each vertex and can be used by a vertex shader. We use it here to determine if the current vertex is a pinned vertex. If so, the mass of 0 is assigned to it which makes this vertex immovable:

float m = position_mass.w;
vec3 pos = position_mass.xyz;
vec3 pos_old = prev_position.xyz;  
vec3 vel = (pos - pos_old) / dt;
float ks=0, kd=0;
int index = gl_VertexID;
int ix = index % texsize_x;
int iy = index / texsize_x;
if(index ==0 || index == (texsize_x-1))   
   m = 0;

Next, the acceleration due to gravity and velocity damping force is applied. After this, a loop is run which basically loops through all of the neighbors of the current vertex and estimates the net internal (spring) force. This force is then added to the combined force for the current vertex:

vec3 F = gravity*m + (DEFAULT_DAMPING*vel);
  
for(int k=0;k<12;k++) {
  ivec2 coord = getNextNeighbor(k, ks, kd);
  int j = coord.x;
  int i = coord.y;    
  if (((iy + i) < 0) || ((iy + i) > (texsize_y-1)))
    continue;
  if (((ix + j) < 0) || ((ix + j) > (texsize_x-1)))
    continue;
  int index_neigh = (iy + i) * texsize_x + ix + j;
  vec3 p2 = texelFetchBuffer(tex_position_mass, 
  index_neigh).xyz;
  vec3 p2_last = texelFetchBuffer(tex_prev_position_mass,   
  index_neigh).xyz;
  vec2 coord_neigh = vec2(ix + j, iy + i)*step;
  float rest_length = length(coord*inv_cloth_size);
  vec3 v2 = (p2- p2_last)/dt;
  vec3 deltaP = pos - p2;  
  vec3 deltaV = vel - v2;   
  float dist = length(deltaP);
  float   leftTerm = -ks * (dist-rest_length);
  float  rightTerm = kd * (dot(deltaV, deltaP)/dist);    
  vec3 springForce = (leftTerm + rightTerm)* normalize(deltaP);
  F +=  springForce;  
}

From the net force, the acceleration is first obtained and then the new position is obtained using Verlet integration. Finally, the collision with the ground plane is determined by looking at the Y value. We end the shader by outputting the output attributes (out_position and out_prev_position), which are then stored into the buffer objects bound as the transform feedback buffers:

  vec3 acc = vec3(0);
  if(m!=0)
     acc = F/m;  
  vec3 tmp = pos; 
  pos = pos * 2.0 - pos_old + acc* dt * dt;
  pos_old = tmp; 
  pos.y=max(0, pos.y);  
  out_position_mass = vec4(pos, m);  
  out_prev_position = vec4(pos_old,m);        
  gl_Position = MVP*vec4(pos, 1);

The shader, along with the transform feedback mechanism, proceeds to deform all of the cloth vertices and in the end, we get the cloth vertices deformed.

There's more…

The demo application implementing this recipe shows the piece of cloth falling under gravity. Several frames from the deformation are shown in the following figure. Using the left mouse button, we can pick the cloth vertices and move them around.

There's more…

In this recipe we only output to a single stream. We can attach more than one stream and store results in separate buffer objects. In addition, we can have several transform feedback objects and we can pause/resume them as required.

See also

  • Chapter 17, Real-Time Physically Based Deformation Using Transform Feedback, in OpenGL Insights, AK Peters CRC press
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.239.77