<< Lesson 0Lesson 2 >>
Welcome to my first WebGL tutorial! This first lesson is based on number 2 in the NeHe OpenGL tutorials, which are a popular way of learning 3D graphics for game development. It shows you how to draw a triangle and a square in a web page. Maybe that’s not so exciting in itself, but it’s a great introduction to the foundations of WebGL; if you understand how this works, the rest should be pretty simple…
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.
More on how it all works below…
A quick 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. I’m writing these as I learn WebGL myself, so there may well be (and probably are) errors; use at your own risk. However, I’m fixing bugs and correcting misconceptions as I hear about them, so if you see anything broken then please let me know in the comments.
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 future lessons) from the repository there. Either way, once you have the code, load it up in your favourite text editor and take a look. It’s pretty daunting at first glance, even if you’ve got a nodding acquaintance with, say, OpenGL. Right at the start we’re defining a couple of shaders, which are generally regarded as relatively advanced… but don’t despair, it’s actually much simpler than it looks.
Like most programs, this WebGL page starts off by defining a bunch of lower-level functions which are used by the high-level code at the bottom. In order to explain it, I’ll start at the bottom and work my way up, so if you’re following through in the code, jump down to the bottom.
You’ll see the following HTML code:
<body onload="webGLStart();"> <a href="http://learningwebgl.com/blog/?p=28"><< Back to Lesson 1</a><br /> <canvas id="lesson01-canvas" style="border: none;" width="500" height="500"></canvas> <br/> <a href="http://learningwebgl.com/blog/?p=28"><< Back to Lesson 1</a><br /> </body>
This is the complete body of the page — everything else is in JavaScript (though if you got the code using “View source” you’ll see some extra junk needed by my website analytics, which you can ignore). Obviously we could put any number of normal HTML tags inside the <body> tags and build our WebGL image into a normal web page, but for this simple demo we’ve just got the WebGL and the links back to this blog post, and the <canvas> tag, which is where the 3D graphics live. Canvases are new for HTML 5.0 — they’re how it supports new kinds of JavaScript-drawn elements in web pages, both 2D and (through WebGL) 3D. We don’t specify anything more than the simple layout properties of the canvas in its tag, and instead leave all of the WebGL setup code to a JavaScript function called webGLStart, which you can see is called once the page is loaded.
Let’s scroll up to that function now and take a look at it:
function webGLStart() {
var canvas = document.getElementById("lesson01-canvas");
initGL(canvas);
initShaders();
initBuffers();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0)
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
setInterval(drawScene, 15);
}
It calls functions to initialise WebGL and the shaders that I mentioned earlier, passing into the former the canvas element on which we want to draw our 3D stuff, and then it initialises some buffers using initBuffers; buffers are things that hold the details of the the triangle and the square that we’re going to be drawing — we’ll talk more about those in a moment. Next, it does some basic setup for the GL engine, saying that when we clear the canvas we should make it black, that clearing should clear 100% of the stuff we’re showing, and that we should do depth testing (so that things drawn behind other things should be hidden by the things in front of them). These steps are implemented by calls to methods on a gl object — we’ll see how that’s initialised later. Finally, it uses setInterval to say that the function drawScene should be called every 15 milliseconds; this (as you’d expect from the name) draws the objects, using the buffers.
We’ll come back to initGL and initShaders later on, as they’re important in understanding how the page works, but first, let’s take a look at initBuffers and drawScene.
initBuffers first; taking it step by step:
var triangleVertexPositionBuffer; var squareVertexPositionBuffer;
We declare two global variables to hold the buffers. (In any real-world WebGL page you wouldn’t have a separate global variable for each object in the scene, but we’re using them here to keep things simple in a first lesson :-)
Next:
function initBuffers() {
triangleVertexPositionBuffer = gl.createBuffer();
We create a buffer for the triangle’s vertex positions. Vertices (don’t you just love irregular plurals?) are the points in 3D space that define the shapes we’re drawing. For our triangle, we will have three of them (which we’ll set up in a minute). This buffer is actually a bit of memory on the graphics card; by putting the vertex positions on the card in our initialisation code and then, when redrawing, essentially just telling WebGL to “draw those things I told you about earlier”, we can make our code really efficient. Of course, when it’s just three vertex positions as in this case, there’s not too much cost to pushing them up to the graphics card — but when you’re dealing with large models with tens of thousands of vertices, it can be a real advantage to do things this way.
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
This line tells WebGL that any following operations that act on buffers should use the one we specify. There’s always this concept of a “current array buffer”, and functions act on that rather than letting you specify which array buffer you want to work with. Odd, but I’m sure there a good performance reasons behind it…
var vertices = [
0.0, 1.0, 0.0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0
];
Next, we define our vertex positions as a JavaScript list. You can see that they’re at the points of an isosceles triangle with its centre at (0, 0, 0).
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
Now, we create a Float32Array object based on our JavaScript list, and tell WebGL to use it to fill the current buffer, which is of course our triangleVertexPositionBuffer. We’ll talk more about Float32Arrays in a future lesson, but for now all you need to know is that they’re a way of turning a JavaScript list into something we can pass over to WebGL for filling its buffers.
triangleVertexPositionBuffer.itemSize = 3;
triangleVertexPositionBuffer.numItems = 3;
The last thing we do with the buffer is to set two new properties on it. These are not something that’s built into WebGL, but they will be very useful later on. One nice thing (some would say, bad thing) about JavaScript is that an object doesn’t have to explicitly support a particular property for you to set it on it. So although the buffer object didn’t previously have itemSize and numItems properties, now it does. We’re using them to say that this 9-element buffer actually represents three separate vertex positions (numItems), each of which is made up of three numbers (itemSize).
Now we’ve completely set up the buffer for the triangle, so it’s on to the square:
squareVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
vertices = [
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
squareVertexPositionBuffer.itemSize = 3;
squareVertexPositionBuffer.numItems = 4;
}
All of that should be pretty obvious — the square has four vertex positions rather than 3, and so the array is bigger and the numItems is different.
OK, so that was what we needed to do to push our two objects’ vertex positions up to the graphics card. Now let’s look at drawScene, which is where we use those buffers to actually draw the image we’re seeing. Taking it step-by-step:
function drawScene() {
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
The first step is to tell WebGL a little bit about the size of the canvas using the viewport function; we’ll come back to why that’s important in a (much!) later lesson; for now, you just need to know that the function needs calling with the size of the canvas before you start drawing. Next, we clear the canvas in preparation for drawing on it:
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
…and then:
perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
Here we’re setting up the perspective with which we want to view the scene. By default, WebGL will draw things that are close by the same size as things that are far away (a style of 3D known as orthographic projection). In order to make things that are further away look smaller, we need to tell it a little about the perspective we’re using. For this scene, we’re saying that our (vertical) field of view is 45°, we’re telling it about the width-to-height ratio of our canvas, and saying that we don’t want to see things that are closer than 0.1 units to our viewpoint, and that we don’t want to see things that are further away than 100 units.
This perspective function is very useful, but is not built into WebGL, so it’s defined as a utility function further up in the code. I’ll describe in more detail how it works later on, but hopefully it should be clear how to use it without needing to know the details.
Now that we have our perspective set up, we can move on to drawing some stuff:
loadIdentity();
The first step is to “move” to the centre of the 3D scene. 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.
The current position and rotation is held in a matrix; as you probably learned at school, matrices can represent translations (moves from place to place), rotations, and other geometrical transformations. For reasons I won’t go into right now, you can use a single 4×4 matrix (not 3×3) to represent any number of transformations in 3D space; you start with the identity matrix — that is, the matrix that represents a transformation that does nothing at all — then multiply it by the matrix that represents your first transformation, then by the one that represents your second transformation, and so on. The combined matrix represents all of your transformations in one. The matrix we use to represent this current move/rotate state is called the model-view matrix, and by now you have probably worked out that the loadIdentity function that we just called sets the model-view matrix to the identity matrix so that we’re ready to start multiplying translations and rotations into it. Or, in other words, it’s moved us to an origin point from which we can move to start drawing our 3D world.
Sharp-eyed readers will have noticed that at the start of this discussion of matrices I said “in OpenGL”, not “in WebGL”. This is because, just like the perspective function, WebGL doesn’t have this stuff built in; again, we have to implement it ourselves, or copy utility functions that implement it for us. Once again, I’ll explain the details of how those utility functions work later, but you can use them without needing to know the details.
Right, let’s move on to the code that draws the triangle on the left-hand side of our canvas.
mvTranslate([-1.5, 0.0, -7.0]);
Having moved to the centre of our 3D space with loadIdentity, we start the triangle by moving 1.5 units to the left (that is, in the negative sense along the X axis), and seven units into the scene (that is, away from the viewer; the negative sense along the Z axis). (mvTranslate, as you might guess, at a low level translates to “multiply the model-view matrix by a translation matrix with the following parameters”.)
The next step is to actually start drawing something!
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
So, you remember that in order to use one of our buffers, we call gl.bindBuffer to specify a current buffer, and then call the code that operates on it. Here we’re selecting our triangleVertexPositionBuffer, then telling WebGL that the values in it should be used for vertex positions. I’ll explain a little more about how that works later; for now, you can see that we’re using the itemSize property we set on the buffer to tell WebGL that each item in the buffer is three numbers long.
Next, we have:
setMatrixUniforms();
This tells WebGL to take account of our current model-view matrix (and also the projection matrix, about which more later). This is required because all of this matrix stuff isn’t built in to WebGL. The way you can look at it is that you do all of the moving around with mvTranslate you want, but this all happens in JavaScript’s private space. setMatrixUniforms moves it over to the graphics card.
Once this is done, WebGL has an array of numbers that it knows should be treated as vertex positions, and it knows about our matrices. The next step tells it what to do with them:
gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);
Or, put another way, “draw the array of vertices I gave you earlier as triangles, starting with item 0 in the array and going up to the numItemsth element”.
Once this is done, WebGL will have drawn our triangle. Next step, draw the square:
mvTranslate([3.0, 0.0, 0.0])
We start by moving our model-view matrix three units to the right. Remember, we’re currently already 1.5 to the left and 7 away from the screen, so this leaves us 1.5 to the right and 7 away. Next:
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
So, we tell WebGL to use our square’s buffer for its vertex positions…
setMatrixUniforms();
…we push over the model-view and projection matrices again (so that we take account of that last mvTranslate), which means that we can finally:
gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
Draw the points. What, you may ask, is a triangle strip? Well, it’s a strip of triangles :-) More usefully, it’s a strip of triangles where the first three vertices you give specify the first triangle, then the last two of those vertices plus the next one specify the next triangle, and so on. In this case, it’s a quick-and-dirty way of specifying a square. In more complex cases, it can be a really useful way of specifying a complex surface in terms of the triangles that approximate it.
Anyway, once that’s done, we’ve finished our drawScene function.
}
If you’ve got this far, you’re definitely ready to start experimenting. Copy the code to a local file, either from GitHub or directly from the live version; if you do the latter, you need index.html, sylvester.js and glUtils.js. Run it up locally to make sure it works, then try changing some of the vertex positions above; in particular, the scene right now is pretty flat; try changing the Z values for the square to 2, or -3, and see it get larger or smaller as it moves back and forward. Or try changing just one or two of them, and watch it distort in perspective. Go crazy, and don’t mind me. I’ll wait.
…
Right, now that you’re back, let’s take a look at the support functions that made all of the code we just went over possible. As I said before, if you’re happy to ignore the details and just copy and paste the support functions that come above initBuffers in the page, you can probably get away with it and build interesting WebGL pages (albeit in black and white — colour’s the next lesson). But none of the details are difficult to understand, and by understanding how this stuff works you’re likely to write better WebGL code later on.
Still with me? Thanks :-) Let’s get the most boring of the functions out of the way first; the first one called by webGLStart, which is initGL. It’s near the top of the web page, and here’s a copy for reference:
var gl;
function initGL(canvas) {
try {
gl = canvas.getContext("experimental-webgl");
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
} catch(e) {
}
if (!gl) {
alert("Could not initialise WebGL, sorry :-(");
}
}
This is very simple. As you may have noticed, the initBuffers and drawScene functions frequently referred to an object called gl, which clearly referred to some kind of core WebGL “thing”. This function gets that “thing”, which is called a WebGL context, and does it by asking the canvas it is given for the context, using a standard context name. (As you can probably guess, at some point the context name will change from “experimental-webgl” to “webgl”; I’ll update this lesson and blog about it when that happens.) Once we’ve got the context, we again use JavaScript’s willingness to allow us to set any property we like on any object to store on it the width and height of the canvas to which it relates; this is so that we can use it in the code that sets up the viewport and the perspective at the start of drawScene. Once that’s done, our GL context is set up.
After calling initGL, webGLStart called initShaders. This, of course, initialises the shaders (duh ;-). We’ll come back to that one later, because first we should take a look at the utility functions that deal with the model-view matrix. Here’s the code:
var mvMatrix;
function loadIdentity() {
mvMatrix = Matrix.I(4);
}
function multMatrix(m) {
mvMatrix = mvMatrix.x(m);
}
function mvTranslate(v) {
var m = Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4();
multMatrix(m);
}
So, we define a variable called mvMatrix to hold the model-view matrix, and then define loadIdentity and mvTranslate using that and a utility function called multMatrix. If you know JavaScript, you’ll know that the matrix algebra functions that we’re using there aren’t part of the normal JavaScript API; they’re in fact provided to us by the two support JavaScript files I mentioned a moment ago, which we include right up near the top of our HMTL page:
<script src="sylvester.js" type="text/javascript"></script> <script src="glUtils.js" type="text/javascript"></script>
The first of these, Sylvester, is an Open Source library for doing matrix and vector algebra in JavaScript, and the second is a set of extensions to Sylvester that (I think) were orginally developed by Vladimir Vukićević.
Anyway, with the help of those simple functions and those helper libraries, we can maintain our model-view matrix. There’s another matrix that needs description, one that I alluded to earlier on — the projection matrix. As you may remember, the perspective function is not built into WebGL. But just like the process of moving things around and rotating them that is encapsulated in the model-view matrix, the process of making things that are far away look proportionally smaller than things close up is the kind of thing that matrices are really good at representing. And, as you’ve doubtless guessed by now, the projection matrix is the one that does just that. Here’s the code:
var pMatrix;
function perspective(fovy, aspect, znear, zfar) {
pMatrix = makePerspective(fovy, aspect, znear, zfar);
}
The makePerspective function is another one defined in that glUtils.js we included earlier; it returns the particular kind of matrix we need to apply the specified kind of perspective.
Right, now we’ve been through everything apart from the setMatrixUniforms function, which, as I said earlier, moves the model-view and projection matrices up from JavaScript to WebGL, and the scary shader-related stuff. They’re inter-related, so let’s start with some background.
Now, what is a shader, you may ask? Well, at some point in the history of 3D graphics they may well have been what they sound like they might be — bits of code that tell the system how to shade, or colour, parts of a scene before it is drawn. However, over time they have grown in scope, to the extent that they can now be better defined as bits of code that can do absolutely anything they want to bits of the scene before it’s drawn. And this is actually pretty useful, because (a) they can run on the graphics card, so they do what they do really quickly and (b) the kind of transformations they can do can be really convenient even in simple examples like this.
The reason that we’re introducing shaders in what is meant to be a simple WebGL example (they’re at least “intermediate” in OpenGL tutorials) is that we use them to get the WebGL system, hopefully running on the graphics card, to apply our model-view matrix and our projection matrix to our scene without us having to move around every point and every vertex in (relatively) slow JavaScript. This is incredibly useful, and worth the extra overhead.
So, here’s how they are set up. As you will remember, webGLStart called initShaders, so let’s go through that step-by-step:
var shaderProgram;
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(shaderProgram);
As you can see, it uses a function called getShader to get two things, a “fragment shader” and a “vertex shader”, and then attaches them both to a WebGL thing called a “program”. A program is a bit of code that lives on the WebGL side of the system; you can look at it as a way of specifying something that can run on the graphics card. As you would expect, you can associate with it a number of shaders, each of which you can see as a snippet of code within that program; specifically, each program can hold one fragment and one vertex shader. We’ll look at them shortly.
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
Once the function has set up the program and attached the shaders, it gets a reference to an “attribute”, which it stores in a new field on the program object called vertexPositionAttribute. Once again we’re taking advantage of JavaScript’s willingness to set any field on any object; program objects don’t have a vertexPositionAttribute field by default, but it’s convenient for us to keep the two values together, so we just make the attribute a new field of the program.
So, what’s the vertexPositionAttribute for? As you may remember, we used it in drawScene; if you look now back at the code that set the triangle’s vertex positions from the appropriate buffer, you’ll see that the stuff we did associated the buffer with that attribute. You’ll see what that means in a moment; for now, let’s just note that we also use gl.enableVertexAttribArray to tell WebGL that we will want to provide values for the attribute using an array.
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
}
The last thing initShaders does is get two more values from the program, the locations of two things called uniform variables. We’ll encounter them soon; for now, you should just note that like the attribute, we store them on the program object for convenience.
Now, let’s take a look at getShader:
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var str = "";
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3)
str += k.textContent;
k = k.nextSibling;
}
var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
This is another one of those functions that is much simpler than it looks. All we’re doing here is looking for an element in our HTML page that has an ID that matches a parameter passed in, pulling out its contents, creating either a fragment or a vertex shader based on its type (more about the difference between those in a future lesson) and then passing it off to WebGL to be compiled into a form that can run on the graphics card. The code then handles any errors, and it’s done! Of course, we could just define shaders as strings within our JavaScript code and not mess around with extracting them from the HTML — but by doing it this way, we make them much easier to read, because they are defined as scripts in the web page, just as if they were JavaScript themselves.
Having seen this, we should take a look at the shaders’ code:
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif
void main(void) {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
}
</script>
The first thing to remember about these is that they are not written in JavaScript, even though the ancestry of the language is clearly similar. In fact, they’re written in a special shader language that owes a lot to C (as, of course, does JavaScript). The first of them, the fragment shader, does pretty much nothing; it has a bit of obligatory boilerplate code to tell the graphics card how precise we want it to be with floating-point numbers, then simply specifies that everything that is drawn will be drawn in white. (How to do stuff in colour is the subject of the next lesson.) The second shader is a little more interesting. It’s a vertex shader — which, you’ll remember, means that it’s a bit of graphics-card code that can do pretty much anything it wants with a vertex. Associated with it, it has two uniform variables called uMVMatrix and uPMatrix. Uniform variables are useful because they can be accessed from outside the shader — indeed, from outside its containing program, as you can probably remember from when we extracted their location in initShaders, and from the code we’ll look at next, where (as I’m sure you’ve realised) we set them to the values of the model-view and the projection matrices. You might want to think of the shader’s program as an object (in the object-oriented sense) and the uniform variables as fields. Now, the shader is called for every vertex, and the vertex is passed in to the shader code as aVertexPosition, thanks to the use of the vertexPositionAttribute in the drawScene, when we associated the attribute with the buffer. The tiny bit of code in the shader’s main routine just multiplies the vertex by the model-view and the projection matrices, and pushes out the result as the final position of the vertex.
So, webGLStart called initShaders, which used getShader to load the fragment and the vertex shaders from scripts in the web page, so that they could be compiled and passed over to WebGL and used later when rendering our 3D scene.
After all that, the only remaining unexplained code is setMatrixUniforms, which is easy to understand once you know everything above :-)
function setMatrixUniforms() {
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, new Float32Array(pMatrix.flatten()));
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, new Float32Array(mvMatrix.flatten()));
}
So, using the references to the uniforms that represent our projection matrix and our model-view matrix that we got back in initShaders, we send WebGL the values from our JavaScript-style matrices.
Phew! That was quite a lot for a first lesson, but hopefully now you (and I) understand all of the groundwork we’re going to need to start building something more interesting — colourful, moving, properly three-dimensional WebGL models. To find out more, read on for lesson 2.
Acknowledgments: obviously I’m deeply in debt to NeHe for his OpenGL tutorial for the script for this lesson, but I’d also like to thank Benjamin DeLillo and Vladimir Vukićević for their WebGL sample code, which I’ve investigated, analysed, probably completely misunderstood, and eventually munged together into the code on which I based this post :-). Also, many thanks to James Coglan for making his excellent Sylvester library Open Source.


@tuni — WebGL is a JavaScript binding for OpenGL ES 2.0. That’s the version of OpenGL that was created for embedded systems, for example high-performance mobile phones like the iPhone, newer Android phones, and the Nokia N900.
That means that it’s easy for anyone who knows OpenGL *ES* to pick up, but might be a bit harder for people who are used to the larger “desktop” OpenGL APIs.
Thanks for the lesson! That was great experience and I`ll surely try the next one.
P.S.: I`ve also had some problems like in some of earlier comments – there was only black canvas without any figures on screen.
It seems that an issue in glUtils.js file could cause it – “makeOrtho” function appeared twice in it, so solution for me was to delete one.
Hi Roman, glad you liked the lesson!
Many thanks for pointing out the bug in gsUtils.js — it’s definitely wrong, and while I really can’t work out how it would break things so thoroughly (I’d expect JavaScript to just replace the first version with the second), it needed fixing. I’ve corrected it and will roll the new version out later on today.
Cheers,
Giles
Hi,
I’m new here and I’m french so sorry for my english lvl !
But I love the idea of webGl and i have a formation about opengl.
I try to do my self with my own index.html the lesson one, but it doesn’t work.
I only do cut and copy from your code. and I use chrome with the enable of webGL. I can see the result of the lesson on your web site. So to resume it’s only the cut and copy of the code that doesn’t work.
Any Idea ?
Hi Meriadeg,
Have you put copies of sylvester.js and glUtils.js in the same directory as your copy of the index.html file?
Cheers,
Giles
Hi Giles and tks for you help.
But yes it’s already done, so in my directory I have the 3 files.
index and the two file.js
The result of the loading of the index is only the big black square. the shape of triangle and the little square inside isn’t visible.
I read through a number of your wonderful tutorials. I really appreciate your effort and the benefit to me (my understanding.) It might serve as a model of pedagogy in any field. I am mystified how you pulled all that together. I am now coding up your lessons and viewing in Chromium.
That said, I will now fuss, not about what you have done, but about my frustrations concerning bringing 3D experiences to the Web. I am frustrated because I feel a web browser should just “get” 3D the way is gets text or time. In particular, I have difficulty imagining that web developers have the inclination or capacity to code against a thin interface to OpenGL. (Think of the lines of javascript code required to make something like synchronization of 3D pages possible!) Perhaps we have different visions of a 3D web.
Anyway, I sincerely compliment your tutorials because they capture abstractions, but those very abstractions reveal some weaknesses of WebGL, as a foundation for realizing a 3D web. That is, for example, I don’t want to actually program a GPU in javascript. That is wacky from my point of view. I want to describe shapes and have the browser render them. Specifically, I don’t want to have to boiler plate javascript like
var str = “”;
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3) {
str += k.textContent;
}
k = k.nextSibling;
}
If I am checking node type codes in javascript, as a rule, to draw a triangle or cube, the browser really doesn’t know what it is doing. Its a all clever workaround. The browser has no idea what a shader is. ARGH.
If Sylvester API were native in a browser, then that code would be comfortable for me. But currently it holds abstractions (one learns as a freshman or sophomore in college) that have no business being interpreted in javascript. Comparing the traditional web to the 3D web, this is analogous to using the Gregorian Calendar vs coding it up; a browser doesn’t need to know about time operations, daylight saving time, and time zones the way it doesn’t need to know about matrix operations in space. NOT! Duh it needs to know both, before one writes scripts.
Sigh, perhaps the 3D web is not something “on top of” the 2D web. It is just a new web with new browsers. However one whines about the failures of Second Life, they have a browser that views and creates rich 3D worlds: Create a thing in 3D, drop a script into it, it then interacts in its world with whatever it encounters. I don’t really want to make lots of triangles, I want to make worlds.
Excuse me for the rant.
And I am sincere about my appreciation for your lessons.
Erf, so… and my problem ? :)
@Mériadeg — hmmm, there’s nothing obviously wrong, then. Have you looked at the JavaScript console for error messages? (It’s under “Developer” on the same menu as cut/copy/paste./
@Don — I’m very glad you like the lessons!
Re: the rant — well, one rant deserves another ;-) You’re entirely right that a lot of — probably most — people will want to code 3D graphics for the web using something different to a low-level shader-based system. But I think WebGL is the right thing for the browser makers to be focussing on right now. It gives the lowest-possible level abstraction that could work across all web-enabled devices, which makes it more flexible than any higher-level system could be. This means that once it’s complete, there will be a solid, guaranteed-cross-platform basis on which people can build the real world-building stuff that will make the 3D Web take off.
WebGL isn’t designed as a replacement for Second Life, it’s designed as a platform on top of which Linden can build a new version of Second Life. But because it’s flexible, it will also allow Linden’s competitors to build their own completely different world-building engines, and will allow people like the X3DOM team to build libraries that allow you to specify 3D models using HTML-like markup, and other developers to build easy-to-use JavaScript libraries like SceneJS, SpiderGL, or GLGE, while still letting the kind of people who currently write computer games keep doing the kind of serious GPU hacking they need to do.
Does that make sense?
Uncaught SyntaxError: Unexpected token < sylvester.js:8
Uncaught SyntaxError: Unexpected token < glUtils.js:8
Uncaught ReferenceError: makePerspective is not defined index.hmtl:128
there are log of errors !
Someone have an Idea ?
Interesting. What are the contents of sylvester.js and glUtils.js? glUtils.js should start with stuff like this:
// augment Sylvester some
Matrix.Translation = function (v)
{
if (v.elements.length == 2) {
var r = Matrix.I(3);
r.elements[2][0] = v.elements[0];
Ok !!
I think it’s that, but I’ll make some try later and i’ll feedback here
My glUtils.js ( and the other ) come frome the directory that you propose on the lesson 0. I only do right click and record the target of the link.
So I had two files glUtils.js.htm and sylverster.js.html, then I only remove the htm.extension
That’s why I have at the beginning of my files something like that <!DocType…
Ok,
SO I understand my errors and corrected it. Now all is fine !
Tks for your help Giles :) Maybe later i’ll have other questions !
That’s great news, glad you could sort it out!
Hi Giles,
Thanks for porting useful examples from the NeHe tutorials in the context of WebGL. I’m looking for a book to supplement my learning on this. Will something covering OpenGL ES 2.0 suffice? I’d be looking for a comprehensive book that introduces the graphics programming scene (as closely related to WebGL as possible) to someone familiar with programming already. Can you recommend any?
Thanks in advance!
Hi Michael,
I really recommend the OpenGL ES 2.0 Programming Guide — it’s very dense but if you’re already an experienced programmer it’s probably the best bet.
Here’s the Amazon page: http://www.amazon.com/OpenGL-ES-2-0-Programming-Guide/dp/0321502795/
Cheers,
Giles
Cheers Giles :)
I just discovered your site – fantastic! Perfect for someone like me with a bit of C and OpenGL (1.0) experience. I’ve not yet looked at ’shaders’, but this was a great explanation here of why you’d want to use them with javascript. (I wondered how people were drawing that much geometry!)
Overall I think you did a very good job, and I’m really glad I found this tutorial. I am an experienced programmer (particularly with using web-related languages such as JS), but I have no experience with 3D programming at all so I fit your target audience pretty well. My biggest criticism though is that you tried to pack too much into the first step.
Particularly, I think it would have been a lot better had you not tried to put your shader scripts in the DOM to parse it later. It was a clever idea, and one that would certainly be useful for real applications of WebGL with complicated shaders, but for those of us just trying to learn the very basics of WebGL, it makes it a lot more complicated and makes it hard to separate your syntax from the new syntax in HTML 5, etc.
It also might have been useful if you pointed out early on that gl was a variable name you had chosen and had initialized in the initGL() function. I went through at least half of the tutorial thinking it must be some new sort of global object that is always set.
Joshua — thanks for the feedback. There’s a lot in this step because I was following some pre-existing OpenGL tutorials; if I were to write it again now, I agree that it would probably be better start from scratch and put a bit less in it.
Re: the shaders in DOM tags — the problem is that they have to go somewhere. I’m loath to put them in strings, as that would be harder to read and to explain. And using XmlHttpRequest to load them would be confusing to non web developers. Is there some other way that I’m missing?
Re: the gl variable — good point, I’ll see if I can put something appropriate in to explain that.
Can you be a bit more specific about units and perspective. How i can start draw in left up corner?
thx
@Milos — have you read this comment: http://learningwebgl.com/blog/?p=28&cpage=1#comment-2846 ?
If you want to draw in the very top left corner of the canvas, the most reliable way would be to switch off perspective (set the projection matrix to the identity matrix) and then draw at (-1, 1, 0).
How to change position of camera. By default it look in negative direction of z axe, i want to put camera on 10 units on y axe and to look at negative direction of y axe. I find in glUtils.js function makeLookat but i don’r know where to call it and how to use it?
@Milos — keep going through the tutorials — that’s introduced at around lesson 10.
Hi !
I only see that we can draw Triangle or TriangleStrip, but in openGl, we can draw quad too, Can someone explain why for me ?
@Mériadeg at a guess, they just dropped quads because you can easily do them with triangle strips…
ok, Thanks :)
Hi,
can someone explain me why the examples work correctly if I run them on this site, but if I run them on my PC (copying and pasting the code) they no longer work?
Both Chromium and minefield have the same problem…
Probably beacause like me for my first try, I didn’t copy in the same folder than index.htm the sylvester.js and glUtils.js
You were damn right Mériadeg… a trivial error :P:P:P
“The second shader is a little more interesting. It’s a vertex shader — which, you’ll remember, means that it’s a bit of graphics-card code that can pretty much anything it wants with a vertex”
I think your missing ‘do’ between ‘can’ and ‘pretty’.
You’re right, thanks for spotting that! I’ve fixed it.
Thank you for this nice (and funny) tutorial to read.
I learned a lot! I have years of (daily )experience with javascript and played now and then with 3dsmax.
3D is an new world for me. Now, on to the next lesson!
@Mario — thanks! Glad you like the lessons :-)
Thanks for writing these. I’m afraid that this tutorial is WAY too much for lesson one though
It was very well written, but a novice would not understand it.
This needs to be broken into smaller units.
After looking over things, what I had suggested seems less possible.
Hi Tom,
Glad you like the writing style, at least :-) Anyway, as I guess you’ve realised, there’s unfortunately just not that much could be pulled out. If I were to start from scratch, and (perhaps more importantly) not follow the NeHe tutorials so closely, then I could probably put the stuff about the model-view and the perspective matrix into a second lesson, or even into second and third. But that’s about it.
The fundamental problem here is that WebGL is very low-level, so it has a steep learning curve. There are great reasons for this — if the browser implementors just provide a really low-level platform that can be thoroughly standardised and implemented on all systems, many people can create libraries to make it easier to program for, and ultimately one or more of those will win (and perhaps wind up being built into later versions of the browsers). A nice analogy is jQuery’s dominance of general-purpose JavaScript. But it does make the very first hurdle for nascent WebGL developers quite high.
On the positive side, I think you’re too gloomy about novices’ ability to work this stuff out. Judging from the comments here, I think anyone with a bit of programming knowledge and, more importantly, enough motivation, can get there eventually.
Cheers,
Giles
I agree with you Giles, I think your first lesson it’s a good way for leaning webGl. In fact, Only shaders are difficult to understand for a novice. But webGl is for someone with a mimimum of knowledge on openGl.
For me, the real difficulty is the link between webGl and javascript, because I can programming in C++ and openGl, but I have no experiences with javascript, and I don’t know if Javascript bring new concept that I have to learn.
Thanks, Mériadeg. I guess in an ideal world there would be three different sets of tutorials:
* For people like you who know OpenGL but not Javascript, so need an “expert-level” concise introduction but no details.
* For people who know Javascript but not OpenGL, who need more detail and an introduction to 3D concepts, but who know web programming back to front.
* For people like me who are competent programmers, but are learning 3D and JavaScript as they go along, who need lots of detail about everything.
People who already know both OpenGL and Javascript probably don’t need tutorials at all :-) And people who don’t know anything about how to program — well, I doubt that they should start learning WebGL before they start other, simpler kinds of programming.
What about to calculate the value fo uPMatrix * uMVMatrix in javaScript?
great!
@Jed — yes, that would work, and might save time (though it’s possible that calculating it for every vertex in the shader would still be faster than calculating it just once in slow JavaScript). Still, for more complex shaders you often need to have the projection and the model-view matrices separately — so it wouldn’t help so much there.