<< Lesson 8Lesson 10 >>
Welcome to my number nine in my series of WebGL tutorials, based on number 9 in the NeHe OpenGL tutorials. In it, we’ll use JavaScript objects so that we can have a number of independently-animated objects in our 3d scene. We’ll also touch on how to change the colour of a loaded texture and what happens when you blend textures together.
Here’s what the lesson looks like when run on a browser that supports WebGL:
Click here and you’ll see the live WebGL version, if you’ve got a browser that supports it; here’s how to get one if you don’t. You should see a large number of differently-coloured stars, spiraling around.
You can use the checkbox underneath the canvas to switch on a “twinkling” effect, which we’ll look at later. You can also use the cursor keys to spin the animation around its X axis, and you can zoom in and out using the Page Up and Page Down keys.
More on how it all works below…
The usual warning: these lessons are targeted at people with a reasonable amount of programming knowledge, but no real experience in 3D graphics; the aim is to get you up and running, with a good understanding of what’s going on in the code, so that you can start producing your own 3D Web pages as quickly as possible. If you haven’t read the previous tutorials already, you should probably do so before reading this one — here I will only explain the differences between the code for lesson 8 and the new code.
There may be bugs and misconceptions in this tutorial. If you spot anything wrong, let me know in the comments and I’ll correct it ASAP.
There are two ways you can get the code for this example; just “View Source” while you’re looking at the live version, or if you use GitHub, you can clone it (and the other lessons) from the repository there.
The best way to show how the code for this example differs from lesson 8’s is to start at the bottom of the file and work our way up, kicking off with webGLStart. Here’s what the function looks like this time:
function webGLStart() {
var canvas = document.getElementById("lesson09-canvas");
initGL(canvas);
initShaders();
initTexture();
initBuffers();
initWorldObjects();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0);
document.onkeydown = handleKeyDown;
document.onkeyup = handleKeyUp;
setInterval(tick, 15);
}
I’ve highlighted one change in red; the call to the new initWorldObjects function. This function creates JavaScript objects to represent the scene, and we’ll come to it shortly, but before we do it’s also worth noting another change here; all of our previous webGLStart functions had two lines to set up depth-testing:
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
These have been removed for this example. You will probably remember from last time that blending and depth-testing don’t play well together, and we use blending all the time in this example. Depth-testing is off by default, so by removing those lines from our usual boilerplate we’re all set.
The next big change is in the animate function. Previously we used this to update the various global variables that represented the changing aspects of our scene — for example, the angle by which we should rotate a cube before drawing it. The change we’ve made here is quite simple; instead of updating variables directly, we loop through all of the objects in our scene and tell each one to animate itself:
var lastTime = 0;
function animate() {
var timeNow = new Date().getTime();
if (lastTime != 0) {
var elapsed = timeNow - lastTime;
for (var i in stars) {
stars[i].animate(elapsed);
}
}
lastTime = timeNow;
}
Working our way up, drawScene comes next. This has changed enough that I won’t highlight the specific changes; instead, we can go through it bit by bit. Firstly:
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
perspective(45, 1.0, 0.1, 100.0);
This is just our usual setup code, unchanged since lesson 1.
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
gl.enable(gl.BLEND);
Next, we switch on blending. We’re using the same blending as we did in the last lesson; you will remember that this allows objects to “shine through” each other. Usefully, it also means that black parts of an object are drawn as if they were transparent; to see how that works, take a look at the description of the blending function in the last lesson. What this means is that when we are drawing the stars that make up our scene, the black bits will seem transparent; indeed, the less bright a part of the star is, the more transparent it will seem. And as the star is drawn using this texture:

…this gets us just the effect we want.
On to the next part of the code:
loadIdentity();
mvTranslate([0.0, 0.0, zoom]);
mvRotate(tilt, [1.0, 0.0, 0.0]);
So, here we just move to the centre of the scene, and then zoom in appropriately. We also tilt the scene around the X axis; the zoom and the tilt are still global variables controlled from the keyboard. We’re now pretty much ready to draw the scene, so first we check whether the “twinkle” checkbox is checked:
var twinkle = document.getElementById("twinkle").checked;
…and then, just as we did when animating them, we iterate over the list of stars and tell each one to draw itself. We pass in the current tilt of the scene and the twinkle value. We also tell it what the current “spin” is — this is used to make the stars spin around their centres as they orbit the centre of the scene.
for (var i in stars) {
stars[i].draw(tilt, spin, twinkle);
spin += 0.1;
}
}
So, that’s drawScene. We’ve now seen that the stars are clearly capable of drawing and animating themselves; the next code is the bit that creates them:
var stars = [];
function initWorldObjects() {
var numStars = 50;
var distances = []
for (var i=0; i < numStars; i++) {
stars.push(new Star((i / numStars) * 5.0, i / numStars));
}
}
So, a simple loop creating 50 stars (you might want to try experimenting with larger or smaller numbers). Each star is given a first parameter specifying a starting distance from the centre of the scene and a second specifying a speed to orbit the centre of the scene, both of which come from their position in the list.
The next code to look at is the class definition for the stars. If you're not used to JavaScript, it will look really odd. (If you know JavaScript well, click here to skip my explanation of its object model.)
JavaScript's object model is very different to other languages'; it doesn't support classes as you'd normally understand them. The way I've found it easiest to understand is that each object is created as an empty dictionary (or hashtable, or associative array) and then turned into a fully-fledged object by putting values into it. The object's fields are simply entries in the dictionary that map to values, and the methods are entries that map to functions. Now, we add to that the fact that for single-word keys, the syntax foo.bar is a valid JavaScript shortcut for foo["bar"], and you can see how you get syntax similar to other languages' from a very different starting point.
Next, when you're in any JavaScript function, there is an implicitly-bound variable called this which refers to the function's "owner". For global functions this is a global per-page "window" object, but if you put the keyword new before it then it will be a brand-new object instead. So, if you have a function that sets this.foo to 1 and this.bar to a function, and then you make sure you always call it with the new keyword, it's basically the same as a constructor combined with a class definition.
Finally, we can note that if a function is called using method invocation-like syntax (that is, foo.bar()), then this will be bound to the function's owner (foo), just as we'd expect, so the object's methods can do stuff to the object itself.
[Thanks to murphy in the comments and to this page by Sergio Pereira for helping me correct the original version of that explanation.]
Let's take a look at the function we write to define a Star object for this scene.
function Star(startingDistance, rotationSpeed) {
this.angle = 0;
this.dist = startingDistance;
this.rotationSpeed = rotationSpeed;
So, the star is initialised with the values we provide and a starting angle of zero. Now, we bind an anonymous function to the object-under-construction's draw attribute, thus creating the draw method:
this.draw = function(tilt, spin, twinkle) {
mvPushMatrix();
So, draw is defined to take the parameters we passed in to it back in the main drawScene function. We start it off by pushing the current model-view matrix onto the matrix stack so that we can move around without fear of having side-effects elsewhere.
// Move to the star's position
mvRotate(this.angle, [0.0, 1.0, 0.0]);
mvTranslate([this.dist, 0.0, 0.0]);
Next we rotate around the Y axis by the star's own angle, and move out by the star's distance from the centre. This puts us in the correct position to draw the star.
// Rotate back so that the star is facing the viewer
mvRotate(-this.angle, [0.0, 1.0, 0.0]);
mvRotate(-tilt, [1.0, 0.0, 0.0]);
These lines are required so that when we alter the tilt of the scene using the cursor keys, the stars still look right. They are drawn as a 2D texture on a square, which looks right when we're looking at it straight on, but would just look like a line if we tilted the scene so that we were viewing it from the side. For similar reasons, we also need to back out the rotation required to position the star. When you "undo" rotations like this, you need to do so in the reverse of the order you did them in the first place, so first we undo the rotation from our positioning, and then the tilt (which was done in drawScene).
The next lines are to draw the star:
if (twinkle) {
// Draw a non-rotating star in the alternate "twinkling" color
gl.uniform3f(shaderProgram.colorUniform, this.twinkleR, this.twinkleG, this.twinkleB);
drawStar();
}
// All stars spin around the Z axis at the same rate
mvRotate(spin, [0.0, 0.0, 1.0]);
// Draw the star in its main color
gl.uniform3f(shaderProgram.colorUniform, this.r, this.g, this.b);
drawStar();
Let's ignore the code that's executed for the twinkling effect for a moment. The star is drawn by first rotating around the Z axis by the spin that was passed in, so that it rotates around its own centre while it orbits the centre of the scene. We then push the star's colour up to the graphics card in a shader uniform, and then call a global drawStar function (which we'll come to in a moment).
Now, what about that twinkling stuff? Well, the star has two colours associated with it — its normal colour, and its "twinkling colour". To make it twinkle, before we draw the star itself we draw a non-spinning star in a different colour. This means that the two stars are blended together, making a nice bright colour, but also means that rays that come out of the first star to be drawn are stationary while the ones that come out of the second star are rotating, giving a nice effect. That's our twinkling.
So, once we've drawn the star, we just pop our model-view matrix from the stack and we're done:
mvPopMatrix();
};
The next method we bind to the star object is the one to animate it:
this.animate = function(elapsedTime) {
var effectiveFPMS = 60 / 1000;
As in the previous lessons, instead of just getting the scene to update as fast as it can, I've chosen to make it change at a steady pace for everyone, so people with faster computers get smoother animations and people with slower ones jerkier. Now, I think that the numbers for the angular speed at which the stars orbit the centre of the scene and at which they move towards the centre were carefully calculated by NeHe, so rather than messing around with them I decided that it was best to assume that the numbers were calibrated for 60 frames per second and then use that and the elapsedTime (which you'll remember is the time between calls to the animate function) to scale the amount we move at each animation "tick" appropriately. elapsedTime is in milliseconds, and so we want an effective frames-per-millisecond of 60 / 1000.
So, now we have this number we can adjust the star's angle — that is, how far around its orbit of the centre of the scene it is:
this.angle += this.rotationSpeed * effectiveFPMS * elapsedTime;
...and we can adjust its distance from the centre, moving it out to the outside of the scene and resetting its colours to something random when it finally reaches the centre:
// Decrease the distance, resetting the star to the outside of
// the spiral if it's at the center.
this.dist -= 0.01 * effectiveFPMS * elapsedTime;
if (this.dist < 0.0) {
this.dist += 5.0;
this.randomiseColors();
}
};
The final bit of code that makes up the star is that code we just saw used to randomise its twinkling and non-twinkling colours:
this.randomiseColors = function() {
// Give the star a random color for normal
// circumstances...
this.r = Math.random();
this.g = Math.random();
this.b = Math.random();
// When the star is twinkling, we draw it twice, once
// in the color below (not spinning) and then once in the
// main color defined above.
this.twinkleR = Math.random();
this.twinkleG = Math.random();
this.twinkleB = Math.random();
};
...and then, once that's defined, we call it:
// Set the colors to a starting value.
this.randomiseColors();
}
...and we're done with the star's constructor. So, that's how a star object is created, complete with methods to draw and animate it. Now, just above this function, you can see the (rather dull) code that draws the star: it just draws a square in a manner that will be familiar from the first lesson, using an appropriate texture and vertex position/texture coordinate buffers:
function drawStar() {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, starTexture);
gl.uniform1i(shaderProgram.samplerUniform, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, starVertexTextureCoordBuffer);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, starVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, starVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, starVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, starVertexPositionBuffer.numItems);
}
A little further up, you can see initBuffers, which sets up those vertex position and texture coordinate buffers, and then the appropriate code in handleKeys to manipulate the zoom and tilt functions when the user presses the up/down cursor keys or the Page Up/Page Down keys. A little further up again, and you will see the initTexture and handleLoadedTexture functions have been updated to load the new texture. All of that is so simple that I won't bore you by describing it :-)
Let's just go right up to the shaders, where you can see the last change that was required for this lesson. All of the lighting-related stuff has been removed from the vertex shader, which is now just the same as it was for lesson 5. The fragment shader is a little bit more interesting:
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform vec3 uColor;
void main(void) {
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, 1.0 - vTextureCoord.t));
gl_FragColor = textureColor * vec4(uColor, 1.0);
}
...but not that much more interesting :-) All we're doing is picking up that colour uniform that was pushed up here by the code in the star's draw method and using it to tint the texture, which is monochrome. This means that our stars appear in the appropriate colour.
And that's it! Now you know all there is to learn from this lesson: how to create JavaScript objects to represent things in your scene, and to give them methods that allow them to be separately animated and drawn.
Next time, we'll show how to load up a scene from a simple file, and take a look at how you can have a camera moving through it, combining these to make kind of nano-Doom :-)
Acknowledgments: As always, I'm deeply in debt to NeHe for his OpenGL tutorial for the script for this lesson.


That was a nice lesson, I can see that Christmas is approaching :)
Your explanations about “this” are not fully correct though. “this” is not pointing to the function, but to the receiver of the function call, which is determined by the calling statement obj.fun(). The function isn’t copied.
By the way, you could also hide most of the members of Star by using the closure scope; only draw() and animate() need to be visible from the outside. Here’s a patch: http://pastie.textmate.org/716668. But maybe I just like closure variables more than member variables :)
Oh, and as for nano-Doom: I am very excited about that! We don’t get any weapons yet, do we?
First off this is an excellent addition to a brilliant resource. I’ve learned so much from this site in the last couple of weeks. I have discovered one little issue with the code which shows up in several tutorials. It isn’t effecting anything yet, but the following line may cause you trouble down the line:
gl.uniform1f(gl.getUniformLocation(shaderProgram, “uSampler”), 0);
This works fine when you only have one texture in your shader; but, if you want to use more then one, it won’t complain or error, just not work as it’s expecting an int not a float:
gl.uniform1i(gl.getUniformLocation(shaderProgram, “uSampler”), 0);
Keep up the great work.
@murphy — thanks for explaining that! I’ll read up a little on JavaScript functions and receivers and fix the description appropriately. Unfortunately the nano-Doom is a bit too primitive for weapons, or indeed monsters. It does have a bobbing-head effect, though :-)
@Paul — glad you like the posts! That’s a great point about the texture uniforms, I’ve tweaked this and the previous tutorials so that future readers don’t get caught out.
@murphy — OK, hopefully that’s better now! I’ve not put in the use of the closure scope, I think I need to think a bit more about that before using it — to a Python guy it looks very odd :-)
Yes, I see…you certainly wouldn’t do that in Python! I just happen to play around with closures a lot, and there are fascinating things you can do with them – but this is a WebGL tutorial, not a functional programming tutorial :) Keep up the great work!
It’s not much of an issue with only 50 stars, but your code is creating a new instance of the draw and animate functions per star instance. They’re all the same, so you don’t need to have separate copies per star. Instead of creating the draw and animate functions in the constructor and assigning them to fields of the newly-constructed instance, you should create and assign them to the prototype, so that all Stars can use the same ones:
function Star(startingDistance, rotationSpeed) {
this.angle = 0;
this.dist = startingDistance;
this.rotationSpeed = rotationSpeed;
};
Star.prototype.draw = new function(tilt, spin, twinkle) { … }
Star.prototype.animate = new function(elapsedTime) { … }
doug — that sounds like a good plan, I don’t want to get people new to JavaScript into bad habits. I’ll update the lesson.
The zoom in/out keyboard mapping seems to be inconsistent with the previous lessons. It may be nice to keep the same mapping throughout…
Good point, I’ll fix that.
Fixed.