|
<span style="float: left; padding: 0px 20px 0px 0px;"><a href="http://learningwebgl.com/blog/?p=134"><< Lesson 2</a></span><span style="float: right; padding: 0px 20px 0px 0px;"><a href="http://learningwebgl.com/blog/?p=370">Lesson 4 >></a></span><br/> |
|
<span style="float: left; padding: 0px 20px 0px 0px;"><a href="http://learningwebgl.com/blog/?p=134"><< Lesson 2</a></span><span style="float: right; padding: 0px 20px 0px 0px;"><a href="http://learningwebgl.com/blog/?p=370">Lesson 4 >></a></span><br/> |
|
Welcome to my number three in my series of WebGL tutorials. This time we're going to start making things move around. It's based on <a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=04">number 4</a> in the NeHe OpenGL tutorials. |
|
Welcome to my number three in my series of WebGL tutorials. This time we're going to start making things move around. It's based on <a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=04">number 4</a> in the NeHe OpenGL tutorials. |
|
Here's what the lesson looks like when run on a browser that supports WebGL: |
|
Here's what the lesson looks like when run on a browser that supports WebGL: |
|
<object width="480" height="385"><param name="movie" value="http://www.youtube.com/v/zM3rqs-6cUg&hl=en&fs=1&hd=1"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/zM3rqs-6cUg&hl=en&fs=1&hd=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"></embed></object> |
|
<object width="480" height="385"><param name="movie" value="http://www.youtube.com/v/zM3rqs-6cUg&hl=en&fs=1&hd=1"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/zM3rqs-6cUg&hl=en&fs=1&hd=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"></embed></object> |
|
<a href="/lessons/lesson03/index.html">Click here and you'll see the live WebGL version</a>, if you've got a browser that supports it; <a href="http://learningwebgl.com/blog/?p=11">here's how to get one</a> if you don't. |
|
<a href="/lessons/lesson03/index.html">Click here and you'll see the live WebGL version</a>, if you've got a browser that supports it; <a href="http://learningwebgl.com/blog/?p=11">here's how to get one</a> if you don't. |
|
More on how it all works below... |
|
More on how it all works below... |
|
<!--more--> |
|
<!--more--> |
|
The usual <a href="http://learningwebgl.com/blog/?page_id=2">warning</a>: 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 <a href="http://learningwebgl.com/blog/?p=28">first</a> and <a href="http://learningwebgl.com/blog/?p=134">second</a> tutorials already, you should probably do so before reading this one — here I will only explain the differences between the code for lesson 2 and the new code. |
|
The usual <a href="http://learningwebgl.com/blog/?page_id=2">warning</a>: 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 <a href="http://learningwebgl.com/blog/?p=28">first</a> and <a href="http://learningwebgl.com/blog/?p=134">second</a> tutorials already, you should probably do so before reading this one — here I will only explain the differences between the code for lesson 2 and the new code. |
|
As before, 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. |
|
As before, 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 <a href="http://github.com/gpjt/webgl-lessons">the repository there</a>. Either way, once you have the code, load it up in your favourite text editor and take a look. |
|
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 <a href="http://github.com/gpjt/webgl-lessons">the repository there</a>. Either way, once you have the code, load it up in your favourite text editor and take a look. |
|
- |
Before I get into describing the code, I'll clarify one thing. The way you animate a 3D scene in WebGL is very simple — you just draw repeatedly, drawing it differently each time. This may well be totally obvious to a lot of readers, but it was a bit of a surprise to me when I was learning OpenGL, and might surprise others who are coming to 3D graphics for the first time with WebGL. The reason I was confused originally was that I was imagining that it would use a higher-level abstraction, which would work in terms of "tell the 3D system that there's (say) a square at point X the first time I draw it, and then to move the square, tell the 3D system that the square I told it about earlier has moved to point Y." Instead, what happens is more that you "tell the 3D system that there's a square at point X, then next time you draw it, tell the 3D system that it's at point Y, and then next time that it's at point Z" and so on.
|
+ |
Before I get into describing the code, I'll clarify one thing. The way you animate a 3D scene in WebGL is very simple — you just draw repeatedly, drawing it differently each time. This may well be totally obvious to a lot of readers, but it was a bit of a surprise to me when I was learning OpenGL, and might surprise others who are coming to 3D graphics for the first time with WebGL. The reason I was confused originally was that I was imagining that it would use a higher-level abstraction, which would work in terms of "tell the 3D system that there's (say) a square at point X the first time I draw it, and then to move the square, tell the 3D system that the square I told it about earlier has moved to point Y." Instead, what happens is more that you "tell the 3D system that there's a square at point X, then next time you draw it, tell the 3D system that there's a square at point Y, and then next time that there's a square at point Z" and so on.
|
|
I hope that last paragraph has made things clearer for at least some people (let me know in the comments if it's just confusing matters and I'll delete it :-) |
|
I hope that last paragraph has made things clearer for at least some people (let me know in the comments if it's just confusing matters and I'll delete it :-) |
|
- |
Anyway, what this means is that because our sample code so far has been using a function called <code>drawScene</code> to draw everything, and has been using this code:
|
+ |
Anyway, what this means is that because our code so far has been using a function called <code>drawScene</code> to draw everything, to animate things we need to arrange matters such that this function is called repeatedly, and draws something slightly different each time. Let's start at the bottom of the <code>index.html</code> file and see how that's done. Firstly, let's take a look at the function that kicks everything off when the page is loaded, <code>webGLStart</code>:
|
|
<pre> |
|
<pre> |
|
|
+ |
function webGLStart() { |
|
|
+ |
var canvas = document.getElementById("lesson03-canvas"); |
|
|
+ |
initGL(canvas); |
|
- |
setInterval(drawScene, 15); |
+ |
initShaders(); |
|
|
+ |
initTexture(); |
|
|
+ |
gl.clearColor(0.0, 0.0, 0.0, 1.0); |
|
|
+ |
gl.clearDepth(1.0); |
|
|
+ |
gl.enable(gl.DEPTH_TEST); |
|
|
+ |
gl.depthFunc(gl.LEQUAL); |
|
|
+ |
<span style="color: red">tick()</span>; |
|
|
+ |
} |
|
</pre> |
|
</pre> |
|
|
+ |
The only change here is that instead of calling <code>drawScene</code> at the end to draw the scene, we call a new function, <code>tick</code>. This is the function that needs to be called regularly; it updates the scene's animation state (eg. the triangle has moved from being 81 degrees rotated to 82 degrees) draws the scene, and also arranges for itself to be called again in an appropriate time. It's the next function up in the file, so let's look at it next. |
|
|
+ |
<pre> |
|
|
+ |
function tick() { |
|
|
+ |
requestAnimFrame(tick); |
|
|
+ |
</pre> |
|
|
+ |
The first line is where <code>tick</code> arranges to be called again when a repaint is needed next. <code>requestAnimFrame</code> is a function in some Google-provided code that we're including into this web page with a <code><script></code> tag at the top, <code>webgl-utils.js</code>. It gives us a browser-independent way of asking the browser to call us back next time it wants to repaint the WebGL scene — for example, next time the computer's display is refreshing itself. Right now, functions to do this exist in all WebGL-supporting browsers, but they have different names for it (for example, Firefox has a function called <code>mozRequestAnimationFrame</code>, while Chrome and Safari have <code>webkitRequestAnimationFrame</code>). In the future they are expected to all use just <code>requestAnimationFrame</code>. Until then, we can use the Google WebGL utils to have just one call that works everywhere. |
|
|
+ |
It's worth noting that you could get a similar effect to using <code>requestAnimFrame</code> by asking JavaScript to call the <code>drawScene</code> function regularly, for example by using the built-in JavaScript <code>setInterval</code> function. A lot of early WebGL code (including earlier versions of these tutorials) did just that, and it worked fine -- right up until people had more than one WebGL page open, in different browser tabs. Because functions scheduled with <code>setInterval</code> are called regardless of whether the browser tab they belong to is showing, using it meant that computers were doing all the work of displaying every open WebGL tab all the time, hidden or not. This was obviously a Bad Thing, and was the reason why <code>requestAnimationFrame</code> was introduced; functions scheduled using it are only called when the tab is visible. |
|
|
+ |
On to the remainder of <code>tick</code>: |
|
|
+ |
<pre> |
|
|
+ |
drawScene(); |
|
|
+ |
animate(); |
|
|
+ |
} |
|
|
+ |
</pre> |
|
- |
...to tell JavaScript to call <code>drawScene</code> at regular 15ms intervals, all we need to do to animate the scene and get the triangle and the square moving is change the code so that every time <code>drawScene</code> is called, it draws stuff slightly differently. |
+ |
So, once we've scheduled tick to be called again next time the browser wants a frame to be painted, we simply draw this one, and update our state for the next. Let's look at the <code>drawScene</code> and <code>animate</code> functions in turn. |
|
- |
What <i>that</i> means is that the bulk of the changes from the <a href="http://learningwebgl.com/blog/?p=134">lesson 2 code</a> are in the <code>drawScene</code> function, so let's start there — it's about two thirds of the way down <code>index.html</code>. The first thing to note is that just before the function declaration, we're now defining two new global variables. |
|
|
|
+ |
<code>drawScene</code> is about two thirds of the way down <code>index.html</code>. The first thing to note is that just before the function declaration, we're now defining two new global variables. |
|
<pre> |
|
<pre> |
|
var rTri = 0; |
|
var rTri = 0; |
|
var rSquare = 0; |
|
var rSquare = 0; |
|
</pre> |
|
</pre> |
|
These are used to track the rotation of the triangle and the square respectively. They both start off rotated by zero degrees, and then over time these numbers will increase — you'll see how later — making them rotate more and more. (A side note — using global variables for things like this in a 3D program that is not a simple demo like this would be really bad practice. I show how to structure things in a more elegant manner in <a href="http://learningwebgl.com/blog/?p=1008">lesson 9</a>.) |
|
These are used to track the rotation of the triangle and the square respectively. They both start off rotated by zero degrees, and then over time these numbers will increase — you'll see how later — making them rotate more and more. (A side note — using global variables for things like this in a 3D program that is not a simple demo like this would be really bad practice. I show how to structure things in a more elegant manner in <a href="http://learningwebgl.com/blog/?p=1008">lesson 9</a>.) |
|
The next change in <code>drawScene</code> comes at the point where we draw the triangle. I'll show all of the code that draws it by way of context, the new lines are the ones in red: |
|
The next change in <code>drawScene</code> comes at the point where we draw the triangle. I'll show all of the code that draws it by way of context, the new lines are the ones in red: |
|
<pre> |
|
<pre> |
|
- |
perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
|
+ |
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
|
|
- |
loadIdentity(); |
|
|
|
+ |
mat4.identity(mvMatrix); |
|
- |
mvTranslate([-1.5, 0.0, -7.0])
|
+ |
mat4.translate(mvMatrix, [-1.5, 0.0, -7.0]);
|
|
<span style="color: red">mvPushMatrix(); |
|
<span style="color: red">mvPushMatrix(); |
|
- |
mvRotate(rTri, [0, 1, 0]);</span>
|
+ |
mat4.rotate(mvMatrix, degToRad(rTri), [0, 1, 0]);</span>
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); |
|
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); |
|
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); |
|
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); |
|
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer); |
|
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer); |
|
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, triangleVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0); |
|
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, triangleVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0); |
|
setMatrixUniforms(); |
|
setMatrixUniforms(); |
|
gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems); |
|
gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems); |
|
<span style="color: red">mvPopMatrix();</span> |
|
<span style="color: red">mvPopMatrix();</span> |
|
</pre> |
|
</pre> |
|
In order to explain what's going on here, let's go back to lesson 1. There, I said: |
|
In order to explain what's going on here, let's go back to lesson 1. There, I said: |
|
<blockquote>In OpenGL, when you’re drawing a scene, you tell it to draw each thing you draw at a "current" position with a "current" rotation — so, for example, you say "move 20 units forward, rotate 32 degrees, then draw the robot", the last bit being some complex set of "move this much, rotate a bit, draw that" instructions in itself. This is useful because you can encapsulate the "draw the robot" code in one function, and then easily move said robot around just by changing the move/rotate stuff you do before calling that function.</blockquote> |
|
<blockquote>In OpenGL, when you’re drawing a scene, you tell it to draw each thing you draw at a "current" position with a "current" rotation — so, for example, you say "move 20 units forward, rotate 32 degrees, then draw the robot", the last bit being some complex set of "move this much, rotate a bit, draw that" instructions in itself. This is useful because you can encapsulate the "draw the robot" code in one function, and then easily move said robot around just by changing the move/rotate stuff you do before calling that function.</blockquote> |
|
You'll remember that this current state is stored in a <i>model-view matrix</i>. Given all that, the purpose of the call to: |
|
You'll remember that this current state is stored in a <i>model-view matrix</i>. Given all that, the purpose of the call to: |
|
<pre> |
|
<pre> |
|
- |
mvRotate(rTri, [0, 1, 0]);
|
+ |
mat4.rotate(mvMatrix, degToRad(rTri), [0, 1, 0]);
|
|
</pre> |
|
</pre> |
|
- |
Is probably pretty obvious; we're changing our current rotation state as stored in the model-view matrix, rotating by <code>rTri</code> degrees around the vertical axis (which is specified by the vector in the second parameter). This means that when the triangle is drawn, it will be rotated by <code>rTri</code> degrees. <code>mvRotate</code>, just like the <code>mvTranslate</code> function we looked at in lesson 1, is coded in JavaScript — we'll take a look at it later.
|
+ |
Is probably pretty obvious; we're changing our current rotation state as stored in the model-view matrix, rotating by <code>rTri</code> degrees around the vertical axis (which is specified by the vector in the third parameter). This means that when the triangle is drawn, it will be rotated by <code>rTri</code> degrees. Note that <code>mat4.rotate</code> takes angles in radians; personally I find degrees easier to deal with, so I've written a simple conversion function <code>degToRag</code> to use here.
|
|
Now, what about the calls to <code>mvPushMatrix</code> and <code>mvPopMatrix</code>? As you would expect from the function names, they're also related to the model-view matrix. Going back to my example of drawing a robot, let's say your code at the highest level needs to move to point A, draw the robot, then move to some offset from point A and draw a teapot. The code that draws the robot might make all kinds of changes to the model-view matrix; it might start with a body, then move down for the legs, then up for the head, and finish off with the arms. The problem is that if after this you tried to move to your offset, you'd move not relative to point A but instead relative to whatever you last drew, which would mean that if your robot lifted its arms, the teapot would start levitating. Not a good thing. |
|
Now, what about the calls to <code>mvPushMatrix</code> and <code>mvPopMatrix</code>? As you would expect from the function names, they're also related to the model-view matrix. Going back to my example of drawing a robot, let's say your code at the highest level needs to move to point A, draw the robot, then move to some offset from point A and draw a teapot. The code that draws the robot might make all kinds of changes to the model-view matrix; it might start with a body, then move down for the legs, then up for the head, and finish off with the arms. The problem is that if after this you tried to move to your offset, you'd move not relative to point A but instead relative to whatever you last drew, which would mean that if your robot lifted its arms, the teapot would start levitating. Not a good thing. |
|
Obviously what is required is some way of storing the state of the model-view matrix before you start drawing the robot, and restoring it afterwards. This is, of course, what <code>mvPushMatrix</code> and <code>mvPopMatrix</code> do. <code>mvPushMatrix</code> puts the matrix onto a stack, and <code>mvPopMatrix</code> gets rid of the current matrix, takes one from the top of the stack, and restores it. Using a stack means that we can have any number of bits of nested drawing code, each of which manipulates the model-view matrix and then restores it afterwards. So once we've finished drawing our rotated triangle, we restore the model-view matrix with <code>mvPopMatrix</code> so that this code: |
|
Obviously what is required is some way of storing the state of the model-view matrix before you start drawing the robot, and restoring it afterwards. This is, of course, what <code>mvPushMatrix</code> and <code>mvPopMatrix</code> do. <code>mvPushMatrix</code> puts the matrix onto a stack, and <code>mvPopMatrix</code> gets rid of the current matrix, takes one from the top of the stack, and restores it. Using a stack means that we can have any number of bits of nested drawing code, each of which manipulates the model-view matrix and then restores it afterwards. So once we've finished drawing our rotated triangle, we restore the model-view matrix with <code>mvPopMatrix</code> so that this code: |
|
<pre> |
|
<pre> |
|
- |
mvTranslate([3.0, 0.0, 0.0])
|
+ |
mat4.translate(mvMatrix, [3.0, 0.0, 0.0]);
|
|
</pre> |
|
</pre> |
|
...moves across the scene in an unrotated frame of reference. (If it's still not clear what this all means, I recommend copying the code and seeing what happens if you remove the push/pop code, then running it again; it will almost certainly "click" pretty quickly.) |
|
...moves across the scene in an unrotated frame of reference. (If it's still not clear what this all means, I recommend copying the code and seeing what happens if you remove the push/pop code, then running it again; it will almost certainly "click" pretty quickly.) |
|
So, these three changes make the triangle rotate around the vertical axis through its centre without affecting the square. There are also three similar lines to make the square rotate around the horizontal axis through <i>its</i> centre: |
|
So, these three changes make the triangle rotate around the vertical axis through its centre without affecting the square. There are also three similar lines to make the square rotate around the horizontal axis through <i>its</i> centre: |
|
<pre> |
|
<pre> |
|
<span style="color: red">mvPushMatrix(); |
|
<span style="color: red">mvPushMatrix(); |
|
- |
mvRotate(rSquare, [1, 0, 0]);</span>
|
+ |
mat4.rotate(mvMatrix, degToRad(rSquare), [1, 0, 0]);</span>
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); |
|
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); |
|
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); |
|
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); |
|
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexColorBuffer); |
|
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexColorBuffer); |
|
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, squareVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0); |
|
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, squareVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0); |
|
setMatrixUniforms(); |
|
setMatrixUniforms(); |
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems); |
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems); |
|
<span style="color: red">mvPopMatrix();</span> |
|
<span style="color: red">mvPopMatrix();</span> |
|
} |
|
} |
|
</pre> |
|
</pre> |
|
...and that's all of the changes to the drawing code in <code>drawScene</code>. |
|
...and that's all of the changes to the drawing code in <code>drawScene</code>. |
|
- |
Obviously, the other thing we need to do to animate our scene is to change the values of <code>rTri</code> and <code>rSquare</code> over time, so that each time the scene is drawn, it's slightly different. We do this with a new function called <code>animate</code> which, like <code>drawScene</code>, will be called regularly (you'll see the code that arranges that in a moment). It looks like this:
|
+ |
Obviously, the other thing we need to do to animate our scene is to change the values of <code>rTri</code> and <code>rSquare</code> over time, so that each time the scene is drawn, it's slightly different. This, of course, happens in our new <code>animate</code> function, which looks like this:
|
|
<pre> |
|
<pre> |
|
var lastTime = 0; |
|
var lastTime = 0; |
|
function animate() { |
|
function animate() { |
|
var timeNow = new Date().getTime(); |
|
var timeNow = new Date().getTime(); |
|
if (lastTime != 0) { |
|
if (lastTime != 0) { |
|
var elapsed = timeNow - lastTime; |
|
var elapsed = timeNow - lastTime; |
|
rTri += (90 * elapsed) / 1000.0; |
|
rTri += (90 * elapsed) / 1000.0; |
|
rSquare += (75 * elapsed) / 1000.0; |
|
rSquare += (75 * elapsed) / 1000.0; |
|
} |
|
} |
|
lastTime = timeNow; |
|
lastTime = timeNow; |
|
} |
|
} |
|
</pre> |
|
</pre> |
|
- |
A simple way of animating a scene would be to just add on a fixed amount each time <code>animate</code> was called (which is what the <a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=04">original OpenGL lesson</a> on which this one was based does), but here I've chosen to do something I think is slightly better practice; the amount by which we rotate the objects is determined by how long it has been since the function was last called. Specifically, the triangle is rotating by 90 degrees per second, and the square by 75 degrees per second. The nice thing about doing it this way is that everyone sees the same rate of motion in the scene regardless of how fast their machine is; people with slower machines just see jerkier images. This doesn't matter so much for a simple demo like this, but obviously can be a bigger deal with games and the like.
|
+ |
A simple way of animating a scene would be to just rotate our triangle and our square by a fixed amount each time <code>animate</code> was called (which is what the <a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=04">original OpenGL lesson</a> on which this one was based does), but here I've chosen to do something I think is slightly better practice; the amount by which we rotate the objects is determined by how long it has been since the function was last called. Specifically, the triangle is rotating by 90 degrees per second, and the square by 75 degrees per second. The nice thing about doing it this way is that everyone sees the same rate of motion in the scene regardless of how fast their machine is; people with slower machines (for whom functions scheduled with <code>requestAnimFrame</code> will be called less frequently) just see jerkier images. This doesn't matter so much for a simple demo like this, but obviously can be a bigger deal with games and the like.
|
|
- |
The next change is the one we have to get <code>animate</code> called regularly, just like <code>drawScene</code>. We do this by creating a new function called <code>tick</code>, which calls both of them and itself is scheduled to be called every 15 milliseconds instead of <code>drawScene</code>: |
+ |
So, that's all of the code that actually animates and draws the scene. Let's look at the supporting code that we had to add, <code>mvPushMatrix</code> and <code>mvPopMatrix</code>: |
|
<pre> |
|
<pre> |
|
|
+ |
var mvMatrix = mat4.create(); |
|
|
+ |
<span style="color: red">var mvMatrixStack = [];</span>
|
|
|
+ |
var pMatrix = mat4.create(); |
|
- |
<span style="color: red">function tick() {
|
+ |
<span style="color: red"> function mvPushMatrix() {
|
|
- |
drawScene(); |
|
|
- |
animate(); |
|
|
- |
}</span> |
|
|
- |
function webGLStart() { |
|
|
- |
var canvas = document.getElementById("lesson03-canvas"); |
|
|
- |
initGL(canvas); |
|
|
- |
initShaders(); |
|
|
- |
initTexture(); |
|
|
- |
gl.clearColor(0.0, 0.0, 0.0, 1.0); |
|
|
- |
gl.clearDepth(1.0); |
|
|
- |
gl.enable(gl.DEPTH_TEST); |
|
|
- |
gl.depthFunc(gl.LEQUAL); |
|
|
- |
setInterval(<span style="color: red">tick</span>, 15);
|
|
|
- |
} |
|
|
- |
</pre> |
|
|
- |
So, that's all of the changes in the code that actually animates and draws the scene. Let's look at the supporting code that we had to add. Firstly, <code>mvPushMatrix</code> and <code>mvPopMatrix</code>: |
|
|
- |
<pre> |
|
|
- |
var mvMatrixStack = []; |
|
|
- |
function mvPushMatrix(m) { |
|
|
- |
if (m) { |
|
|
|
+ |
var copy = mat4.create(); |
|
|
+ |
mat4.set(mvMatrix, copy); |
|
- |
mvMatrixStack.push(m.dup());
|
+ |
mvMatrixStack.push(copy);
|
|
- |
mvMatrix = m.dup(); |
|
|
- |
} else { |
|
|
- |
mvMatrixStack.push(mvMatrix.dup()); |
|
|
- |
} |
|
|
} |
|
} |
|
function mvPopMatrix() { |
|
function mvPopMatrix() { |
|
if (mvMatrixStack.length == 0) { |
|
if (mvMatrixStack.length == 0) { |
|
throw "Invalid popMatrix!"; |
|
throw "Invalid popMatrix!"; |
|
} |
|
} |
|
mvMatrix = mvMatrixStack.pop(); |
|
mvMatrix = mvMatrixStack.pop(); |
|
- |
return mvMatrix; |
+ |
}</span> |
|
- |
} |
|
|
</pre> |
|
</pre> |
|
There shouldn't be anything surprising there. We have a list to hold our stack of matrices, and define push and pop appropriately. |
|
There shouldn't be anything surprising there. We have a list to hold our stack of matrices, and define push and pop appropriately. |
|
- |
Now, let's look at <code>mvRotate</code> |
|
|
- |
<pre> |
|
|
- |
function mvRotate(ang, v) { |
|
|
- |
var arad = ang * Math.PI / 180.0; |
|
|
- |
var m = Matrix.Rotation(arad, $V([v[0], v[1], v[2]])).ensure4x4(); |
|
|
- |
multMatrix(m); |
|
|
- |
} |
|
|
- |
</pre> |
|
|
- |
Again, pretty simple — all of the hard work of creating a matrix to represent a rotation is handled by the <a href="http://sylvester.jcoglan.com/">Sylvester</a> library. |
|
|
And... that's it! There are no more changes to go through. Now you know how to animate simple WebGL scenes. If you have any questions, comments, or corrections, please do leave a comment below. |
|
And... that's it! There are no more changes to go through. Now you know how to animate simple WebGL scenes. If you have any questions, comments, or corrections, please do leave a comment below. |
|
Next time, (to quote NeHe's preface to his <a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=05">lesson 5</a>) we'll "make the object into TRUE 3D object, rather than 2D objects in a 3D world". <a href="http://learningwebgl.com/blog/?p=370">Click here to find out how</a>. |
|
Next time, (to quote NeHe's preface to his <a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=05">lesson 5</a>) we'll "make the object into TRUE 3D object, rather than 2D objects in a 3D world". <a href="http://learningwebgl.com/blog/?p=370">Click here to find out how</a>. |
|
<span style="float: left; padding: 0px 20px 0px 0px;"><a href="http://learningwebgl.com/blog/?p=134"><< Lesson 2</a></span><span style="float: right; padding: 0px 20px 0px 0px;"><a href="http://learningwebgl.com/blog/?p=370">Lesson 4 >></a></span><br/> |
|
<span style="float: left; padding: 0px 20px 0px 0px;"><a href="http://learningwebgl.com/blog/?p=134"><< Lesson 2</a></span><span style="float: right; padding: 0px 20px 0px 0px;"><a href="http://learningwebgl.com/blog/?p=370">Lesson 4 >></a></span><br/> |
|
- |
<em>Acknowledgments: The code for <code>mvPushMatrix</code>, <code>mvPopMatrix</code>, and <code>mvRotate</code> comes from <a href="http://people.mozilla.com/~vladimir/webgl/spore/sporeview.html">Vladimir Vukićević</a>'s spore creature viewer. And, of course, I'm deeply in debt to <a href="http://nehe.gamedev.net/">NeHe</a> for his <a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=04">OpenGL tutorial</a> for the script for this lesson.</em>
|
+ |
<em>Acknowledgments: The code for <code>mvPushMatrix</code> and <code>mvPopMatrix</code> is adapted from <a href="http://people.mozilla.com/~vladimir/webgl/spore/sporeview.html">Vladimir Vukićević</a>'s spore creature viewer. And, of course, I'm deeply in debt to <a href="http://nehe.gamedev.net/">NeHe</a> for his <a href="http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=04">OpenGL tutorial</a> for the script for this lesson.</em>
|