In this tutorial you’ll learn how to render the above skeletal animation in your web browser using WebGL and a combination of small modules.

We’ll first install our dependencies, and then go step by step through our code implementation.

We won’t be diving into the math or shaders behind our skeletal animation, but we will be lightly introducing the larger systems that power it.

The goal of this tutorial is for you to walk away with a working skeletal animation, and a sense of how the pieces fit together.

So.. let’s dive right in.

Our dependencies

First we’ll create the directory for our tutorial project.

mkdir skeletal-animation-tutorial cd skeletal-animation-tutorial

We’ll download the 3d model and texture that we’ll be using for our animation. These were created by following Sebastian Lague’s excellent Blender character creation tutorial.

curl -OL https://github.com/chinedufn/skeletal-animation-system \ /raw/8e5e9c247a5204b6fe20bc92d737f017fc64dc27/demo/webgl/asset \ /cowboy.dae > cowboy.dae

curl -OL https://github.com/chinedufn/skeletal-animation-system \ /raw/8e5e9c247a5204b6fe20bc92d737f017fc64dc27/demo/webgl/asset/ \ cowboy-texture.png > cowboy-texture.png

Let’s convert our COLLADA .dae 3d model file into a JSON file that we’ll be loading into our tutorial app.

npm install -g collada-dae-parser@0.11.2 && \ dae2json cowboy.dae > cowboy.json

Now we’ll install the JavaScript modules that we need. We’ll install specific versions of these modules so that we’re sure that this tutorial will work even if these modules receive breaking updates in the future.

npm install keyframes-to-dual-quats@1.0.0 \ load-collada-dae@0.6.0 \ raf-loop@1.1.3 \ skeletal-animation-system@0.6.2

And lastly we’ll install budo , a rapid prototyping tool that will make it easy for us to view our animation locally.

npm install -g budo

Great - we’ve installed all of our dependencies you we will not need an internet connection for the remainder of the tutorial.

Implementing our animation

Alright now that we’re all set up we can jump into writing some code.

You’ll learn more if you follow along and type out the code yourself, but here’s the tutorial’s source on GitHub in case you get stuck.

Let’s create our tutorial’s JavaScript file.

touch tutorial.js

Now open up your empty tutorial.js file that you just created and in your favorite editor!

Creating a canvas

Create the canvas that we’ll be rendering our animation onto.

var canvas = document . createElement ( 'canvas' ) canvas . width = 400 canvas . height = 400 var gl = canvas . getContext ( 'webgl' ) gl . enable ( gl . DEPTH_TEST )

Adding controls

Next we’ll create the slider that will control our animation’s playback speed. Dragging this slider will make our model walk in slow or fast motion.

var playbackSlider = document . createElement ( 'input' ) playbackSlider . type = 'range' playbackSlider . min = 0 playbackSlider . max = 2 playbackSlider . step = 0.01 playbackSlider . value = 1 playbackSlider . style . display = 'block'

We’ll also create a span to display our playback speed.

var speedDisplay = document . createElement ( 'span' ) speedDisplay . innerHTML = 'Playback Speed: 100%'

Let’s now track our playback speed in a variable that we update whenever we drag our slider.

var playbackSpeed = 1 playbackSlider . oninput = function () { playbackSpeed = playbackSlider . value speedDisplay . innerHTML = 'Playback Speed: ' + ( playbackSpeed * 100 ). toFixed ( 0 ) + '%' }

So far we’ve created the DOM elements that will power our (slightly) interactive animation. Now we’ll insert them into the page.

var demoLocation = document . querySelector ( '#skeletal-animation-tutorial' ) || document . body demoLocation . appendChild ( canvas ) demoLocation . appendChild ( playbackSlider ) demoLocation . appendChild ( speedDisplay )

Setting up our 3d model

Earlier in the tutorial we converted our COLLADA file into a JSON file. We’ll pull this file in and prepare it for usage.

var cowboyJSON = require ( './cowboy.json' ) var keyframesToDualQuats = require ( 'keyframes-to-dual-quats' ) cowboyJSON . keyframes = keyframesToDualQuats ( cowboyJSON . keyframes )

cowboyJSON.keyframes had a bunch of 4x4 matrices that represent all of our 3d model’s bones at any given keyframe. We convert these matrices into dual quaternions.

The mathematical reasons for this are outside of the scope of this tutorial, but suffice it to say using dual quaternions helps with preventing our 3d model from getting oddly stretched and deformed when we’re interpolating between different keyframes.

Next we’ll load up our model’s texture and then once that texture loads we’ll buffer our model’s data onto the GPU.

var cowboyModel var texture = new window . Image () texture . onload = function () { // We buffer our 3d model data on the GPU // so that we can later draw it var loadCollada = require ( 'load-collada-dae' ) cowboyModel = loadCollada ( gl , cowboyJSON , { texture : texture }) gl . useProgram ( cowboyModel . shaderProgram ) } texture . src = '/cowboy-texture.png'

We now have everything that we need to render our 3d model, so we’ll start up a render loop.

In our loop we’ll first calculate where we want all of our model’s joints to be based on the current time.

We’ll then draw our model with it’s joints in these newly calculated locations.

var secondsElapsed = 0 var renderLoop = require ( 'raf-loop' ) var animationSystem = require ( 'skeletal-animation-system' ) renderLoop ( function ( millisecondsSinceLastRender ) { if ( cowboyModel ) { // Add the rest of the tutorial code here } }). start ()

Implementing our render loop

The rest of our tutorial’s code will go inside of the render loop that we just set up.

We’ll first set up the uniform data that we’ll later pass into our model’s shader.

var uniforms = { // per-vertex lighting uUseLighting : true , uAmbientColor : [ 1 , 0.9 , 0.9 ], // NOTE: This lighting direction needs to be a normalized vector uLightingDirection : [ 1 , 0 , 0 ], uDirectionalColor : [ 1 , 0 , 0 ], // Move the model back 27 units so we can see it uMVMatrix : [ 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0.0 , 0.0 , - 27.0 , 1 ], uPMatrix : require ( 'gl-mat4/perspective' )([], Math . PI / 4 , 400 / 400 , 0.1 , 100 ) }

Now we’ll use skeletal-animation-system to calculate all of our model’s joint’s dual quaternions for our current time.

Notice that we use our playbackSpeed to modify how quickly we increment our secondsElapsed which allows us to slow down or speed up our animation’s playback speed using our playbackSlider .

secondsElapsed += millisecondsSinceLastRender * playbackSpeed / 1000 var interpolatedJoints = animationSystem . interpolateJoints ({ currentTime : secondsElapsed , keyframes : cowboyJSON . keyframes , jointNums : [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 ], currentAnimation : { range : [ 6 , 17 ], startTime : 0 } }). joints

Now that we have our interpolated joints we’ll add them to our skeletal animation shader’s uniforms.

for ( var i = 0 ; i < 18 ; i ++ ) { uniforms [ 'boneRotQuaternions' + i ] = interpolatedJoints [ i ]. slice ( 0 , 4 ) uniforms [ 'boneTransQuaternions' + i ] = interpolatedJoints [ i ]. slice ( 4 , 8 ) }

And last but not least, we call our draw function. This passes all of our uniform data above to the GPU and then uses it to draw our animated model.

cowboyModel . draw ({ attributes : cowboyModel . attributes , uniforms : uniforms })

And there you have it. With any luck you now have the code to render a skeletal animation in your browser.

Run budo --open --live --host=127.0.0.1 tutorial.js to view your work!

Good job!

What would you like to learn next? Let me know on Twitter!

Til’ next time,

- CFN