*(pData), XDIM*YDIM*ZDIM*sizeof(GLubyte)); infile.close(); glGenTextures(1, &textureID); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_3D, textureID); // set the texture parameters glTexImage3D(GL_TEXTURE_3D,0,GL_RED,XDIM,YDIM,ZDIM,0, GL_RED,GL_UNSIGNED_BYTE,pData); GL_CHECK_ERRORS glGenerateMipmap(GL_TEXTURE_3D); return true; } else { GPU-based Volume Rendering Techniques 256 return false; } 3. Similar to the shadow mapping technique, calculate the shadow matrix by multiplying the model-view and projection matrices of the light with the bias matrix: MV_L=glm::lookAt(lightPosOS,glm::vec3(0,0,0), glm::vec3(0,1,0)); P_L=glm::perspective(45.0f,1.0f,1.0f, 200.0f); B=glm::scale(glm::translate(glm::mat4(1), glm::vec3(0.5,0.5,0.5)), glm::vec3(0.5,0.5,0.5)); BP = B*P_L; S = BP*MV_L; 4. In the rendering code, calculate the half vector by using the view direction vector and the light direction vector: viewVec = -glm::vec3(MV[0][2], MV[1][2], MV[2][2]); lightVec = glm::normalize(lightPosOS); bIsViewInverted = glm::dot(viewVec, lightVec)<0; halfVec = glm::normalize( (bIsViewInverted?-viewVec:viewVec) + lightVec); 5. Slice the volume data as in the 3D texture slicing recipe. The only difference here is that instead of slicing the volume data in the direction perpendicular to the view, we slice it in the direction which is halfway between the view and the light vectors. float max_dist = glm::dot(halfVec, vertexList[0]); float min_dist = max_dist; int max_index = 0; int count = 0; for(int i=1;i<8;i++) { float dist = glm::dot(halfVec, vertexList[i]); if(dist > max_dist) { max_dist = dist; max_index = i; } if(dist* 0.1) { float alpha = clamp(density, 0.0, 1.0); alpha *= color.a; vFragColor = vec4(color.xyz*lightIntensity*alpha, alpha); } In the next step, the scene is rendered from the point of view of the light. This time, the normal over compositing is used. This ensures that the light contributions accumulate with each other similar to how light behaves in normal circumstances. In this case, we use the same fragment shader as was used in the 3D texture slicing recipe (see Chapter7/ HalgAngleSlicing/shaders/textureSlicer.frag). vFragColor = texture(volume, vUV).rrrr * color ; Chapter 7 259 There's more… The demo application implementing this recipe renders the scene, as shown in the following screenshot, similar to the previous recipes. The light source position can be changed using the right mouse button. We can see the shadow changing dynamically for the scene. Attenuation of light is also controlled by setting a shader uniform. This is the reason why we can observe a bluish tinge in the output image. Note that we cannot see the black halo around the volume dataset as was evident in earlier recipes. The reason for this is the if condition used in the fragment shader. We only perform these calculations if the current density value is greater than 0.1. This essentially removed air and other low intensity artifacts, producing a much better result. See also ff Chapter 39, Volume Rendering Techniques, in GPU Gems 1. Available online at http://http.developer.nvidia.com/GPUGems/gpugems_ch39.html ff Chapter 6, Global Volume Illumination, in Real-time Volume Graphics, AK Peters/ CRC Press. 8 Skeletal and Physically-based Simulation on the GPU In this chapter we will focus on the following topics: ff Implementing skeletal animation using matrix palette skinning ff Implementing skeletal animation using dual quaternion skinning ff Modeling cloth using transform feedback ff Implementing collision detection and response on a transform feedback-based cloth model ff Implementing a particle system using transform feedback Introduction Most of the real-time graphics applications have interactive elements. We have automated bots that move and animate in an interactive application. These elements include objects that are animated using preset sequences of frames. These are called frame-by-frame animations. There are other scene elements that have motion, which is derived using physical simulation. These are called physically-based animations. In addition, humanoid or character models have a special category of animations called skeletal animation. In this chapter, we will look at recipes for doing skeletal and physically-based simulation on the GPU in modern OpenGL. Skeletal and Physically-based Simulation on the GPU 262 Implementing skeletal animation using matrix palette skinning When working with games and simulation systems, virtual characters are often used to give a detailed depiction of scenarios. Such characters are typically represented using a combination of bones and skin. The vertices of the 3D model are assigned influence weights (called blend weights) that control how much a bone influences that vertex. Up to four bones can influence a vertex. The process whereby bone weights are assigned to the vertices of a 3D model is called skinning. Each bone stores its transformation. These stored sequences of transformations are applied to every frame and every bone in the model and in the end, we get an animated character on the screen. This representation of animation is called skeletal animation. There are several methods for skeletal animation. One popular method is matrix palette skinning, which is also known as linear blend skinning (LBS). This method will be implemented in this recipe. Getting ready The code for this recipe is contained in the Chapter8/MatrixPaletteSkinning directory. This recipe will be using the Implementing EZMesh model loading recipe from Chapter 5, Mesh Model Formats and Particle Systems and it will augment it with skeletal animation. The EZMesh format was developed by John Ratcliff and it is an easy-to-understand format for storing skeletal animation. Typical skeletal animation formats like COLLADA and FBX are needlessly complicated, where dozens of segments have to be parsed before the real content can be loaded. On the other hand, the EZMesh format stores all of the information in an XML-based format, which is easier to parse. It is the default skeletal animation format used in the NVIDIA PhysX sdk. More information about the EZMesh model format and loaders can be obtained from the references in the See also section of this recipe. How to do it… Let us start our recipe by following these simple steps: 1. Load the EZMesh model as we did in the Implementing EZMesh loader recipe from Chapter 5, Mesh Model Formats and Particle System. In addition to the model submeshes, vertices, normals, texture coordinates, and materials, we also load the skeleton information from the EZMesh file. EzmLoader ezm; if(!ezm.Load(mesh_filename.c_str(), skeleton, animations, submeshes, vertices, indices, material2ImageMap, min, max)) { cout<<"Cannot load the EZMesh file"<mSkeletonCount>0) { NVSHARE::MeshSkeleton* pSkel = ms->mSkeletons[0]; Bone b; for(int i=0;iGetBoneCount();i++) { const NVSHARE::MeshBone pBone = pSkel->mBones[i]; const int s = strlen(pBone.mName); b.name = new char[s+1]; memset(b.name, 0, sizeof(char)*(s+1)); strncpy_s(b.name,sizeof(char)*(s+1), pBone.mName, s); b.orientation = glm::quat( pBone.mOrientation[3],pBone.mOrientation[0], pBone.mOrientation[1],pBone.mOrientation[2]); b.position = glm::vec3( pBone.mPosition[0], pBone.mPosition[1],pBone.mPosition[2]); b.scale = glm::vec3(pBone.mScale[0], pBone.mScale[1], pBone.mScale[2]); if(!bYup) { float tmp = b.position.y; b.position.y = b.position.z; b.position.z = -tmp; tmp = b.orientation.y; b.orientation.y = b.orientation.z; b.orientation.z = -tmp; tmp = b.scale.y; b.scale.y = b.scale.z; b.scale.z = -tmp; } glm::mat4 S = glm::scale(glm::mat4(1), b.scale); glm::mat4 R = glm::toMat4(b.orientation); Skeletal and Physically-based Simulation on the GPU 264 glm::mat4 T = glm::translate(glm::mat4(1), b.position); b.xform = T*R*S; b.parent = pBone.mParentIndex; skeleton.push_back(b); } UpdateCombinedMatrices(); bindPose.resize(skeleton.size()); invBindPose.resize(skeleton.size()); animatedXform.resize(skeleton.size()); 3. Generate the bind pose and inverse bind pose arrays from the stored bone transformations: for(size_t i=0;imVertices[j].mWeight[0]; mesh.vertices[j].blendWeights.y = pMesh->mVertices[j].mWeight[1]; mesh.vertices[j].blendWeights.z = pMesh->mVertices[j].mWeight[2]; mesh.vertices[j].blendWeights.w = pMesh->mVertices[j].mWeight[3]; mesh.vertices[j].blendIndices[0] = pMesh->mVertices[j].mBone[0]; mesh.vertices[j].blendIndices[1] = pMesh->mVertices[j].mBone[1]; mesh.vertices[j].blendIndices[2] = pMesh->mVertices[j].mBone[2]; mesh.vertices[j].blendIndices[3] = pMesh->mVertices[j].mBone[3]; 5. In the idle callback function, calculate the amount of time to spend on the current frame. If the amount has elapsed, move to the next frame and reset the time. After this, calculate the new bone transformations as well as new skinning matrices, and pass them to the shader: QueryPerformanceCounter(¤t); dt = (double)(current.QuadPart - last.QuadPart) / (double)freq.QuadPart; last = current; static double t = 0; t+=dt; NVSHARE::MeshAnimation* pAnim = &animations[0]; float framesPerSecond = pAnim->GetFrameCount()/ pAnim->GetDuration(); if( t > 1.0f/ framesPerSecond) { currentFrame++; Chapter 8 265 t=0; } if(bLoop) { currentFrame = currentFrame%pAnim->mFrameCount; } else { currentFrame=max(-1,min(currentFrame,pAnim->mFrameCount-1)); } if(currentFrame == -1) { for(size_t i=0;imTrackCount;j++) { NVSHARE::MeshAnimTrack* pTrack = pAnim->mTracks[j]; NVSHARE::MeshAnimPose* pPose = pTrack->GetPose(currentFrame); skeleton[j].position.x = pPose->mPos[0]; skeleton[j].position.y = pPose->mPos[1]; skeleton[j].position.z = pPose->mPos[2]; glm::quat q; q.x = pPose->mQuat[0]; q.y = pPose->mQuat[1]; q.z = pPose->mQuat[2]; q.w = pPose->mQuat[3]; skeleton[j].scale = glm::vec3(pPose->mScale[0], pPose->mScale[1], pPose->mScale[2]); if(!bYup) { skeleton[j].position.y = pPose->mPos[2]; skeleton[j].position.z = -pPose->mPos[1]; q.y = pPose->mQuat[2]; q.z = -pPose->mQuat[1]; skeleton[j].scale.y = pPose->mScale[2]; skeleton[j].scale.z = -pPose->mScale[1]; } skeleton[j].orientation = q; glm::mat4 S =glm::scale(glm::mat4(1),skeleton[j].scale); glm::mat4 R = glm::toMat4(q); glm::mat4 T = glm::translate(glm::mat4(1), skeleton[j]. position); Skeletal and Physically-based Simulation on the GPU 266 skeleton[j].xform = T*R*S; Bone& b = skeleton[j]; if(b.parent==-1) b.comb = b.xform; else b.comb = skeleton[b.parent].comb * b.xform; animatedXform[j] = b.comb * invBindPose[j] ; } } shader.Use(); glUniformMatrix4fv(shader("Bones"),animatedXform.size(), GL_FALSE,glm::value_ptr(animatedXform[0])); shader.UnUse(); shader.UnUse(); How it works… There are two parts of this recipe: generation of skinning matrices and the calculation of GPU skinning in the vertex shader. To understand the first step, we will start with the different transforms that will be used in skinning. Typically, in a simulation or game, a transform is represented as a 4×4 matrix. For skeletal animation, we have a collection of bones. Each bone has a local transform (also called relative transform), which tells how the bone is positioned and oriented with respect to its parent bone. If the bone's local transform is multiplied to the global transform of its parent, we get the global transform (also called absolute transform) of the bone. Typically, the animation formats store the local transforms of the bones in the file. The user application uses this information to generate the global transforms. We define our bone structure as follows: struct Bone { glm::quat orientation; glm::vec3 position; glm::mat4 xform, comb; glm::vec3 scale; char* name; int parent; }; Chapter 8 267 The first field is orientation, which is a quaternion storing the orientation of bone in space relative to its parent. The position field stores its position relative to its parent. The xform field is the local (relative) transform, and the comb field is the global (absolute) transform. The scale field contains the scaling transformation of the bone. In the big picture, the scale field gives the scaling matrix (S), the orientation field gives the rotation matrix (R), and the position field gives the translation matrix (T). The combined matrix T*R*S gives us the relative transform that is calculated when we load the bone information from the EZMesh file in the second step. The name field is the unique name of the bone in the skeleton. Finally, the parent field stores the index of the parent of the current bone in the skeleton array. For the root bone, the parent is -1. For all of the other bones, it will be a number starting from 0 to N-1, where N is the total number of bones in the skeleton. After we have loaded and stored the relative transforms of each bone in the skeleton, we iterate through each bone to obtain its absolute transform. This is carried out in the UpdateCombinedMatrices function in Chapter8/MatrixPaletteSkinning/main.cpp. for(size_t i=0;i0) { GLuint id = materialMap[ material2ImageMap[ submeshes[i].materialName]]; GLint whichID[1]; glGetIntegerv(GL_TEXTURE_BINDING_2D, whichID); if(whichID[0] != id) glBindTexture(GL_TEXTURE_2D, id); glUniform1f(shader("useDefault"), 0.0); } else { glUniform1f(shader("useDefault"), 1.0); } glDrawElements(GL_TRIANGLES, submeshes[i].indices.size(), GL_UNSIGNED_INT, &submeshes[i].indices[0]); }//end for shader.UnUse(); } The matrix palette skinning is carried out on the GPU using the vertex shader (Chapter8/ MatrixPaletteSkinning/shaders/shader.vert). We simply use the blend indices and blend weights to calculate the correct vertex position and normal based on the combined influence of all of the effecting bones. The Bones array contains the skinning matrices that we generated earlier. The complete vertex shader is as follows: Note that the Bones uniform array is not declared in the shader, as it is filled in the shader code dynamically as was shown earlier. Chapter 8 271 #version 330 core layout(location = 0) in vec3 vVertex; layout(location = 1) in vec3 vNormal; layout(location = 2) in vec2 vUV; layout(location = 3) in vec4 vBlendWeights; layout(location = 4) in ivec4 viBlendIndices; smooth out vec2 vUVout; uniform mat4 P; uniform mat4 MV; uniform mat3 N; smooth out vec3 vEyeSpaceNormal; smooth out vec3 vEyeSpacePosition; void main() { vec4 blendVertex=vec4(0); vec3 blendNormal=vec3(0); vec4 vVertex4 = vec4(vVertex,1); int index = viBlendIndices.x; blendVertex = (Bones[index] * vVertex4) * vBlendWeights.x; blendNormal = (Bones[index] * vec4(vNormal, 0.0)).xyz * vBlendWeights.x; index = viBlendIndices.y; blendVertex = ((Bones[index] * vVertex4) * vBlendWeights.y) + blendVertex; blendNormal = (Bones[index] * vec4(vNormal, 0.0)).xyz * vBlendWeights.y + blendNormal; index = viBlendIndices.z; blendVertex = ((Bones[index] * vVertex4) * vBlendWeights.z) + blendVertex; blendNormal = (Bones[index] * vec4(vNormal, 0.0)).xyz * vBlendWeights.z + blendNormal; index = viBlendIndices.w; blendVertex = ((Bones[index] * vVertex4) * vBlendWeights.w) + blendVertex; blendNormal = (Bones[index] * vec4(vNormal, 0.0)).xyz * vBlendWeights.w + blendNormal; vEyeSpacePosition = (MV*blendVertex).xyz; vEyeSpaceNormal = normalize(N*blendNormal); vUVout=vUV; gl_Position = P*vec4(vEyeSpacePosition,1); } The fragment shader uses the attenuated point light source for illumination as we have seen in the Implementing per-fragment point light with attenuation recipe in Chapter 4, Lights and Shadows. Skeletal and Physically-based Simulation on the GPU 272 There's more… The output from the demo application for this recipe shows the dude.ezm model animating using the matrix palette skinning technique as shown in the following figure. The light source can be rotated by right-clicking on it and dragging. Pressing the l key stops the loop playback. See also ff NVIDIA DirectX SDK 9.0 Matrix Palette Skinning demo at http://http.download. nvidia.com/developer/SDK/Individual_Samples/DEMOS/Direct3D9/ src/HLSL_PaletteSkin/docs/HLSL_PaletteSkin.pdf ff John Ratcliff code suppository containing a lot of useful tools, including the EZMesh format specifications and loaders available online at http://codesuppository. blogspot.sg/2009/11/test-application-for-meshimport-library. html ff Improved Skinning demo in the NVIDIA sdk at http://http.download.nvidia. com/developer/SDK/Individual_Samples/samples.html Chapter 8 273 Implementing skeletal animation using dual quaternion skinning Matrix palette skinning suffers from candy wrapping artefacts, especially in regions like shoulder and elbow, where there are several rotations across various axes. If dual quaternion skinning is employed, these artefacts are minimized. In this recipe we will implement skeletal animation using dual quaternion skinning. Before understanding dual quaternions, let us first see what quaternions are. Quaternions are a mathematical entity containing three imaginary dimensions (which specify the axis of rotation) and a real dimension (which specifies the angle of rotation). Quaternions are used in 3D graphics to represent rotation, since they do not suffer from gimbal lock, as Euler angles do. In order to store translation with rotation simultaneously, dual quaternions are used to store dual number coefficients instead of real ones. Instead of four components, as in a quaternion, dual quaternions have eight components. Even in dual quaternion skinning, the linear blend method is used. However, due to the nature of transformation in dual quaternion, spherical blending is preferred. After linear blending, the resulting dual quaternion is renormalized, which generates a spherical blending result, which is a better approximation as compared to the linear blend skinning. This whole process is illustrated by the following figure: Getting ready The code for this recipe is contained in the Chapter8/DualQuaternionSkinning folder. We will be building on top of the previous recipe and replace the skinning matrices with dual quaternions. Skeletal and Physically-based Simulation on the GPU 274 How to do it… Converting linear blend skinning to dual quaternion skinning requires the following steps: 1. Load the EZMesh model as we did in the Implementing EZMesh loader recipe from Chapter 5, Mesh Model Formats and Particle System: if(!ezm.Load(mesh_filename.c_str(), skeleton, animations, submeshes, vertices, indices, material2ImageMap, min, max)) { cout<<"Cannot load the EZMesh file"< indices; vector X; vector X_last; vector F; Skeletal and Physically-based Simulation on the GPU 282 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); Chapter 8 283 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); } Skeletal and Physically-based Simulation on the GPU 284 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 (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; } Chapter 8 287 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; Skeletal and Physically-based Simulation on the GPU 288 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); Chapter 8 289 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. 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 ff Chapter 17, Real-Time Physically Based Deformation Using Transform Feedback, in OpenGL Insights, AK Peters CRC press Skeletal and Physically-based Simulation on the GPU 290 Implementing collision detection and response on a transform feedback-based cloth model In this recipe, we will build on top of the previous recipe and add collision detection and response to the cloth model. Getting ready The code for this recipe is contained in the Chapter8/ TransformFeedbackClothCollision directory. For this recipe, the setup code and rendering code remains the same as in the previous recipe. The only change is the addition of the ellipsoid/sphere collision code. How to do it… Let us start this 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 as in the previous recipe. 2. Set up a pair of vertex array objects and buffer objects as in the previous recipe. Also attach buffer textures for easier access to the buffer object memory in the vertex shader. 3. 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 again: 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()); 4. Generate an ellipsoid object by using a simple 4×4 matrix. Also store the inverse of the ellipsoid's transform. The location of the ellipsoid is stored by the translate matrix, the orientation by the rotate matrix, and the non-uniform scaling by the scale matrix as follows. When applied, the matrices work in the opposite order. The non-uniform scaling causes the sphere to compress in the Z direction first. Then, the rotation orients the ellipsoid such that it is rotated by 45 degrees on the X axis. Finally, the ellipsoid is shifted by 2 units on the Y axis: ellipsoid = glm::translate(glm::mat4(1),glm::vec3(0,2,0)); ellipsoid = glm::rotate(ellipsoid, 45.0f ,glm::vec3(1,0,0)); Chapter 8 291 ellipsoid = glm::scale(ellipsoid, glm::vec3(fRadius,fRadius,fRadius/2)); inverse_ellipsoid = glm::inverse(ellipsoid); 5. In the rendering function, bind the cloth deformation shader (Chapter8/ TransformFeedbackClothCollision/shaders/Spring.vert) and then run a loop. In each 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. Do the ping pong strategy as in the previous recipe. 6. After the loop is terminated, bind the render VAO and render the cloth: 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; Skeletal and Physically-based Simulation on the GPU 292 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); Chapter 8 293 9. After applying the floor collision, check for collision with an ellipsoid. If there is a collision, modify the position such that the collision is resolved. Finally, output the appropriate attributes from the vertex shader. vec4 x0 = inv_ellipsoid*vec4(pos,1); vec3 delta0 = x0.xyz-ellipsoid.xyz; float dist2 = dot(delta0, delta0); if(dist2<1) { delta0 = (ellipsoid.w - dist2) * delta0 / dist2; vec3 delta; vec3 transformInv = vec3(ellipsoid_xform[0].x, ellipsoid_xform[1].x, ellipsoid_xform[2].x); transformInv /= dot(transformInv, transformInv); delta.x = dot(delta0, transformInv); transformInv = vec3(ellipsoid_xform[0].y, ellipsoid_xform[1].y, ellipsoid_xform[2].y); transformInv /= dot(transformInv, transformInv); delta.y = dot(delta0, transformInv); transformInv = vec3(ellipsoid_xform[0].z, ellipsoid_xform[1].z, ellipsoid_xform[2].z); transformInv /= dot(transformInv, transformInv); delta.z = dot(delta0, transformInv); pos += delta ; pos_old = pos; } out_position_mass = vec4(pos, m); out_prev_position = vec4(pos_old,m); gl_Position = MVP*vec4(pos, 1); How it works… The cloth deformation vertex shader has some additional lines of code to enable collision detection and response. For detection of collision with a plane, we can simply put the current position in the plane equation to find the distance of the current vertex from the plane. If it is less than 0, we have passed through the plane, in which case, we can move the vertex back in the plane's normal direction. void planeCollision(inout vec3 x, vec4 plane) { float dist = dot(plane.xyz,x)+ plane.w; if(dist<0) { x += plane.xyz*-dist; } } Skeletal and Physically-based Simulation on the GPU 294 Simple geometric primitive, like spheres and ellipsoids, are trivial to handle. In case of collision with the sphere, we check the distance of the current position from the center of the sphere. If this distance is less than the sphere's radius, we have a collision. Once we have a collision, we push the position in the normal direction based on the amount of penetration. void sphereCollision(inout vec3 x, vec4 sphere) { vec3 delta = x - sphere.xyz; float dist = length(delta); if (dist < sphere.w) { x = sphere.xyz + delta*(sphere.w / dist); } } Note that in the preceding calculation, we can avoid the square root altogether by comparing against the squared distance. This can provide significant performance gain when a large number of vertices are there. For an arbitrarily oriented ellipsoid, we first move the point into the ellipsoid's object space by multiplying with the inverse of the ellipsoid's transform. In this space, the ellipsoid is a unit sphere, hence we can then determine collision by simply looking at the distance between the current vertex and the ellipsoid. If it is less than 1, we have a collision. In this case, we then transform the point to the ellipsoids world space to find the penetration depth. This is then used to displace the current position out in the normal direction. vec4 x0 = inv_ellipsoid*vec4(pos,1); vec3 delta0 = x0.xyz-ellipsoid.xyz; float dist2 = dot(delta0, delta0); if(dist2<1) { delta0 = (ellipsoid.w - dist2) * delta0 / dist2; vec3 delta; vec3 transformInv = vec3(ellipsoid_xform[0].x, ellipsoid_xform[1].x, ellipsoid_xform[2].x); transformInv /= dot(transformInv, transformInv); delta.x = dot(delta0, transformInv); transformInv = vec3(ellipsoid_xform[0].y, ellipsoid_xform[1].y, ellipsoid_xform[2].y); transformInv /= dot(transformInv, transformInv); delta.y = dot(delta0, transformInv); transformInv = vec3(ellipsoid_xform[0].z, ellipsoid_xform[1].z, ellipsoid_xform[2].z); transformInv /= dot(transformInv, transformInv); delta.z = dot(delta0, transformInv); pos += delta ; pos_old = pos; } Chapter 8 295 There's more… The demo application implementing this recipe renders a piece of cloth fixed at two points and is allowed to fall under gravity. In addition, there is an oriented ellipsoid with which the cloth collides as shown in the following figure: Although we have touched upon basic collision primitives, like spheres, oriented ellipsoids, and plane, more complex primitives can be implemented with the combination of these basic primitives. In addition, polygonal primitives can also be implemented. We leave that as an exercise for the reader. See also ff MOVANIA Muhammad Mobeen and Lin Feng, "A Novel GPU-based Deformation Pipeline" in ISRN Computer Graphics, Volume 2012(2012), Article ID 936315, available online at http://downloads.hindawi.com/isrn/cg/2012/936315.pdf Skeletal and Physically-based Simulation on the GPU 296 Implementing a particle system using transform feedback In this recipe, we will implement a simple particle system using the transform feedback mechanism. In this mode, the GPU bypasses the rasterizer and, later, the programmable graphics pipeline stages to feedback result to the vertex shader. The benefit from this mode is that using this feature, we can implement a physically-based simulation entirely on the GPU. Getting ready The code for this recipe is contained in the Chapter8/TransformFeedbackParticles directory. How to do it… Let us start this recipe by following these simple steps: 1. Set up two vertex array pairs: one for update and another for rendering. Bind two vertex buffer objects to each of the pairs, as was done in the previous two recipes. Here, the buffer objects will store the per-particle properties. Also, enable the corresponding vertex attributes: glGenVertexArrays(2, vaoUpdateID); glGenVertexArrays(2, vaoRenderID); glGenBuffers( 2, vboID_Pos); glGenBuffers( 2, vboID_PrePos); glGenBuffers( 2, vboID_Direction); for(int i=0;i<2;i++) { glBindVertexArray(vaoUpdateID[i]); glBindBuffer( GL_ARRAY_BUFFER, vboID_Pos[i]); glBufferData( GL_ARRAY_BUFFER, TOTAL_PARTICLES* sizeof(glm::vec4), 0, 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, TOTAL_PARTICLES* sizeof(glm::vec4), 0, GL_DYNAMIC_COPY); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0,0); glBindBuffer( GL_ARRAY_BUFFER, vboID_Direction[i]); glBufferData( GL_ARRAY_BUFFER, TOTAL_PARTICLES* sizeof(glm::vec4), 0, GL_DYNAMIC_COPY); glEnableVertexAttribArray(2); Chapter 8 297 glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0,0); } 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); } 2. Generate a transform feedback object and bind it. Next, specify the output attributes from the shader that would be stored in the transform feedback buffer. After this step, relink the shader program: glGenTransformFeedbacks(1, &tfID); glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tfID); const char* varying_names[]={"out_position", "out_prev_position", "out_direction"}; glTransformFeedbackVaryings(particleShader.GetProgram(), 3, varying_names, GL_SEPARATE_ATTRIBS); glLinkProgram(particleShader.GetProgram()); 3. In the update function, bind the particle vertex shader that will output to the transform feedback buffer and set the appropriate uniforms and update vertex array object. Note that to enable read/write access, we use a pair of vertex array objects such that we can read from one and write to another: particleShader.Use(); glUniformMatrix4fv(particleShader("MVP"), 1, GL_FALSE, glm::value_ptr(mMVP)); glUniform1f(particleShader("time"), t); for(int i=0;i 0) { prevPos = pos; pos += dir*speed; if(dot(pos+emitterPos, collidor.xyz)+ collidor.w <0) { dir = reflect(dir, collidor.xyz); speed *= DAMPING_COEFFICIENT; } dir += emitterForce; life--; } else { uint seed = uint(time + gl_VertexID); Skeletal and Physically-based Simulation on the GPU 300 life = emitterLife + int(randhashf(seed++, emitterLifeVar)); float yaw = emitterYaw + (randhashf(seed++, emitterYawVar )); float pitch = emitterPitch + randhashf(seed++, emitterPitchVar); RotationToDirection(pitch, yaw, dir); float nspeed = emitterSpeed + (randhashf(seed++, emitterSpeedVar )); dir *= nspeed; pos = emitterPos; prevPos = emitterPos; speed = 1; } out_position = vec4(pos, speed); out_prev_position = vec4(prevPos, life); out_direction = vec4(dir, 0); gl_Position = MVP*vec4(pos, 1); } The three helper functions randhash, randhashf, and RotationToDirection are defined as follows: uint randhash(uint seed) { uint i=(seed^12345391u)*2654435769u; i^=(i<<6u)^(i>>26u); i*=2654435769u; i+=(i<<5u)^(i>>12u); return i; } float randhashf(uint seed, float b) { return float(b * randhash(seed)) / UINT_MAX; } void RotationToDirection(float pitch, float yaw, out vec3 direction) { direction.x = -sin(yaw) * cos(pitch); direction.y = sin(pitch); direction.z = cos(pitch) * cos(yaw); } Chapter 8 301 How it works… The transform feedback mechanism allows us to feedback one or more attributes from the vertex shader or geometry shader back to a buffer object. This feedback path could be used for implementing a physically-based simulation. This recipe uses this mechanism to output the particle position after each iteration. After each step, the buffers are swapped and, therefore, it can simulate the particle motion. To make the particle system, we first set up three pairs of vertex buffer objects that store the per-particle attributes that we input to the vertex shader. These include the particle's position, previous position, life, direction, and speed. These are stored into separate buffer objects for convenience. We could have stored all of these attributes into a single interleaved buffer object. Since we output to the buffer object from our shader, we specify the buffer object usage as GL_DYNAMIC_COPY. Similarly, we set up a separate vertex array object for rendering the particles: for(int i=0;i<2;i++) { glBindVertexArray(vaoUpdateID[i]); glBindBuffer( GL_ARRAY_BUFFER, vboID_Pos[i]); glBufferData( GL_ARRAY_BUFFER, TOTAL_PARTICLES* sizeof(glm::vec4), 0, 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, TOTAL_PARTICLES*sizeof(glm::vec4), 0, GL_DYNAMIC_COPY); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0,0); glBindBuffer( GL_ARRAY_BUFFER, vboID_Direction[i]); glBufferData( GL_ARRAY_BUFFER, TOTAL_PARTICLES*sizeof(glm::vec4), 0, GL_DYNAMIC_COPY); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0,0); } 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); } Skeletal and Physically-based Simulation on the GPU 302 Next, we specify the shader output attributes that we would like to connect to the transform feedback buffers. We use three outputs, namely out_position, out_prev_position, and out_direction, which output the particle's current position, particle's previous position, and the particle's direction along with the particle's speed, current, and initial life, respectively. We specify that we would connect these to separate buffer objects: glGenTransformFeedbacks(1, &tfID); glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tfID); const char* varying_names[]={"out_position", "out_prev_position", "out_direction"}; glTransformFeedbackVaryings(particleShader.GetProgram(), 3, varying_ names, GL_SEPARATE_ATTRIBS); glLinkProgram(particleShader.GetProgram()); One last step is the actual initialization of the transform feedback. We do so by first binding the particle vertex shader. Then, we pass the appropriate uniforms to the shader, which includes the combined modelview projection (MVP) matrix and the time (t): particleShader.Use(); glUniformMatrix4fv(particleShader("MVP"),1,GL_FALSE, glm::value_ptr(mMVP)); glUniform1f(particleShader("time"), t); We then run a loop for the number of iterations desired. In the loop, we first bind the update vertex array object and assign the appropriate transform feedback buffer base indices: for(int i=0;i 0) { prevPos = pos; pos += dir*speed; if(dot(pos+emitterPos, collidor.xyz)+ collidor.w <0) { dir = reflect(dir, collidor.xyz); speed *= DAMPING_COEFFICIENT; } dir += emitterForce; life--; } If the life is less than 0, we reset the particle's direction of motion to a new random direction. We reset the life to a random value based on the maximum allowed value. The current and previous positions of the particle are reset to the emitter origin and finally, the speed is reset to the default value. We then output the output attributes: else { uint seed = uint(time + gl_VertexID); life = emitterLife + int(randhashf(seed++, emitterLifeVar)); float yaw = emitterYaw + (randhashf(seed++, emitterYawVar )); float pitch=emitterPitch+randhashf(seed++, emitterPitchVar); RotationToDirection(pitch, yaw, dir); float nspeed = emitterSpeed + (randhashf(seed++, emitterSpeedVar )); dir *= nspeed; Skeletal and Physically-based Simulation on the GPU 304 pos = emitterPos; prevPos = emitterPos; speed = 1; } out_position = vec4(pos, speed); out_prev_position = vec4(prevPos, life); out_direction = vec4(dir, 0); gl_Position = MVP*vec4(pos, 1); There's more… The demo application for this recipe generates a simple particle system running entirely on the GPU using the transform feedback mechanism coupled with a vertex shader that writes to output attributes bound as transform feedback buffers. Running the demo application gives us the output as shown in the following figure: Note that for this demo, we render the particles as points of size 10 units. We could easily change the rendering mode to point sprites with size modified in the vertex shader to give the particles a different look. Also, we can also change the colors and blending modes to achieve various effects. In addition, we could achieve the same result by using one vertex buffer pair with interleaved attributes or two separate transform feedback objects. All of these variants should be straightforward to implement by following the guidelines laid out in this recipe. Chapter 8 305 We had already looked at a simple approach of simulating GPU-based particle systems using vertex shader in Chapter 5, Mesh Model Formats and Particle Systems, we will now detail pros and cons of each. In Chapter 5 we presented a stateless particle system since all of the attributes (that is, position and velocity) were generated on the fly using the vertex ID, time, and basic kinematic equations on each particle vertex. As the state of the particle is not stored, we cannot reproduce the same simulation every frame. Hence, collision detection and response are problematic, as we do not have any information of the previous state of the particle, which is often required for collision response. On the contrary, the particle simulation technique presented in this recipe uses a state- preserving particle system. We stored current and previous positions of each particle in buffer objects. In addition, we used transform feedback and a vertex shader for particle simulation on the GPU. As the state of the particle is stored, we can carry out collision detection and response easily. See also ff OGLDev Tutorial on particle system using transform feedback at http://ogldev. atspace.co.uk/www/tutorial28/tutorial28.html ff OpenGL 4.0 Shading Language Cookbook, Chapter 9, Animation and Particles, the Creating a particle system using transform feedback section, Packt Publishing, 2011. ff Noise based Particles, Part II at The Little Grasshopper, http://prideout.net/ blog/?p=67 Index Symbols 2D image drawing, fragment shader used 48-54 drawing, SOIL image loading library used 48-54 3D graphics programming URL 27, 114 3ds file format URL 156 3ds file loader URL 156 3ds Max 141 3DS model loading implementing, separate buffers used 146-155 3D texture slicing about 220 used, for implementing volume rendering 220-227 3 x 3 Gaussian blur effect 100 A absolute transform. See global transform area filtering implementing, on image 98-100 attenuation per-fragment point light, implementing with 117, 118 Autodesk® 3ds 141 Autodesk® FBX (.fbx) 163 Axially Aligned Bounding Box (AABB) 77 B bi-directional reflectance distribution function (BRDF) 202 Bisection function 235 blend weights 262 Blinn Phong lighting model 111 blurring effect 100 C C3dsLoader class 146 C3dsLoader::Load3DS function 146, 152 CAbstractCamera class 56 CFreeCamera class 56 cloth modeling, transform feedback used 279-289 COLLADA 262 Collada (.dae) 163 collision detection implementing 290-295 color used, for implementing object picking 74, 75 colored triangle rendering, shaders used 19-27 convolution area filtering, applying on image 98-100 CreateTexture function 255 CTargetCamera class 56 D depth buffer used, for implementing object picking 72, 73 308 dual depth peeling about 189 used, for implementing order independent transparency 189-193 dual quaternion skinning used, for implementing skeletal animation 273-279 dynamic cube mapping used, for rendering reflective object 93-96 E emboss north-east direction effect 100 emboss north-west direction effect 100 emboss south-east direction effect 101 emboss south-west direction effect 101 EmitVertex() function 42 EndPrimitive() function 42 EZMesh (.ezm) 163 EZMesh format 262 EZMesh model implementing 163-169 EzmLoader::Load function 263 F FBO shadow mapping, implementing with 122-127 used, for implementing mirror object 89-92 FBX 262 final bone matrix. See skinning matrix FPS style input support used, for implementing vector-based camera 56-59 fragment shader about 16 used, for drawing 2D image 48-54 used, for implementing twirl filter 82-85 Framebuffer objects (FBOs) 81 frame-by-frame animations 261 free camera about 59 implementing 60-62 freeglut libraries OpenGL v3.3 core profile, setting up on Visual Studio 2010 8-14 URL, for downloading 8 front-to-back peeling used, for implementing order independent transparency 182-188 G geometry shader about 16 plane, subdividing with instanced rendering 45-48 URL, for tutorial 44 used, for subdividing plane 37-44 glBindBufferBase function 280 glBufferData function 149 glClearDepth function 15 glDepthFunc function 15 glDrawElements function 159 GLEW OpenGL v3.3 core profile, setting up on Visual Studio 2010 8-14 GLEW library URL, for downloading 8 glFramebufferRenderbuffer function 89 glFramebufferTexture2D function 89 glGetTransformFeedbacks function 279 glMapBuffer function 149 glm library URL, for downloading 9 global illumination implementing, spherical harmonics lighting used 202-206 global illumination methods 181 global transform 266 glow effect implementing 101-105 GLSL shader class designing 16-19 GLSLShader::CreateAndLinkProgram function 23 GLSLShader::operator() function 27 glTexImage3D function 226 glTransformFeedbackVaryings function 280 glutCloseFunc method 14 glutMainLoop() function 14 glutSwapBuffer function 27 glutSwapBuffers function 15 glVertexAttribIPointer function 270 309 Gouraud shading 108 GPU-based path tracing implementing 213-217 GPU-based ray tracing implementing 207-211 H half angle slicing used, for implementing volumetric lighting 254-259 height map used, for implementing terrains 142-145 I image area filtering, applying on 98-100 interleaved buffers used, for implementing Wavefront ® Obj model 157-161 intersectBox function 78 L level of detail (LOD) 222 Lib3ds URL 156 Lift function 62 Lighthouse 3D view frustum culling URL, for tutorial 72 linear blend skinning (LBS) 262 local transform 266 M main() function 12 Marching Cubes 253 Marching Tetrahedra (MT) algorithm about 248 used, for implementing polygonal isosurface extraction 248-253 matrix palette skinning about 262 used, for implementing skeletal animation 262-271 Maya 141 MeshImport library URL 170 MeshSystem::mSkeletons array 263 mirror, with render-to-texture implementing, FBO used 89-92 modelview projection (MVP) 143 Move function 65 N NVIDIA PhysX sdk 262 O object picking implementing, color used 74, 75 implementing, depth buffer used 72, 73 implementing, scene intersection queries used 76-78 OBJ file specification URL 162 ObjLoader::Load function 157 ObjLoader object 157 offscreen rendering functionality 81 OnInit() function 12 OnMouseMove function 36 OnRender() function 12, 15 OnResize() function 12 OnShutdown() function 12, 26, 35, 54 OpenGL 4.3 8 OpenGL API 7 OpenGL shading language (GLSL) 7 OpenGL v2.0 7 OpenGL v3.0 8 OpenGL v3.3 55, 141 OpenGL v3.3 core profile setting up, on Visual Studio 2010 8-14 order independent transparency implementing, dual depth peeling used 189-193 implementing, front-to-back peeling used 182-188 P Pan function 65 particle system about 171 310 implementing 171-179 implementing, transform feedback used 296-305 percentage closer filtering (PCF) about 128 shadow mapping, implementing with 128-131 per-fragment directional light about 114 implementing 115, 116 per-fragment point light implementing 108-113 implementing, with attenuation 117, 118 per-fragment spot light about 120 implementing 120, 121 per-vertex point lighting implementing 108-113 Phong shading 108 physically based animations 261 plane subdividing, geometry shader used 37-44 polygonal isosurface extraction implementing, Marching Tetrahedra algorithm used 248-253 pseudo isosurface rendering implementing, in single-pass GPU ray casting 232-236 Q Quake's md2 (.md2) 163 Quaternions 273 R reflective object rendering, dynamic cube mapping used 93- 96 relative transform. See local transform response on cloth model implementing 290-295 ripple mesh deformer implementing, vertex shader used 28-36 S SampleVoxel function 238 scene intersection queries used, for implementing object picking 76-78 screen space ambient occlusion (SSAO) about 195 implementing 196-201 separate buffers used, for implementing 3DS model loading 146-155 shader binding 18 shaders used, for rendering colored triangle 19-27 shadow mapping implementing, with FBO 122-127 implementing, with percentage closer filtering (PCF) 128-131 sharpening effect 100 single-pass GPU ray casting pseudo isosurface rendering, implementing 232-236 used, for implementing volume rendering 228-232 skeletal animation about 261, 262 implementing, dual quaternion skinning used 273-279 implementing, matrix palette skinning used 262-271 skinning 262 skinning matrix 268 skybox rendering, static cube mapping used 85-88 SliceVolume function 224 smooth mouse filtering URL 59 SOIL image loading library 142 URL, for downloading 9 used, for drawing 2D image 48-54 spherical harmonics lighting used, for implementing global illumination 202-206 splatting used, for implementing volume rendering 237-244 static cube mapping used, for rendering skybox 85-88 Strafe function 62 311 T target camera about 63 implementing 63-66 Terragen 146 TERRAIN_DEPTH parameter 143 terrain height map generation tools 146 terrain rendering 144 terrains implementing, height map used 142-145 TERRAIN_WIDTH parameter 143 tessellation control shader 16 tessellation evaluation shader 16 texelFetchBuffer function 284 transfer function implementing, for volume classification 244-247 transform 266 transform feedback about 279 used, for implementing particle system 296-304 used, for modeling cloth 279-289 twirl filter implementing, fragment shader used 82-85 U unweighted smoothing effect 100 V variance shadow mapping about 132 implementing 132-139 varying attributes 22 vector-based camera implementing, with FPS style input support 56-59 vertex array object (VAO) 24, 239, 283 vertex buffer object (VBO) 239 vertex shader about 16 used, for implementing ripple mesh deformer 28-36 view frustum culling about 66 implementing 67-70 virtual terrain project about 146 URL 146 volume classification transfer function, implementing for 244-247 volume rendering about 219, 220 implementing, 3D texture slicing used 220-227 implementing, single-pass GPU ray casting used 228-232 implementing, splatting used 237-244 volumetric lighting implementing, half angle slicing used 254-259 W Walk function 62 Wavefront® OBJ 141 Wavefront ® Obj model implementing, interleaved buffers used 157-161 world machine about 146 URL 146 world space position 28 Z Zoom function 65 Thank you for buying OpenGL Development Cookbook About Packt Publishing Packt, pronounced 'packed', published its first book "Mastering phpMyAdmin for Effective MySQL Management" in April 2004 and subsequently continued to specialize in publishing highly focused books on specific technologies and solutions. Our books and publications share the experiences of your fellow IT professionals in adapting and customizing today's systems, applications, and frameworks. Our solution based books give you the knowledge and power to customize the software and technologies you're using to get the job done. Packt books are more specific and less general than the IT books you have seen in the past. Our unique business model allows us to bring you more focused information, giving you more of what you need to know, and less of what you don't. Packt is a modern, yet unique publishing company, which focuses on producing quality, cutting-edge books for communities of developers, administrators, and newbies alike. For more information, please visit our website: www.packtpub.com. Writing for Packt We welcome all inquiries from people who are interested in authoring. Book proposals should be sent to author@packtpub.com. If your book idea is still at an early stage and you would like to discuss it first before writing a formal book proposal, contact us; one of our commissioning editors will get in touch with you. We're not just looking for published authors; if you have strong technical skills but no writing experience, our experienced editors can help you develop a writing career, or simply get some additional reward for your expertise. OpenGL 4.0 Shading Language Cookbook ISBN: 978-1-84951-476-7 Paperback: 340 pages Over 60 highly focused, practical recipes to maximize your use of the OpenGL Shading Language 1. A full set of recipes demonstrating simple and advanced techniques for producing high-quality, real-time 3D graphics using GLSL 4.0 2. How to use the OpenGL Shading Language to implement lighting and shading techniques 3. Use the new features of GLSL 4.0 including tessellation and geometry shaders OpenCV 2 Computer Vision Application Programming Cookbook ISBN: 978-1-84951-324-1 Paperback: 304 pages Over 50 recipes to master this library of programming functions for real-time computer vision 1. Teaches you how to program computer vision applications in C++ using the different features of the OpenCV library 2. Demonstrates the important structures and functions of OpenCV in detail with complete working examples 3. Describes fundamental concepts in computer vision and image processing 4. Gives you advice and tips to create more effective object-oriented computer vision programs Please check www.PacktPub.com for information on our titles OpenSceneGraph 3 Cookbook ISBN: 978-1-84951-688-4 Paperback: 426 pages Over 80 recipes to show advanced 3D programming techniques with the OpenSceneGraph API 1. Introduce the latest OpenSceneGraph features to create stunning graphics, as well as integration with other famous libraries 2. Produce high-quality programs with short and familiar code 3. Enriched with a lot of code and the necessary screenshots Unity Game Development Essentials ISBN: 978-1-84719-818-1 Paperback: 316 pages Build fully functional, professional 3D games with realistic environments, sound, dynamic effects, and more! 1. Kick start game development, and build ready-to- play 3D games with ease 2. Understand key concepts in game design including scripting, physics, instantiation, particle effects, and more 3. Test & optimize your game to perfection with essential tips-and-tricks 4. Written in clear, plain English, this book is packed with working examples and innovative ideas Please check www.PacktPub.com for information on our titles

贡献于2013-10-12