Skeletal-Animation.html (51243B)
1 <!DOCTYPE html> 2 <html lang="ja"> 3 <head> 4 <meta charset="utf-8"/> 5 <title>LearnOpenGL</title> 6 <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> 7 <link rel="stylesheet" href="../static/style.css" /> 8 <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"> </script> 9 <script src="/static/functions.js"></script> 10 </head> 11 <body> 12 <nav> 13 <ol> 14 <li id="Introduction"> 15 <a href="https://learnopengl.com/Introduction">はじめに</a> 16 </li> 17 <li id="Getting-started"> 18 <span class="closed">入門</span> 19 <ol> 20 <li id="Getting-started/OpenGL"> 21 <a href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a> 22 </li> 23 <li id="Getting-started/Creating-a-window"> 24 <a href="https://learnopengl.com/Getting-started/Creating-a-window">ウィンドウの作成</a> 25 </li> 26 <li id="Getting-started/Hello-Window"> 27 <a href="https://learnopengl.com/Getting-started/Hello-Window">最初のウィンドウ</a> 28 </li> 29 <li id="Getting-started/Hello-Triangle"> 30 <a href="https://learnopengl.com/Getting-started/Hello-Triangle">最初の三角形</a> 31 </li> 32 <li id="Getting-started/Shaders"> 33 <a href="https://learnopengl.com/Getting-started/Shaders">シェーダー</a> 34 </li> 35 <li id="Getting-started/Textures"> 36 <a href="https://learnopengl.com/Getting-started/Textures">テクスチャ</a> 37 </li> 38 <li id="Getting-started/Transformations"> 39 <a href="https://learnopengl.com/Getting-started/Transformations">座標変換</a> 40 </li> 41 <li id="Getting-started/Coordinate-Systems"> 42 <a href="https://learnopengl.com/Getting-started/Coordinate-Systems">座標系</a> 43 </li> 44 <li id="Getting-started/Camera"> 45 <a href="https://learnopengl.com/Getting-started/Camera">カメラ</a> 46 </li> 47 <li id="Getting-started/Review"> 48 <a href="https://learnopengl.com/Getting-started/Review">まとめ</a> 49 </li> 50 </ol> 51 </li> 52 <li id="Lighting"> 53 <span class="closed">Lighting </span> 54 <ol> 55 <li id="Lighting/Colors"> 56 <a href="https://learnopengl.com/Lighting/Colors">Colors </a> 57 </li> 58 <li id="Lighting/Basic-Lighting"> 59 <a href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a> 60 </li> 61 <li id="Lighting/Materials"> 62 <a href="https://learnopengl.com/Lighting/Materials">Materials </a> 63 </li> 64 <li id="Lighting/Lighting-maps"> 65 <a href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a> 66 </li> 67 <li id="Lighting/Light-casters"> 68 <a href="https://learnopengl.com/Lighting/Light-casters">Light casters </a> 69 </li> 70 <li id="Lighting/Multiple-lights"> 71 <a href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a> 72 </li> 73 <li id="Lighting/Review"> 74 <a href="https://learnopengl.com/Lighting/Review">Review </a> 75 </li> 76 </ol> 77 </li> 78 <li id="Model-Loading"> 79 <span class="closed">Model Loading </span> 80 <ol> 81 <li id="Model-Loading/Assimp"> 82 <a href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a> 83 </li> 84 <li id="Model-Loading/Mesh"> 85 <a href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a> 86 </li> 87 <li id="Model-Loading/Model"> 88 <a href="https://learnopengl.com/Model-Loading/Model">Model </a> 89 </li> 90 </ol> 91 </li> 92 <li id="Advanced-OpenGL"> 93 <span class="closed">Advanced OpenGL </span> 94 <ol> 95 <li id="Advanced-OpenGL/Depth-testing"> 96 <a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a> 97 </li> 98 <li id="Advanced-OpenGL/Stencil-testing"> 99 <a href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a> 100 </li> 101 <li id="Advanced-OpenGL/Blending"> 102 <a href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a> 103 </li> 104 <li id="Advanced-OpenGL/Face-culling"> 105 <a href="https://learnopengl.cm/Advanced-OpenGL/Face-culling">Face culling </a> 106 </li> 107 <li id="Advanced-OpenGL/Framebuffers"> 108 <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a> 109 </li> 110 <li id="Advanced-OpenGL/Cubemaps"> 111 <a href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a> 112 </li> 113 <li id="Advanced-OpenGL/Advanced-Data"> 114 <a href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a> 115 </li> 116 <li id="Advanced-OpenGL/Advanced-GLSL"> 117 <a href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a> 118 </li> 119 <li id="Advanced-OpenGL/Geometry-Shader"> 120 <a href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a> 121 </li> 122 <li id="Advanced-OpenGL/Instancing"> 123 <a href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a> 124 </li> 125 <li id="Advanced-OpenGL/Anti-Aliasing"> 126 <a href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a> 127 </li> 128 </ol> 129 </li> 130 <li id="Advanced-Lighting"> 131 <span class="closed">Advanced Lighting </span> 132 <ol> 133 <li id="Advanced-Lighting/Advanced-Lighting"> 134 <a href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a> 135 </li> 136 <li id="Advanced-Lighting/Gamma-Correction"> 137 <a href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a> 138 </li> 139 <li id="Advanced-Lighting/Shadows"> 140 <span class="closed">Shadows </span> 141 <ol> 142 <li id="Advanced-Lighting/Shadows/Shadow-Mapping"> 143 <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a> 144 </li> 145 <li id="Advanced-Lighting/Shadows/Point-Shadows"> 146 <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a> 147 </li> 148 </ol> 149 </li> 150 <li id="Advanced-Lighting/Normal-Mapping"> 151 <a href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a> 152 </li> 153 <li id="Advanced-Lighting/Parallax-Mapping"> 154 <a href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a> 155 </li> 156 <li id="Advanced-Lighting/HDR"> 157 <a href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a> 158 </li> 159 <li id="Advanced-Lighting/Bloom"> 160 <a href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a> 161 </li> 162 <li id="Advanced-Lighting/Deferred-Shading"> 163 <a href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a> 164 </li> 165 <li id="Advanced-Lighting/SSAO"> 166 <a href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a> 167 </li> 168 </ol> 169 </li> 170 <li id="PBR"> 171 <span class="closed">PBR </span> 172 <ol> 173 <li id="PBR/Theory"> 174 <a href="https://learnopengl.com/PBR/Theory">Theory </a> 175 </li> 176 <li id="PBR/Lighting"> 177 <a href="https://learnopengl.com/PBR/Lighting">Lighting </a> 178 </li> 179 <li id="PBR/IBL"> 180 <span class="closed">IBL </span> 181 <ol> 182 <li id="PBR/IBL/Diffuse-irradiance"> 183 <a href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a> 184 </li> 185 <li id="PBR/IBL/Specular-IBL"> 186 <a href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a> 187 </li> 188 </ol> 189 </li> 190 </ol> 191 </li> 192 <li id="In-Practice"> 193 <span class="closed">In Practice </span> 194 <ol> 195 <li id="In-Practice/Debugging"> 196 <a href="https://learnopengl.com/In-Practice/Debugging">Debugging </a> 197 </li> 198 <li id="In-Practice/Text-Rendering"> 199 <a href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a> 200 </li> 201 <li id="In-Practice/2D-Game"> 202 <span class="closed">2D Game </span> 203 <ol> 204 <li id="In-Practice/2D-Game/Breakout"> 205 <a href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a> 206 </li> 207 <li id="In-Practice/2D-Game/Setting-up"> 208 <a href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a> 209 </li> 210 <li id="In-Practice/2D-Game/Rendering-Sprites"> 211 <a href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a> 212 </li> 213 <li id="In-Practice/2D-Game/Levels"> 214 <a href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a> 215 </li> 216 <li id="In-Practice/2D-Game/Collisions"> 217 <span class="closed">Collisions </span> 218 <ol> 219 <li id="In-Practice/2D-Game/Collisions/Ball"> 220 <a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a> 221 </li> 222 <li id="In-Practice/2D-Game/Collisions/Collision-detection"> 223 <a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a> 224 </li> 225 <li id="In-Practice/2D-Game/Collisions/Collision-resolution"> 226 <a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a> 227 </li> 228 </ol> 229 </li> 230 <li id="In-Practice/2D-Game/Particles"> 231 <a href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a> 232 </li> 233 <li id="In-Practice/2D-Game/Postprocessing"> 234 <a href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a> 235 </li> 236 <li id="In-Practice/2D-Game/Powerups"> 237 <a href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a> 238 </li> 239 <li id="In-Practice/2D-Game/Audio"> 240 <a href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a> 241 </li> 242 <li id="In-Practice/2D-Game/Render-text"> 243 <a href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a> 244 </li> 245 <li id="In-Practice/2D-Game/Final-thoughts"> 246 <a href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a> 247 </li> 248 </ol> 249 </li> 250 </ol> 251 </li> 252 <li id="Guest-Articles"> 253 <span class="closed">Guest Articles </span> 254 <ol> 255 <li id="Guest-Articles/How-to-publish"> 256 <a href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a> 257 </li> 258 <li id="Guest-Articles/2020"> 259 <span class="closed">2020 </span> 260 <ol> 261 <li id="Guest-Articles/2020/OIT"> 262 <span class="closed">OIT </span> 263 <ol> 264 <li id="Guest-Articles/2020/OIT/Introduction"> 265 <a href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a> 266 </li> 267 <li id="Guest-Articles/2020/OIT/Weighted-Blended"> 268 <a href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a> 269 </li> 270 </ol> 271 </li> 272 <li id="Guest-Articles/2020/Skeletal-Animation"> 273 <a href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a> 274 </li> 275 </ol> 276 </li> 277 <li id="Guest-Articles/2021"> 278 <span class="closed">2021 </span> 279 <ol> 280 <li id="Guest-Articles/2021/CSM"> 281 <a href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a> 282 </li> 283 <li id="Guest-Articles/2021/Scene"> 284 <span class="closed">Scene </span> 285 <ol> 286 <li id="Guest-Articles/2021/Scene/Scene-Graph"> 287 <a href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a> 288 </li> 289 <li id="Guest-Articles/2021/Scene/Frustum-Culling"> 290 <a href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a> 291 </li> 292 </ol> 293 </li> 294 <li id="Guest-Articles/2021/Tessellation"> 295 <span class="closed">Tessellation </span> 296 <ol> 297 <li id="Guest-Articles/2021/Tessellation/Height-map"> 298 <a href="https://learnopengl.com/Guest-Articles/2021/Tessellation/Height-map">Height map </a> 299 </li> 300 </ol> 301 </li> 302 </ol> 303 </li> 304 </ol> 305 </li> 306 <li id="Code-repository"> 307 <a href="https://learnopengl.com/Code-repository">Code repository </a> 308 </li> 309 <li id="Translations"> 310 <a href="https://learnopengl.com/Translations">Translations </a> 311 </li> 312 <li id="About"> 313 <a href="https://learnopengl.com/About">About </a> 314 </li> 315 </ol> 316 </nav> 317 <main> 318 <div id="content"> 319 <h1 id="content-title">Skeletal Animation</h1> 320 <h1 id="content-url" style='display:none;'>Guest-Articles/2020/Skeletal-Animation</h1> 321 <p>3D Animations can bring our games to life. Objects in 3D world like humans and & 322 animals feel more organic when they move their limbs to do certain things like walking, running & attacking. 323 This tutorial is about Skeletal animation which you all have been waiting for. We will first understand the concept thoroughly and then understand the data 324 we need to animate a 3D model using Assimp. I'd recommend you to finish the <a href="https://learnopengl.com/Model-Loading/Assimp">Model Loading</a> chapter of this saga as this tutorial code continues from there. You can still understand the concept and implement it in your way. So let's get started.</p> 325 326 327 <h3>Interpolation</h3> 328 <p>To understand how animation works at basic level we need to understand the concept of Interpolation. 329 Interpolation can be defined as something happening over time. Like an enemy moving from point A to point B in time T i.e Translation happening over time . 330 A gun turret smoothly rotates to face the target i.e Rotation happening over time and a tree is scaling up from size A to size B in time T i.e Scaling happening over time.</p> 331 <p>A simple interpolation equation used for Translation and Scale looks like this..</p> 332 <p style="text-align: center;"><strong>a = a * (1 - t) + b * t </strong></p> 333 <p>It is known as as Linear Interpolation equation or Lerp. For Rotation we cannot use Vector. 334 The reason for that is if we went ahead and tried to use the linear interpolation equation on a vector of X(Pitch),Y(Yaw) & Z(Roll), the interpolation won't be linear. 335 You will encounter 336 weird issues like The 337 Gimbal Lock(See references section below to learn about it). To avoid this issue we use Quaternion for rotations. 338 Quaternion provides something called The Spherical Interpolation or 339 Slerp equation which gives the same result as Lerp but for two rotations A & B. 340 I won't be able to explain how the equation works because its out of the scope for now. You can surely checkout references section below to 341 understand The Quaternion. 342 </p> 343 <h3>Components of An Animated Model : Skin, Bones and Keyframes</h3> 344 <p>The whole process of an animation starts with the addition of the first component which is The Skin in a software like blender or Maya. 345 Skin is nothing but meshes which add visual aspect to the model to tell the viewer how it looks like. 346 But If you want to move any mesh then just like the real world, you need to add Bones. You can see the images below to understand how it looks in software like blender....</p> 347 <p> </p> 348 <img src="/img/guest/2020/skeletal_animation/skin.png" alt="skin" width="300" height="300" class="clean"> <img src="/img/guest/2020/skeletal_animation/bones.png" alt="bones" width="300" height="300"class="clean"> <img src="/img/guest/2020/skeletal_animation/merged.png" alt="skin and bones" width="300" height="300"class="clean"> 349 <p>These bones are usually added in hierarchical fashion for characters like humans & animals and the reason is pretty obvious. We want parent-child relationship among limbs. 350 For example, If we move our right shoulder then our right bicep, forearm, hand and fingers should move as well. This is how the hierarchy looks like....</p> 351 <p> </p> 352 353 <p> 354 <img src="/img/guest/2020/skeletal_animation/parent_child.png" alt="" width="853" height="425"/></p> 355 356 <p>In the above diagram if you grab the hip bone and move it, all limbs will be affected by its movement.</p> 357 <p>At this point, we are ready to create KeyFrames for an animation. Keyframes are poses at different point of time in an animation. We will interpolate between 358 these Keyframes to go from one pose to another pose smoothly in our code. Below you can see how poses are created for a simple 4 frame jump animation...</p> 359 <p><img src="/img/guest/2020/skeletal_animation/poses.gif" width="300px" class="clean" alt=""/> <img src="/img/guest/2020/skeletal_animation/interpolating.gif" class="clean" width="300px" alt="Interpolation bw frames" hspace="20"/></p> 360 <p> </p> 361 <h3>How Assimp holds animation data</h3> 362 <p>We are almost there to the code part but first we need to understand how assimp holds imported animation data. Look at the diagram below..</p> 363 <p><img src="/img/guest/2020/skeletal_animation/assimp1.jpeg" alt="" width="710" height="800"/></p> 364 <p>Just like in the <a href="https://learnopengl.com/Model-Loading/Assimp">Model Loading</a> chapter, we will start with the <code>aiScene</code> pointer 365 which holds a pointer to the root node and look what do we have here, an array of Animations. 366 This array of <code>aiAnimation</code> contains the general information like duration of an animation represented here as 367 <code>mDuration</code> and then we have a <code>mTicksPerSecond</code> variable, which controls how fast 368 we should interpolate between frames. If you remember from the last section that an animation has keyframes. 369 Similary, an <code>aiAnimation</code> contains an <code>aiNodeAnim</code> array called Channels. 370 This array of contains all bones and their keyframes which are going to be engaged in an animation. 371 372 An <code>aiNodeAnim</code> contains name of the bone and you 373 will find 3 types of keys to interpolate between here, Translation,Rotation & Scale.</p> 374 375 <p>Alright, there's one last thing we need to understand and we are good to go for writing some code.</p> 376 377 <p> </p> 378 <h3>Influence of multiple bones on vertices</h3> 379 <p>When we curl our forearm and we see our biceps muscle pop up. We can also say that forearm bone transformation is affecting vertices on our biceps. 380 Similary, there could be multiple bones affecting a single vertex in a mesh. 381 For characters like solid metal robots all forearm vertices will only be affected by forearm bone but for characters like humans, animals etc, there could be 382 upto 4 bones which can affect a vertex. Let's see how assimp stores that information...</p> 383 <p> </p> 384 <p><img src="/img/guest/2020/skeletal_animation/assimp2.jpeg" alt="" width="760" height="860"/></p> 385 <p> </p> 386 <p>We start with the <code>aiScene</code> pointer again which contains an array of all aiMeshes. 387 Each <code>aiMesh</code> object has an array of <code>aiBone</code> which contains the information like 388 how much influence this <code>aiBone</code> will have on set of vertices on the mesh. 389 aiBone contains the name of the bone, an array of <code>aiVertexWeight</code> which basically 390 tells us how much influence this <code>aiBone</code> will have on what vertices on the mesh. 391 Now we have one more member of <code>aiBone</code> which is offsetMatrix. It's a 4x4 matrix 392 used to transform vertices from model space to their bone space. 393 You can see this in action in images below....</p> 394 395 <img src="/img/guest/2020/skeletal_animation/mesh_space.png" class="clean" alt="Mesh Space" style="width:50%"> 396 <img src="/img/guest/2020/skeletal_animation/bone_space.png" class="clean" alt="Bone Space" style="width:50%"> 397 <p> 398 When vertices are in bone space they will be transformed relative to their bone 399 as they are supposed to. You will soon see this in action 400 in code. 401 </p> 402 403 <h3>Finally! Let's code.</h3> 404 <p>Thank you for making it this far. We will start with directly looking at the end result which is our final vertex 405 shader code. This will give us good sense what we need at the end.. </p> 406 407 <pre><code>#version 430 core 408 409 layout(location = 0) in vec3 pos; 410 layout(location = 1) in vec3 norm; 411 layout(location = 2) in vec2 tex; 412 layout(location = 3) in ivec4 boneIds; 413 layout(location = 4) in vec4 weights; 414 415 uniform mat4 projection; 416 uniform mat4 view; 417 uniform mat4 model; 418 419 const int MAX_BONES = 100; 420 const int MAX_BONE_INFLUENCE = 4; 421 uniform mat4 finalBonesMatrices[MAX_BONES]; 422 423 out vec2 TexCoords; 424 425 void main() 426 { 427 vec4 totalPosition = vec4(0.0f); 428 for(int i = 0 ; i < MAX_BONE_INFLUENCE ; i++) 429 { 430 if(boneIds[i] == -1) 431 continue; 432 if(boneIds[i] >= MAX_JOINTS) 433 { 434 totalPosition = vec4(pos,1.0f); 435 break; 436 } 437 vec4 localPosition = finalBoneMatrices[boneIds[i]] * vec4(pos,1.0f); 438 totalPosition += localPosition * weights[i]; 439 vec3 localNormal = mat3(finalBoneMatrices[boneIds[i]]) * norm; 440 } 441 442 mat4 viewModel = view * model; 443 gl_Position = projection * viewModel * totalPosition; 444 TexCoords = tex; 445 } 446 </code></pre> 447 448 <p> 449 Fragment shader remains the same from the <a href="https://learnopengl.com/Model-Loading/Model">model loading</a> chapter. 450 Starting from the top you see two new attributes layout declaration. 451 First <code>boneIds</code> and second is <code>weights</code>. we also have 452 a uniform array <code>finalBonesMatrices</code> which stores transformations of all bones. 453 <code>boneIds</code> contains indices which are used to read the <code>finalBonesMatrices</code> 454 array and apply those transformation to <code>pos</code> vertex with their respective weights 455 stored in <code> weights </code> array. This happens inside <code> for </code> loop above. 456 Now let's add support in our <code>Mesh</code> class for bone weights first.. 457 </p> 458 459 <pre><code>#define MAX_BONE_INFLUENCE 4 460 461 struct Vertex { 462 // position 463 glm::vec3 Position; 464 // normal 465 glm::vec3 Normal; 466 // texCoords 467 glm::vec2 TexCoords; 468 469 //bone indexes which will influence this vertex 470 int m_BoneIDs[MAX_BONE_INFLUENCE]; 471 472 //weights from each bone 473 float m_Weights[MAX_BONE_INFLUENCE]; 474 }; 475 </code></pre> 476 477 <p> 478 We have added two new attributes for the <code>Vertex</code>, just like we saw in our vertex shader. 479 Now's let's load them in GPU buffers just like other attributes in our <code>Mesh::setupMesh </code> function... 480 </p> 481 482 <pre><code>class Mesh 483 { 484 ... 485 486 void setupMesh() 487 { 488 ... 489 490 // ids 491 <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(3); 492 glVertexAttribIPointer(3, 4, GL_INT, sizeof(Vertex), 493 (void*)offsetof(Vertex, m_BoneIDs)); 494 495 // weights 496 <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(4); 497 <function id='30'>glVertexAttribPointer</function>(4, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), 498 (void*)offsetof(Vertex, m_Weights)); 499 500 ... 501 } 502 ... 503 } 504 </code></pre> 505 <p>Just like before, except now we have added 3 and 4 layout location ids for <code>boneIds</code> and <code>weights</code>. One imporant thing to notice here is how we are passing data for <code>boneIds</code>. We are using <code>glVertexAttribIPointer</code> and we passed GL_INT as third parameter. </p> 506 <p>Now we can extract the bone-weight information from the assimp data structure. Let's make some changes in Model class...</p> 507 508 <pre><code>struct BoneInfo 509 { 510 /*id is index in finalBoneMatrices*/ 511 int id; 512 513 /*offset matrix transforms vertex from model space to bone space*/ 514 glm::mat4 offset; 515 516 }; 517 </code></pre> 518 519 <p> This <code> BoneInfo </code> will store our offset matrix and also a unique id which will 520 be used as an index to store it in <code>finalBoneMatrices</code> array we saw earlier in our shader. 521 Now we will add bone weight extraction support in <code>Model</code>... </p> 522 523 <pre><code>class Model 524 { 525 private: 526 ... 527 std::map<string, BoneInfo> m_BoneInfoMap; // 528 int m_BoneCounter = 0; 529 530 ... 531 void SetVertexBoneDataToDefault(Vertex& vertex) 532 { 533 for (int i = 0; i < MAX_BONE_WEIGHTS; i++) 534 { 535 vertex.m_BoneIDs[i] = -1; 536 vertex.m_Weights[i] = 0.0f; 537 } 538 } 539 540 Mesh processMesh(aiMesh* mesh, const aiScene* scene) 541 { 542 vector vertices; 543 vector indices; 544 vector textures; 545 546 for (unsigned int i = 0; i < mesh->mNumVertices; i++) 547 { 548 Vertex vertex; 549 550 SetVertexBoneDataToDefault(vertex); 551 552 vertex.Position = AssimpGLMHelpers::GetGLMVec(mesh->mVertices[i]); 553 vertex.Normal = AssimpGLMHelpers::GetGLMVec(mesh->mNormals[i]); 554 555 if (mesh->mTextureCoords[0]) 556 { 557 glm::vec2 vec; 558 vec.x = mesh->mTextureCoords[0][i].x; 559 vec.y = mesh->mTextureCoords[0][i].y; 560 vertex.TexCoords = vec; 561 } 562 else 563 vertex.TexCoords = glm::vec2(0.0f, 0.0f); 564 565 vertices.push_back(vertex); 566 } 567 ... 568 ExtractBoneWeightForVertices(vertices,mesh,scene); 569 570 return Mesh(vertices, indices, textures); 571 } 572 573 void SetVertexBoneData(Vertex& vertex, int boneID, float weight) 574 { 575 for (int i = 0; i < MAX_BONE_WEIGHTS; ++i) 576 { 577 if (vertex.m_BoneIDs[i] < 0) 578 { 579 vertex.m_Weights[i] = weight; 580 vertex.m_BoneIDs[i] = boneID; 581 break; 582 } 583 } 584 } 585 586 void ExtractBoneWeightForVertices(std::vector& vertices, aiMesh* mesh, 587 const aiScene* scene) 588 { 589 for (int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) 590 { 591 int boneID = -1; 592 std::string boneName = mesh->mBones[boneIndex]->mName.C_Str(); 593 if (m_BoneInfoMap.find(boneName) == m_BoneInfoMap.end()) 594 { 595 BoneInfo newBoneInfo; 596 newBoneInfo.id = m_BoneCounter; 597 newBoneInfo.offset = AssimpGLMHelpers:: 598 ConvertMatrixToGLMFormat(mesh->mBones[boneIndex]->mOffsetMatrix); 599 m_BoneInfoMap[boneName] = newBoneInfo; 600 boneID = m_BoneCounter; 601 m_BoneCounter++; 602 } 603 else 604 { 605 boneID = m_BoneInfoMap[boneName].id; 606 } 607 assert(boneID != -1); 608 auto weights = mesh->mBones[boneIndex]->mWeights; 609 int numWeights = mesh->mBones[boneIndex]->mNumWeights; 610 611 for (int weightIndex = 0; weightIndex < numWeights; ++weightIndex) 612 { 613 int vertexId = weights[weightIndex].mVertexId; 614 float weight = weights[weightIndex].mWeight; 615 assert(vertexId <= vertices.size()); 616 SetVertexBoneData(vertices[vertexId], boneID, weight); 617 } 618 } 619 } 620 ....... 621 }; 622 </code></pre> 623 624 <p>We start by declaring a map <code>m_BoneInfoMap</code> and a counter <code>m_BoneCounter</code> 625 which will be incremented as soon as we read a new bone. 626 we saw in the diagram earlier that each <code>aiMesh</code> contains all 627 aiBones which are associated with the <code>aiMesh</code>. 628 The whole process of the bone-weight extraction starts from the 629 <code> processMesh </code> 630 function. For each loop iteration we are setting <code>m_BoneIDs</code> and <code>m_Weights</code> to 631 their default values 632 by calling function <code>SetVertexBoneDataToDefault</code>. 633 Just before the <code>processMesh</code> function ends, we call the 634 <code>ExtractBoneWeightData</code>. In the <code>ExtractBoneWeightData</code> we run 635 a for loop for each <code>aiBone</code> and check if this bone already exists in the <code>m_BoneInfoMap</code>. 636 If we couldn't find it then it's considered a new bone and we create new <code>BoneInfo</code> 637 with an id and store its associated <code>mOffsetMatrix</code> to it. Then we store this new <code>BoneInfo</code> 638 in <code>m_BoneInfoMap</code> and then we increment the <code>m_BoneCounter</code> counter to create 639 an id for next bone. In case we find the bone name in <code>m_BoneInfoMap</code> then 640 that means this bone affects vertices of mesh out of 641 its scope. So we take it's Id and proceed further to know which vertices it affects. </p> 642 643 <p> One thing to notice that we are calling <code>AssimpGLMHelpers::ConvertMatrixToGLMFormat</code>. 644 Assimp store its matrix data in different format than GLM so this function just gives us our matrix in GLM format. 645 </p> 646 <p>We have extracted the offsetMatrix for the bone and now we will simply iterate its <code>aiVertexWeight</code>array 647 and extract all vertices indices which will be influenced by this bone along with their 648 respective weights and call <code>SetVertexBoneData</code> to fill up <code>Vertex.boneIds</code> and <code>Vertex.weights</code> with extracted information. </p> 649 650 <p>Phew! You deserve a coffee break at this point. </p> 651 652 <h3>Bone,Animation & Animator classes</h3> 653 <p>Here's high level view of classes..</p> 654 655 <p><img src="/img/guest/2020/skeletal_animation/bird_eye_view.png" class="clean" alt="" width="700"/></p> 656 657 <p> Let us remind ourselves what we are trying to achieve. For each rendering frame we want to interpolate all bones in heirarchy smoothly and get their final transformations matrices which will be supplied to shader 658 uniform <code>finalBonesMatrices</code>. 659 Here's what each class does... 660 661 <p><b>Bone</b> : A single bone which reads all keyframes data from <code>aiNodeAnim</code>. It will also interpolate between its keys i.e Translation,Scale & Rotation based on the current animation time. </p> 662 <p><b>AssimpNodeData</b> : This struct will help us to isolate our <code><b>Animation</b> from Assimp. </code> </p> 663 <p><b>Animation</b> : An asset which reads data from aiAnimation and create a heirarchical record of <code><b>Bone</b></code>s </p> 664 <p><b>Animator</b> : This will read the heirarchy of <code>AssimpNodeData</code>, 665 Interpolate all bones in a recursive manner and then prepare final bone transformation matrices for us that we need. 666 667 </p> 668 669 <p> 670 Here's the code for <code>Bone</code>... 671 672 <pre><code>struct KeyPosition 673 { 674 glm::vec3 position; 675 float timeStamp; 676 }; 677 678 struct KeyRotation 679 { 680 glm::quat orientation; 681 float timeStamp; 682 }; 683 684 struct KeyScale 685 { 686 glm::vec3 scale; 687 float timeStamp; 688 }; 689 690 class Bone 691 { 692 private: 693 std::vector<KeyPosition> m_Positions; 694 std::vector<KeyRotation> m_Rotations; 695 std::vector<KeyScale> m_Scales; 696 int m_NumPositions; 697 int m_NumRotations; 698 int m_NumScalings; 699 700 glm::mat4 m_LocalTransform; 701 std::string m_Name; 702 int m_ID; 703 public: 704 /*reads keyframes from aiNodeAnim*/ 705 Bone(const std::string& name, int ID, const aiNodeAnim* channel) 706 : 707 m_Name(name), 708 m_ID(ID), 709 m_LocalTransform(1.0f) 710 { 711 m_NumPositions = channel->mNumPositionKeys; 712 713 for (int positionIndex = 0; positionIndex < m_NumPositions; ++positionIndex) 714 { 715 aiVector3D aiPosition = channel->mPositionKeys[positionIndex].mValue; 716 float timeStamp = channel->mPositionKeys[positionIndex].mTime; 717 KeyPosition data; 718 data.position = AssimpGLMHelpers::GetGLMVec(aiPosition); 719 data.timeStamp = timeStamp; 720 m_Positions.push_back(data); 721 } 722 723 m_NumRotations = channel->mNumRotationKeys; 724 for (int rotationIndex = 0; rotationIndex < m_NumRotations; ++rotationIndex) 725 { 726 aiQuaternion aiOrientation = channel->mRotationKeys[rotationIndex].mValue; 727 float timeStamp = channel->mRotationKeys[rotationIndex].mTime; 728 KeyRotation data; 729 data.orientation = AssimpGLMHelpers::GetGLMQuat(aiOrientation); 730 data.timeStamp = timeStamp; 731 m_Rotations.push_back(data); 732 } 733 734 m_NumScalings = channel->mNumScalingKeys; 735 for (int keyIndex = 0; keyIndex < m_NumScalings; ++keyIndex) 736 { 737 aiVector3D scale = channel->mScalingKeys[keyIndex].mValue; 738 float timeStamp = channel->mScalingKeys[keyIndex].mTime; 739 KeyScale data; 740 data.scale = AssimpGLMHelpers::GetGLMVec(scale); 741 data.timeStamp = timeStamp; 742 m_Scales.push_back(data); 743 } 744 } 745 746 /* Interpolates b/w positions,rotations & scaling keys based on the curren time of the 747 animation and prepares the local transformation matrix by combining all keys tranformations */ 748 void Update(float animationTime) 749 { 750 glm::mat4 translation = InterpolatePosition(animationTime); 751 glm::mat4 rotation = InterpolateRotation(animationTime); 752 glm::mat4 scale = InterpolateScaling(animationTime); 753 m_LocalTransform = translation * rotation * scale; 754 } 755 756 glm::mat4 GetLocalTransform() { return m_LocalTransform; } 757 std::string GetBoneName() const { return m_Name; } 758 int GetBoneID() { return m_ID; } 759 760 /* Gets the current index on mKeyPositions to interpolate to based on the current 761 animation time */ 762 int GetPositionIndex(float animationTime) 763 { 764 for (int index = 0; index < m_NumPositions - 1; ++index) 765 { 766 if (animationTime < m_Positions[index + 1].timeStamp) 767 return index; 768 } 769 assert(0); 770 } 771 772 /* Gets the current index on mKeyRotations to interpolate to based on the current 773 animation time */ 774 int GetRotationIndex(float animationTime) 775 { 776 for (int index = 0; index < m_NumRotations - 1; ++index) 777 { 778 if (animationTime < m_Rotations[index + 1].timeStamp) 779 return index; 780 } 781 assert(0); 782 } 783 784 /* Gets the current index on mKeyScalings to interpolate to based on the current 785 animation time */ 786 int GetScaleIndex(float animationTime) 787 { 788 for (int index = 0; index < m_NumScalings - 1; ++index) 789 { 790 if (animationTime < m_Scales[index + 1].timeStamp) 791 return index; 792 } 793 assert(0); 794 } 795 private: 796 797 /* Gets normalized value for Lerp & Slerp*/ 798 float GetScaleFactor(float lastTimeStamp, float nextTimeStamp, float animationTime) 799 { 800 float scaleFactor = 0.0f; 801 float midWayLength = animationTime - lastTimeStamp; 802 float framesDiff = nextTimeStamp - lastTimeStamp; 803 scaleFactor = midWayLength / framesDiff; 804 return scaleFactor; 805 } 806 807 /* figures out which position keys to interpolate b/w and performs the interpolation 808 and returns the translation matrix */ 809 glm::mat4 InterpolatePosition(float animationTime) 810 { 811 if (1 == m_NumPositions) 812 return <function id='55'>glm::translate</function>(glm::mat4(1.0f), m_Positions[0].position); 813 814 int p0Index = GetPositionIndex(animationTime); 815 int p1Index = p0Index + 1; 816 float scaleFactor = GetScaleFactor(m_Positions[p0Index].timeStamp, 817 m_Positions[p1Index].timeStamp, animationTime); 818 glm::vec3 finalPosition = glm::mix(m_Positions[p0Index].position, 819 m_Positions[p1Index].position 820 , scaleFactor); 821 return <function id='55'>glm::translate</function>(glm::mat4(1.0f), finalPosition); 822 } 823 824 /* figures out which rotations keys to interpolate b/w and performs the interpolation 825 and returns the rotation matrix */ 826 glm::mat4 InterpolateRotation(float animationTime) 827 { 828 if (1 == m_NumRotations) 829 { 830 auto rotation = glm::normalize(m_Rotations[0].orientation); 831 return glm::toMat4(rotation); 832 } 833 834 int p0Index = GetRotationIndex(animationTime); 835 int p1Index = p0Index + 1; 836 float scaleFactor = GetScaleFactor(m_Rotations[p0Index].timeStamp, 837 m_Rotations[p1Index].timeStamp, animationTime); 838 glm::quat finalRotation = glm::slerp(m_Rotations[p0Index].orientation, 839 m_Rotations[p1Index].orientation, scaleFactor); 840 finalRotation = glm::normalize(finalRotation); 841 return glm::toMat4(finalRotation); 842 } 843 844 /* figures out which scaling keys to interpolate b/w and performs the interpolation 845 and returns the scale matrix */ 846 glm::mat4 Bone::InterpolateScaling(float animationTime) 847 { 848 if (1 == m_NumScalings) 849 return <function id='56'>glm::scale</function>(glm::mat4(1.0f), m_Scales[0].scale); 850 851 int p0Index = GetScaleIndex(animationTime); 852 int p1Index = p0Index + 1; 853 float scaleFactor = GetScaleFactor(m_Scales[p0Index].timeStamp, 854 m_Scales[p1Index].timeStamp, animationTime); 855 glm::vec3 finalScale = glm::mix(m_Scales[p0Index].scale, 856 m_Scales[p1Index].scale, scaleFactor); 857 return <function id='56'>glm::scale</function>(glm::mat4(1.0f), finalScale); 858 } 859 }; 860 </code></pre> 861 862 <p> 863 864 We start by creating 3 structs for our key types. Each struct holds a value and a time stamp. Timestamp tells us at what point of an animation we need to interpolate to its value. 865 <code>Bone</code> has a constructor which reads from <code>aiNodeAnim</code> and stores keys and their timestamps to <code>mPositionKeys, mRotationKeys & mScalingKeys </code>. The main interpolation process 866 starts from <code>Update(float animationTime)</code> which gets called every frame. This function calls respective interpolation functions for all key types and combines all final interpolation results 867 and store it to a 4x4 Matrix <code>m_LocalTransform</code>. The interpolations functions for translation & scale keys are similar but for rotation we are using <code>Slerp</code> to interpolate between quaternions. 868 Both <code>Lerp</code> & <code>Slerp</code> takes 3 arguments. First argument takes last key, second argument takes next key and third argument takes value of range 0-1,we call it scale factor here. 869 Let's see how we calculate this scale factor in function <code>GetScaleFactor</code>... 870 871 <p><img src="/img/guest/2020/skeletal_animation/scale_factor.png" alt="skin"/></p> 872 873 <p>In code...</p> 874 875 <p><b> float midWayLength = animationTime - lastTimeStamp; </b></p> 876 <p><b> float framesDiff = nextTimeStamp - lastTimeStamp;</b></p> 877 <p><b> scaleFactor = midWayLength / framesDiff; </b></p> 878 <p></p> 879 </p> 880 881 Let's move on to <code><b>Animation</b></code> class now... 882 883 <pre><code>struct AssimpNodeData 884 { 885 glm::mat4 transformation; 886 std::string name; 887 int childrenCount; 888 std::vector<AssimpNodeData> children; 889 }; 890 891 class Animation 892 { 893 public: 894 Animation() = default; 895 896 Animation(const std::string& animationPath, Model* model) 897 { 898 Assimp::Importer importer; 899 const aiScene* scene = importer.ReadFile(animationPath, aiProcess_Triangulate); 900 assert(scene && scene->mRootNode); 901 auto animation = scene->mAnimations[0]; 902 m_Duration = animation->mDuration; 903 m_TicksPerSecond = animation->mTicksPerSecond; 904 ReadHeirarchyData(m_RootNode, scene->mRootNode); 905 ReadMissingBones(animation, *model); 906 } 907 908 ~Animation() 909 { 910 } 911 912 Bone* FindBone(const std::string& name) 913 { 914 auto iter = std::find_if(m_Bones.begin(), m_Bones.end(), 915 [&](const Bone& Bone) 916 { 917 return Bone.GetBoneName() == name; 918 } 919 ); 920 if (iter == m_Bones.end()) return nullptr; 921 else return &(*iter); 922 } 923 924 925 inline float GetTicksPerSecond() { return m_TicksPerSecond; } 926 927 inline float GetDuration() { return m_Duration;} 928 929 inline const AssimpNodeData& GetRootNode() { return m_RootNode; } 930 931 inline const std::map<std::string,BoneInfo>& GetBoneIDMap() 932 { 933 return m_BoneInfoMap; 934 } 935 936 private: 937 void ReadMissingBones(const aiAnimation* animation, Model& model) 938 { 939 int size = animation->mNumChannels; 940 941 auto& boneInfoMap = model.GetBoneInfoMap();//getting m_BoneInfoMap from Model class 942 int& boneCount = model.GetBoneCount(); //getting the m_BoneCounter from Model class 943 944 //reading channels(bones engaged in an animation and their keyframes) 945 for (int i = 0; i < size; i++) 946 { 947 auto channel = animation->mChannels[i]; 948 std::string boneName = channel->mNodeName.data; 949 950 if (boneInfoMap.find(boneName) == boneInfoMap.end()) 951 { 952 boneInfoMap[boneName].id = boneCount; 953 boneCount++; 954 } 955 m_Bones.push_back(Bone(channel->mNodeName.data, 956 boneInfoMap[channel->mNodeName.data].id, channel)); 957 } 958 959 m_BoneInfoMap = boneInfoMap; 960 } 961 962 void ReadHeirarchyData(AssimpNodeData& dest, const aiNode* src) 963 { 964 assert(src); 965 966 dest.name = src->mName.data; 967 dest.transformation = AssimpGLMHelpers::ConvertMatrixToGLMFormat(src->mTransformation); 968 dest.childrenCount = src->mNumChildren; 969 970 for (int i = 0; i < src->mNumChildren; i++) 971 { 972 AssimpNodeData newData; 973 ReadHeirarchyData(newData, src->mChildren[i]); 974 dest.children.push_back(newData); 975 } 976 } 977 float m_Duration; 978 int m_TicksPerSecond; 979 std::vector<Bone> m_Bones; 980 AssimpNodeData m_RootNode; 981 std::map<std::string, BoneInfo> m_BoneInfoMap; 982 }; 983 </code></pre> 984 985 <p> Here, creation of an <code>Animation</code> object starts with a constructor. It takes two arguments. First, path to the animation file & second parameter is the <code>Model</code> for this animation. 986 You will see later ahead why we need this <code>Model</code> reference here. We then create an <code>Assimp::Importer</code> to read the animation file, followed by an <code>assert</code> check which will throw 987 an error if animation could not be found. Then we read general animation data like how long is this animation which is <code>mDuration</code> and the animation speed represented by <code>mTicksPerSecond</code>. 988 We then call <code>ReadHeirarchyData</code> which replicates <code>aiNode</code> heirarchy of Assimp and creates heirarchy of <code>AssimpNodeData</code>. 989 </p> 990 991 <p> Then we call a function called <code>ReadMissingBones</code>. I had to write this function because sometimes when I loaded FBX model separately, it had some bones missing and I found those missing bones in 992 the animation file. This function reads the missing bones information and stores their information in <code>m_BoneInfoMap</code> of <code>Model</code> and saves a reference of <code>m_BoneInfoMap</code> locally in 993 the m_BoneInfoMap.</p> 994 995 <p>And we have our animation ready. Now let's look at our final stage, The Animator class...</p> 996 997 <pre><code>class Animator 998 { 999 public: 1000 Animator::Animator(Animation* Animation) 1001 { 1002 m_CurrentTime = 0.0; 1003 m_CurrentAnimation = currentAnimation; 1004 1005 m_FinalBoneMatrices.reserve(100); 1006 1007 for (int i = 0; i < 100; i++) 1008 m_FinalBoneMatrices.push_back(glm::mat4(1.0f)); 1009 } 1010 1011 void Animator::UpdateAnimation(float dt) 1012 { 1013 m_DeltaTime = dt; 1014 if (m_CurrentAnimation) 1015 { 1016 m_CurrentTime += m_CurrentAnimation->GetTicksPerSecond() * dt; 1017 m_CurrentTime = fmod(m_CurrentTime, m_CurrentAnimation->GetDuration()); 1018 CalculateBoneTransform(&m_CurrentAnimation->GetRootNode(), glm::mat4(1.0f)); 1019 } 1020 } 1021 1022 void Animator::PlayAnimation(Animation* pAnimation) 1023 { 1024 m_CurrentAnimation = pAnimation; 1025 m_CurrentTime = 0.0f; 1026 } 1027 1028 void Animator::CalculateBoneTransform(const AssimpNodeData* node, glm::mat4 parentTransform) 1029 { 1030 std::string nodeName = node->name; 1031 glm::mat4 nodeTransform = node->transformation; 1032 1033 Bone* Bone = m_CurrentAnimation->FindBone(nodeName); 1034 1035 if (Bone) 1036 { 1037 Bone->Update(m_CurrentTime); 1038 nodeTransform = Bone->GetLocalTransform(); 1039 } 1040 1041 glm::mat4 globalTransformation = parentTransform * nodeTransform; 1042 1043 auto boneInfoMap = m_CurrentAnimation->GetBoneIDMap(); 1044 if (boneInfoMap.find(nodeName) != boneInfoMap.end()) 1045 { 1046 int index = boneInfoMap[nodeName].id; 1047 glm::mat4 offset = boneInfoMap[nodeName].offset; 1048 m_FinalBoneMatrices[index] = globalTransformation * offset; 1049 } 1050 1051 for (int i = 0; i < node->childrenCount; i++) 1052 CalculateBoneTransform(&node->children[i], globalTransformation); 1053 } 1054 1055 std::vector<glm::mat4> GetFinalBoneMatrices() 1056 { 1057 return m_FinalBoneMatrices; 1058 } 1059 1060 private: 1061 std::vector<glm::mat4> m_FinalBoneMatrices; 1062 Animation* m_CurrentAnimation; 1063 float m_CurrentTime; 1064 float m_DeltaTime; 1065 }; 1066 </code></pre> 1067 1068 <p> 1069 1070 <code>Animator</code> constructor takes an animation to play and 1071 then it proceeds to reset the animation time <code>m_CurrentTime</code> to 0. 1072 It also initializes <code>m_FinalBoneMatrices</code> 1073 which is a <code>std::vector<glm::mat4></code>. 1074 The main point of attention here is <code>UpdateAnimation(float deltaTime)</code> function. 1075 It advances the <code>m_CurrentTime</code> with rate of 1076 <code>m_TicksPerSecond</code> and then calls the <code>CalculateBoneTransform</code> function. 1077 We will pass two arguments in the start, first is the <code>m_RootNode</code> of <code>m_CurrentAnimation</code> 1078 and second is an identity matrix passed as <code>parentTransform</code> This function then check if <code>m_RootNode</code>s bone is engaged in this animation by finding it in <code>m_Bones</code> array of <code>Animation</code>. 1079 If bone is found then it calls <code>Bone.Update()</code> function which interpolates all bones and return local bone transform matrix to 1080 <code>nodeTransform</code>. 1081 But this is local space matrix and will move bone around origin if passed in shaders. So we multiply this <code>nodeTransform</code> with <code>parentTransform</code> and 1082 we store the result in <code>globalTransformation</code>. This would be enough but vertices are still in default model space. 1083 we find offset matrix in <code>m_BoneInfoMap</code> and then multiply it 1084 with <code>globalTransfromMatrix</code>. 1085 We will also get the id index which will be used to write final transformation of this bone to m_FinalBoneMatrices. 1086 </p> 1087 1088 <p> 1089 Finally! we call <code>CalculateBoneTransform</code> for each child nodes of this node and pass <code>globalTransformation</code> as <code>parentTransform</code>. 1090 We break this recursive loop when there will no children 1091 left to process further. 1092 </p> 1093 1094 </p> 1095 </p> 1096 1097 <h3> Let's Animate</h3> 1098 1099 <p> 1100 Fruit of our hardwork is finally here! Here's how we will play the animation in <code>main.cpp</code> ... 1101 </p> 1102 1103 <pre><code>int main() 1104 { 1105 ... 1106 1107 Model ourModel(FileSystem::getPath("resources/objects/vampire/dancing_vampire.dae")); 1108 Animation danceAnimation(FileSystem::getPath( 1109 "resources/objects/vampire/dancing_vampire.dae"), &ourModel); 1110 Animator animator(&danceAnimation); 1111 1112 // draw in wireframe 1113 //<function id='43'>glPolygonMode</function>(GL_FRONT_AND_BACK, GL_LINE); 1114 1115 // render loop 1116 // ----------- 1117 while (!<function id='14'>glfwWindowShouldClose</function>(window)) 1118 { 1119 // per-frame time logic 1120 // -------------------- 1121 float currentFrame = <function id='47'>glfwGetTime</function>(); 1122 deltaTime = currentFrame - lastFrame; 1123 lastFrame = currentFrame; 1124 1125 // input 1126 // ----- 1127 processInput(window); 1128 animator.UpdateAnimation(deltaTime); 1129 1130 // render 1131 // ------ 1132 <function id='13'><function id='10'>glClear</function>Color</function>(0.05f, 0.05f, 0.05f, 1.0f); 1133 <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 1134 1135 // don't forget to enable shader before setting uniforms 1136 ourShader.use(); 1137 1138 // view/projection transformations 1139 glm::mat4 projection = <function id='58'>glm::perspective</function>(<function id='63'>glm::radians</function>(camera.Zoom), 1140 (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); 1141 glm::mat4 view = camera.GetViewMatrix(); 1142 ourShader.setMat4("projection", projection); 1143 ourShader.setMat4("view", view); 1144 1145 auto transforms = animator.GetFinalBoneMatrices(); 1146 for (int i = 0; i < transforms.size(); ++i) 1147 ourShader.setMat4("finalBonesTransformations[" + std::to_string(i) + "]", 1148 transforms[i]); 1149 1150 // render the loaded model 1151 glm::mat4 model = glm::mat4(1.0f); 1152 model = <function id='55'>glm::translate</function>(model, glm::vec3(0.0f, -0.4f, 0.0f)); 1153 model = <function id='56'>glm::scale</function>(model, glm::vec3(.5f, .5f, .5f)); 1154 ourShader.setMat4("model", model); 1155 ourModel.Draw(ourShader); 1156 1157 // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) 1158 // ------------------------------------------------------------------------------- 1159 <function id='24'>glfwSwapBuffers</function>(window); 1160 <function id='23'>glfwPollEvents</function>(); 1161 } 1162 1163 // glfw: terminate, clearing all previously allocated GLFW resources. 1164 // ------------------------------------------------------------------ 1165 <function id='25'>glfwTerminate</function>(); 1166 return 0; 1167 } 1168 </code></pre> 1169 1170 <p> 1171 1172 We start with loading our <code>Model</code> which will setup bone weight data for the shader and then create an <code>Animation</code> by giving it the path. 1173 Then we create our <code>Animator</code> object by passing it the created <code>Animation</code>. In render loop we then update our <code>Animator</code>, take the 1174 final bone transformations and give it to shaders. Here's the output we all have been waiting for... 1175 1176 </p> 1177 1178 <img src="/img/guest/2020/skeletal_animation/output.gif" alt="output"> 1179 1180 <p> Download the model used <a href="/data/models/vampire.zip">here.</a> Note that animations 1181 and meshes are baked in single DAE(collada) file. You can find the full source code <a href="/code_viewer_gh.php?code=src/8.guest/2020/skeletal_animation/skeletal_animation.cpp" target="_blank">here</a>. 1182 1183 <h3>Further reading</h3> 1184 <ul> 1185 <li><a href="http://www.songho.ca/math/quaternion/quaternion.html" target="_blank"> 1186 Quaternions</a>: An article by songho to understand quaternions in depth.</li> 1187 <li><a href="http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html" target="_blank"> 1188 Skeletal Animation with Assimp</a>: An article by OGL Dev.</li> 1189 <li><a href="https://youtu.be/f3Cr8Yx3GGA" target="_blank"> 1190 Skeletal Animation with Java</a>: A fantastic youtube playlist by Thin Matrix.</li> 1191 <li><a href="https://www.gamasutra.com/view/feature/131686/rotating_objects_using_quaternions.php" target="_blank"> 1192 Why Quaternions should be used for Rotation</a>: An awesome gamasutra article.</li> 1193 1194 </ul> 1195 1196 1197 1198 <author> 1199 <strong>Article by: </strong>Ankit Singh Kushwah<br/> 1200 <strong>Contact: </strong><a href="mailto:eklavyagames@gmail.com" target="_blank">e-mail</a> 1201 </author> 1202 1203 </div> 1204 1205 </main> 1206 </body> 1207 </html>