You are previewing HTML5 Canvas.

HTML5 Canvas

Cover of HTML5 Canvas by Steve Fulton... Published by O'Reilly Media, Inc.
  1. HTML5 Canvas
    1. SPECIAL OFFER: Upgrade this ebook with O’Reilly
    2. A Note Regarding Supplemental Files
    3. Preface
      1. Running the Examples in the Book
      2. What You Need to Know
      3. How This Book Is Organized
      4. Conventions Used in This Book
      5. Using Code Examples
      6. We’d Like to Hear from You
      7. Safari® Books Online
      8. Acknowledgments
    4. 1. Introduction to HTML5 Canvas
      1. The Basic HTML Page
      2. Basic HTML We Will Use in This Book
      3. The Document Object Model (DOM) and Canvas
      4. JavaScript and Canvas
      5. HTML5 Canvas “Hello World!”
      6. Debugging with Console.log
      7. The 2D Context and the Current State
      8. The HTML5 Canvas Object
      9. Another Example: Guess The Letter
      10. What’s Next
    5. 2. Drawing on the Canvas
      1. The Basic File Setup for This Chapter
      2. The Basic Rectangle Shape
      3. The Canvas State
      4. Using Paths to Create Lines
      5. Advanced Path Methods
      6. Compositing on the Canvas
      7. Simple Canvas Transformations
      8. Filling Objects with Colors and Gradients
      9. Filling Shapes with Patterns
      10. Creating Shadows on Canvas Shapes
      11. What’s Next
    6. 3. The HTML5 Canvas Text API
      1. Displaying Basic Text
      2. Setting the Text Font
      3. Text and the Canvas Context
      4. Text with Gradients and Patterns
      5. Width, Height, Scale, and toDataURL() Revisited
      6. Final Version of Text Arranger
      7. What’s Next
    7. 4. Images on the Canvas
      1. The Basic File Setup for This Chapter
      2. Image Basics
      3. Simple Cell-Based Sprite Animation
      4. Advanced Cell-Based Animation
      5. Applying Rotation Transformations to an Image
      6. Creating a Grid of Tiles
      7. Zooming and Panning an Image
      8. Pixel Manipulation
      9. Copying from One Canvas to Another
      10. What’s Next
    8. 5. Math, Physics, and Animation
      1. Moving in a Straight Line
      2. Bouncing Off Walls
      3. Curve and Circular Movement
      4. Simple Gravity, Elasticity, and Friction
      5. Easing
      6. What’s Next?
    9. 6. Mixing HTML5 Video and Canvas
      1. HTML5 Video Support
      2. Converting Video Formats
      3. Basic HTML5 Video Implementation
      4. Preloading Video in JavaScript
      5. Video and the Canvas
      6. Video on the Canvas Examples
      7. Animation Revisited: Moving Videos
      8. What’s Next?
    10. 7. Working with Audio
      1. The Basic <audio> Tag
      2. Audio Formats
      3. Audio Tag Properties, Functions, and Events
      4. Playing a Sound with No Audio Tag
      5. Creating a Canvas Audio Player
      6. Case Study in Audio: Space Raiders Game
      7. What’s Next
    11. 8. Canvas Game Essentials
      1. Why Games in HTML5?
      2. Our Basic Game HTML5 File
      3. Our Game’s Design
      4. Game Graphics: Drawing with Paths
      5. Animating on the Canvas
      6. Applying Transformations to Game Graphics
      7. Game Graphic Transformations
      8. Game Object Physics and Animation
      9. A Basic Game Framework
      10. Putting It All Together
      11. The player Object
      12. Geo Blaster Game Algorithms
      13. The Geo Blaster Basic Full Source
      14. Rock Object Prototype
      15. What’s Next
    12. 9. Combining Bitmaps and Sound
      1. Geo Blaster Extended
      2. Creating a Dynamic Tile Sheet at Runtime
      3. A Simple Tile-Based Game
      4. What’s Next
    13. 10. Mobilizing Games with PhoneGap
      1. Going Mobile!
      2. Creating the iOS Application with PhoneGap
      3. Beyond the Canvas
      4. What’s Next
    14. 11. Further Explorations
      1. 3D with WebGL
      2. Multiplayer Applications with ElectroServer 5
      3. Conclusion
    15. Index
    16. About the Authors
    17. Colophon
    18. SPECIAL OFFER: Upgrade this ebook with O’Reilly
O'Reilly logo

Chapter 11. Further Explorations

There are many emerging technologies and frameworks that can help take HTML5 Canvas into rarely explored areas. In this chapter, we will cover a couple of those areas: using Canvas for 3D with WebGL, and using Canvas for multiplayer applications. Both of these areas are still experimental, requiring you to either download beta/developer versions of browsers, or launch browsers using command-line switches so you can turn various technologies off and on.

This chapter is structured a bit differently. The discussions are focused on giving you some tools and information about these new and emerging areas for Canvas. While we will offer code, examples, and some explanation, it’s geared more toward getting you started on the path to learning than on teaching you how every detail works. We have structured these two topics in this way for one very simple reason: they are incomplete. With APIs, libraries, locations, browsers, names, etc., all subject to change, we are not comfortable presenting these topics as finished and ready to go. You need to approach this chapter knowing that things are bound to go wrong or change frequently.

Having said that, even though these topics are experimental, that does not mean you cannot use them right now, nor does it mean they are not useful or capable of doing very cool things. You just need to be aware of the pitfalls before treading forward.

3D with WebGL

The 2D capabilities of HTML5 Canvas are impressive, but what about 3D? There is no “production” 3D context available in the standard version of any web browser at this time. However, the best support for a 3D context will probably come in the form of WebGL.

What Is WebGL?

WebGL is a JavaScript API that gives programmers access to the 3D hardware on the user’s machine. Currently, it is only supported by the debug/development versions of Opera, Firefox, and Chrome. The API is managed by Kronos, the same organization that manages OpenGL. In fact, much of WebGL is similar to programming in OpenGL. This is both good and bad. It’s good because it’s a standard programming interface that is recognizable to many developers, but bad because it is not as easy to learn as the 2D Canvas context.

How Do I Test WebGL?

First, you need to find a web browser that supports WebGL. When trying to run a WebGL application, a browser that does not support WebGL might give a message like the one shown in Figure 11-1.

Trying to run WebGL in a standard web browser

Figure 11-1. Trying to run WebGL in a standard web browser

Note

Chromium, the open source version of Chrome, will display WebGL. You can download the latest development build of Chromium from http://build.chromium.org/f/chromium/continuous/.

Once you have a browser that can display WebGL, you need to write the code to make it happen. You start that process by accessing the WebGL context instead of the Canvas 2d context. So, instead of this code, which we have used throughout this book:

context = theCanvas.getContext("2d");

We reference the experimental-webgl context, like this:

gl = theCanvas.getContext("experimental-webgl");

How Do I Learn More About WebGL?

The best place to learn about WebGL is at http://learningwebgl.com/. This site has an FAQ, a blog, and some helpful low-level lessons on how to create apps using WebGL. You can also find a ton of great content about WebGL at http://developer.mozilla.org.

One warning, though: programming WebGL is not for the uninitiated. Although WebGL is based on OpenGL, it is still a very low-level API, meaning you will need to create everything by hand. At the end of this section, we will guide you toward some higher-level libraries that should make this process a bit easier.

What Does a WebGL Application Look Like?

Now we are going to show you a WebGL application demo that rotates a 3D cube on Canvas (see Figure 11-2). Since we are not experts in 3D graphics, we will forgo our practice of describing every line of code in the example; instead, we will highlight interesting sections of code to help you understand what is happening.

This demo is based on Lesson 4 from Giles Thomas’s Learning WebGL website (http://learningwebgl.com/blog/?p=370). While this is only one short demo, it should give you a very good idea of how to structure and build code for a WebGL application.

Note

Much of this code has been adapted from the work of Giles Thomas with his expressed, written permission.

3D rotating cube (CH11EX1.html)

Figure 11-2. 3D rotating cube (CH11EX1.html)

JavaScript libraries

First, we add some JavaScript libraries. Modernizr 1.6 includes a test for WebGL support in a web browser. This version was freshly released, but it could be updated with new features at any time (in fact, at the time of this writing, this had been updated to version 1.7). It is necessary to make sure you have the most recent versions of your libraries:

<script src="modernizr-1.6.min.js"></script>

We now need to include some JavaScript libraries to assist with our application. sylvester.js and glUtils.as are two libraries that you will find included for most apps that use WebGL. sylvester.js (http://sylvester.jcoglan.com/) is a library that helps when performing vector and matrix math calculations in JavaScript. glUtils.as is an extension for sylvester.js, specifically for helping with math related to WebGL:

<script type="text/javascript" src="sylvester.js"></script>
<script type="text/javascript" src="glUtils.js"></script>

Shaders

Shaders are pieces of code that run directly on a graphics card. They describe how a scene—how you refer to a 3D canvas when working with WebGL—should be rendered. Many of these little programs perform mathematical transformations that would otherwise run very slowly in JavaScript. In fact, we are pointing these out because they are not JavaScript; they are written in a way that WebGL can understand. These sections of code will be read in like text files and passed to the graphics hardware. Full discussions of topics like shaders are far out of scope for this little section of the book, but we will tell you a bit about each one of them to help set the tone for what comes next.

The first shader below is a fragment shader, which tells the graphics card that we will be using floating-point numbers and blended colors. The second shader is the vertex shader. It works with the vertices (defined points in 3D space used to create 3D objects) and will be used for every vertex we draw onto the Canvas 3D context:

<script id="shader-fs" type="x-shader/x-fragment">
  #ifdef GL_ES
  precision highp float;
  #endif

  varying vec4 vColor;

  void main(void) {
    gl_FragColor = vColor;
  }
</script>

<script id="shader-vs" type="x-shader/x-vertex">
  attribute vec3 aVertexPosition;
  attribute vec4 aVertexColor;

  uniform mat4 uMVMatrix;
  uniform mat4 uPMatrix;

  varying vec4 vColor;

  void main(void) {
    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
    vColor = aVertexColor;
  }
</script>

Testing for WebGL support with Modernizr

The structure of the code in this example is much like the other applications we have written in this book. However, it has been modified to work with the specific needs of the 3D context. In the canvasApp() function, we need to test to see whether the browser has WebGL support. This is easily accomplished by using the Modernizr.webgl static constant in Modernizr 1.6:

if ( !webglSupport()) {
   alert("Unable to initialize WebGL");
   return;
}
function webglSupport() {
   return Modernizr.webgl;
}

Initialization in canvasApp()

In canvasApp() we still get a context, but this time it is the experimental-webgl context. Also, just like in our other apps, we still call drawScreen() on an interval to render the canvas:

var theCanvas = document.getElementById("canvasOne");
webGLContext = theCanvas.getContext("experimental-webgl");

setInterval(drawScreen, 33);

However, there is additional code in canvasApp() required to set up the application to rotate the cube. A couple of the most important initialization steps are the calls to initShaders() and initBuffers():

initShaders();
initBuffers();

The initShaders() function itself calls a function named getShader() to load in the text of the shader programs we have already defined. You can see the code for these functions in the code listing a bit later in Example 11-1.

Note

You can learn about the shaders used in this program in “Lesson 2—Adding colour” on the LearningWebGL website: http://learningwebgl.com/blog/?p=134.

Once we have loaded the shader programs, we need to create the buffers. Buffers refer to space in the video card’s memory that we set aside to hold the geometry describing our 3D objects. In our case, we need to create buffers to describe the cube we will rotate on the canvas. We do this in initBuffers().

The initBuffers() function contains a lot of code, but we’ll discuss only a couple very interesting sections. The first is the Vertex Position buffer, which describes the vertices that make up the sides of the cube:

webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexPositionBuffer);
      vertices = [
        // Front face
        -1.0, -1.0,  1.0,
         1.0, -1.0,  1.0,
         1.0,  1.0,  1.0,
        -1.0,  1.0,  1.0,

        // Back face
        -1.0, -1.0, -1.0,
        -1.0,  1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0, -1.0, -1.0,

        // Top face
        -1.0,  1.0, -1.0,
        -1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0, -1.0,

        // Bottom face
        -1.0, -1.0, -1.0,
         1.0, -1.0, -1.0,
         1.0, -1.0,  1.0,
        -1.0, -1.0,  1.0,

        // Right face
         1.0, -1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0,  1.0,  1.0,
         1.0, -1.0,  1.0,

        // Left face
        -1.0, -1.0, -1.0,
        -1.0, -1.0,  1.0,
        -1.0,  1.0,  1.0,
        -1.0,  1.0, -1.0,
      ];

The Vertex Color buffer holds information about the color that will appear on each side of the cube. These values are set as percentages of RBGA values (red, green, blue, alpha):

webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexColorBuffer);
      var colors = [
        [1.0, 1.0, 1.0, 1.0],     // Front face
        [0.9, 0.0, 0.0, 1.0],     // Back face
        [0.6, 0.6, 0.6, 1.0],     // Top face
        [0.6, 0.0, 0.0, 1.0],     // Bottom face
        [0.3 ,0.0, 0.0, 1.0],     // Right face
        [0.3, 0.3, 0.3, 1.0],     // Left face
      ];

The Vertex Index buffer is kind of like a map that builds the object (our cube) based on the colors specified in Vertex Color (the order of these elements) and the vertices specified in the Vertex Position buffer. Each of these sets of three values represents a triangle that will be drawn onto the 3D context:

webGLContext.bindBuffer(webGLContext.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
      var cubeVertexIndices = [
        0, 1, 2,      0, 2, 3,    // Front face
        4, 5, 6,      4, 6, 7,    // Back face
        8, 9, 10,     8, 10, 11,  // Top face
        12, 13, 14,   12, 14, 15, // Bottom face
        16, 17, 18,   16, 18, 19, // Right face
        20, 21, 22,   20, 22, 23  // Left face
      ]

Again, there is more code in initBuffers() than we described here, but start with these three sections when you want to play with the code and make your own objects.

Animating the cube

Now that you know a bit about creating an object in WebGL, let’s learn about animating the cube on the canvas. Similar to what we did in the 2D context, we use the drawScreen() function to position, draw, and animate objects in the 3D context. The first thing we do here is set up the viewport, which defines the canvas’ view of the 3D scene. Next, we clear the canvas and then set up the perspective. The perspective has four parameters:

Field of view

The angle at which we will view the 3D scene (25 degrees).

Width-to-height ratio

The radio of width to height of the current size of the canvas (500×500).

Minimum units

The smallest unit size away from our viewport we want to display (0.1).

Maximum units

The furthest unit size away from our viewport that we want to see (100.0).

function drawScreen() {

   webGLContext.viewport(0, 0, webGLContext.viewportWidth, 
       webGLContext.viewportHeight);
   webGLContext.clear(webGLContext.COLOR_BUFFER_BIT | 
       webGLContext.DEPTH_BUFFER_BIT);

   perspective(25, (webGLContext.viewportWidth / webGLContext.viewportHeight),
                    0.1, 100.0);

Next, we move to the center of the 3D scene, calling loadIdentity() so we can start drawing. We then call mvTranslate(), passing the locations on the x, y, and z axes to draw the cube. To rotate the cube, we call a function named mvPushMatrix(), and later mvPopMatrix(), which is similar to how we called context.save() and context.restore() when rotating objects on the 2D canvas. The call to mvRotate() then makes the cube rotate from the center, tilted up and to the right:

loadIdentity();

mvTranslate([0, 0.0, -10.0])

mvPushMatrix();
mvRotate(rotateCube, [0, .5, .5]);

Next, we draw the cube by binding the buffers that hold the vertices and color information that we set up earlier for the cube’s sides. We then draw each side, made up of two triangles each:

webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexPositionBuffer);
webGLContext.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 
    cubeVertexPositionBuffer.itemSize, webGLContext.FLOAT, false, 0, 0);

webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexColorBuffer);
webGLContext.vertexAttribPointer(shaderProgram.vertexColorAttribute, 
    cubeVertexColorBuffer.itemSize, webGLContext.FLOAT, false, 0, 0);

webGLContext.bindBuffer(webGLContext.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
setMatrixUniforms();webGLContext.drawElements(webGLContext.TRIANGLES, cubeVertexIndexBuffer.numItems, 
    webGLContext.UNSIGNED_SHORT, 0);

mvPopMatrix();

Finally, we increase the rotateCube variable so that the next time drawScreen() is called, the cube will be updated with a new angle. The code below adds 2 degrees to the rotation angle each time drawScreen() is called:

  rotateCube += 2;

   }

Full Code Listing

Example 11-1 gives the full code listing for CH11EX1.html. Notice that many of the code styles and constructs we have used over the past 10 chapters are still in place in this application. Besides the obvious inclusion of code related directly to WebGL, this application operates essentially the same way as the other apps we discussed in this book.

Example 11-1. WebGL test

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH11EX1: WebGL Test </title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript" src="sylvester.js"></script>
<script type="text/javascript" src="glUtils.js"></script>

<script id="shader-fs" type="x-shader/x-fragment">
   #ifdef GL_ES
   precision highp float;
   #endif

   varying vec4 vColor;

   void main(void) {
     gl_FragColor = vColor;
   }
</script>

<script id="shader-vs" type="x-shader/x-vertex">
   attribute vec3 aVertexPosition;
   attribute vec4 aVertexColor;

   uniform mat4 uMVMatrix;
   uniform mat4 uPMatrix;

   varying vec4 vColor;

   void main(void) {
     gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
     vColor = aVertexColor;
   }
</script>

<script type="text/javascript">
window.addEventListener("load", eventWindowLoaded, false);

function eventWindowLoaded () {
   canvasApp();
}

function canvasSupport () {
   return Modernizr.canvas;
}

function webglSupport() {
   return Modernizr.webgl;
}
function canvasApp () {

function drawScreen() {

      webGLContext.viewport(0, 0, webGLContext.viewportWidth, 
          webGLContext.viewportHeight);
      webGLContext.clear(webGLContext.COLOR_BUFFER_BIT | webGLContext.DEPTH_BUFFER_BIT);

      perspective(25, (webGLContext.viewportWidth / webGLContext.viewportHeight), 
          0.1, 100.0);
      loadIdentity();

      mvTranslate([0, 0.0, -10.0])

      mvPushMatrix();
      mvRotate(rotateCube, [0, .5, .5]);

      webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexPositionBuffer);
      webGLContext.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 
          cubeVertexPositionBuffer.itemSize, webGLContext.FLOAT, false, 0, 0);

      webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexColorBuffer);
      webGLContext.vertexAttribPointer(shaderProgram.vertexColorAttribute, 
          cubeVertexColorBuffer.itemSize, webGLContext.FLOAT, false, 0, 0);

      webGLContext.bindBuffer(webGLContext.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
      setMatrixUniforms();
      webGLContext.drawElements(webGLContext.TRIANGLES, cubeVertexIndexBuffer.numItems, 
          webGLContext.UNSIGNED_SHORT, 0);

      mvPopMatrix();
      rotateCube += 2;

      }

      if (!canvasSupport() ) {
           alert("Unable to initialize Canvas");
           return;
        }

      if ( !webglSupport()) {
          alert("Unable to initialize WebGL");
          return;
        }

      var webGLContext;
      var rotateCube = 0;

      var theCanvas = document.getElementById("canvasOne");
      webGLContext =theCanvas.getContext("experimental-webgl");
      webGLContext.viewportWidth =theCanvas.width;
      webGLContext.viewportHeight = theCanvas.height;

      initShaders()
      initBuffers();

      webGLContext.clearColor(0.0, 0.0, 0.0, 1.0);
      webGLContext.clearDepth(1.0);
      webGLContext.enable(webGLContext.DEPTH_TEST);
      webGLContext.depthFunc(webGLContext.LEQUAL);

      setInterval(drawScreen, 33);

   function getShader(webglcontext, id) {
      var shaderScript = document.getElementById(id);
      if (!shaderScript) {
        return null;
      }

      var str = "";
      var scriptChild = shaderScript.firstChild;
      while (scriptChild) {
        if (scriptChild.nodeType == 3) {
         str += scriptChild.textContent;
        }
        scriptChild = scriptChild.nextSibling;
      }

      var shader;
      if (shaderScript.type == "x-shader/x-fragment") {
        shader = webGLContext.createShader(webGLContext.FRAGMENT_SHADER);
      } else if (shaderScript.type == "x-shader/x-vertex") {
        shader = webGLContext.createShader(webGLContext.VERTEX_SHADER);
      } else {
        return null;
      }

      webGLContext.shaderSource(shader, str);
      webGLContext.compileShader(shader);

      if (!webGLContext.getShaderParameter(shader, webGLContext.COMPILE_STATUS)) {
        alert(webGLContext.getShaderInfoLog(shader));
        return null;
      }

      return shader;
     }

     var shaderProgram;
   function initShaders() {
      var fragmentShader = getShader(webGLContext, "shader-fs");
      var vertexShader = getShader(webGLContext, "shader-vs");

      shaderProgram = webGLContext.createProgram();
      webGLContext.attachShader(shaderProgram, vertexShader);
      webGLContext.attachShader(shaderProgram, fragmentShader);
      webGLContext.linkProgram(shaderProgram);

      if (!webGLContext.getProgramParameter(shaderProgram, webGLContext.LINK_STATUS)) {
        alert("Could not initialize shaders");
      }

      webGLContext.useProgram(shaderProgram);

      shaderProgram.vertexPositionAttribute = webGLContext.getAttribLocation
          (shaderProgram, "aVertexPosition");
      webGLContext.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

      shaderProgram.vertexColorAttribute = webGLContext.getAttribLocation
          (shaderProgram, "aVertexColor");
      webGLContext.enableVertexAttribArray(shaderProgram.vertexColorAttribute);

      shaderProgram.pMatrixUniform = webGLContext.getUniformLocation 
          (shaderProgram, "uPMatrix");
      shaderProgram.mvMatrixUniform = webGLContext.getUniformLocation 
          (shaderProgram, "uMVMatrix");
     }

     var mvMatrix;
     var mvMatrixStack = [];

   function mvPushMatrix(matrix) {
      if (matrix) {
        mvMatrixStack.push(matrix.dup());
        mvMatrix = matrix.dup();
      } else {
        mvMatrixStack.push(mvMatrix.dup());
      }
     }

   function mvPopMatrix() {
      if (mvMatrixStack.length == 0) {
        throw "Invalid popMatrix!";
      }      mvMatrix = mvMatrixStack.pop();
      return mvMatrix;
     }

   function loadIdentity() {
      mvMatrix = Matrix.I(4);
     }

   function multMatrix(matrix) {
      mvMatrix = mvMatrix.x(matrix);
     }

   function mvTranslate(vector) {
      var matrix = Matrix.Translation($V([vector[0], vector[1], vector[2]])).ensure4x4();
      multMatrix(matrix);
     }

   function mvRotate(angle, vector) {
      var radians = angle * Math.PI / 180.0;
      var matrix = Matrix.Rotation(radians, $V([vector[0], 
          vector[1], vector[2]])).ensure4x4();
      multMatrix(matrix);
     }

     var pMatrix;
     function perspective(fovy, aspect, znear, zfar) {
      pMatrix = makePerspective(fovy, aspect, znear, zfar);
     }

   function setMatrixUniforms() {
      webGLContext.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, 
          new Float32Array(pMatrix.flatten()));
      webGLContext.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, 
          new Float32Array(mvMatrix.flatten()));
     }

     var cubeVertexPositionBuffer;
     var cubeVertexColorBuffer;
     var cubeVertexIndexBuffer;
   function initBuffers() {

      cubeVertexPositionBuffer = webGLContext.createBuffer();
      webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexPositionBuffer);
      vertices = [
        // Front face
        -1.0, -1.0,  1.0,
         1.0, -1.0,  1.0,
         1.0,  1.0,  1.0,
        -1.0,  1.0,  1.0,

        // Back face
        -1.0, -1.0, -1.0,
        -1.0,  1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0, -1.0, -1.0,

        // Top face
        -1.0,  1.0, -1.0,
        -1.0,  1.0,  1.0,
         1.0,  1.0,  1.0,
         1.0,  1.0, -1.0,

        // Bottom face
        -1.0, -1.0, -1.0,
         1.0, -1.0, -1.0,
         1.0, -1.0,  1.0,
        -1.0, -1.0,  1.0,

        // Right face
         1.0, -1.0, -1.0,
         1.0,  1.0, -1.0,
         1.0,  1.0,  1.0,
         1.0, -1.0,  1.0,

        // Left face
        -1.0, -1.0, -1.0,
        -1.0, -1.0,  1.0,
        -1.0,  1.0,  1.0,
        -1.0,  1.0, -1.0,
      ];
      webGLContext.bufferData(webGLContext.ARRAY_BUFFER, new Float32Array(vertices), 
          webGLContext.STATIC_DRAW);
      cubeVertexPositionBuffer.itemSize = 3;
      cubeVertexPositionBuffer.numItems = 24;

      cubeVertexColorBuffer = webGLContext.createBuffer();
      webGLContext.bindBuffer(webGLContext.ARRAY_BUFFER, cubeVertexColorBuffer);
      var colors = [
        [1.0, 1.0, 1.0, 1.0],     // Front face
        [0.9, 0.0, 0.0, 1.0],     // Back face
        [0.6, 0.6, 0.6, 1.0],     // Top face
        [0.6, 0.0, 0.0, 1.0],     // Bottom face
        [0.3 ,0.0, 0.0, 1.0],     // Right face
        [0.3, 0.3, 0.3, 1.0],     // Left face
      ];

      var unpackedColors = []
      for (var i in colors) {
        var color = colors[i];
        for (var j=0; j < 4; j++) {
         unpackedColors = unpackedColors.concat(color);
        }
      }
      webGLContext.bufferData(webGLContext.ARRAY_BUFFER, new Float32Array(unpackedColors), 
          webGLContext.STATIC_DRAW);
      cubeVertexColorBuffer.itemSize = 4;
      cubeVertexColorBuffer.numItems = 24;

      cubeVertexIndexBuffer = webGLContext.createBuffer();
      webGLContext.bindBuffer(webGLContext.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
      var cubeVertexIndices = [
        0, 1, 2,      0, 2, 3,    // Front face
        4, 5, 6,      4, 6, 7,    // Back face
        8, 9, 10,     8, 10, 11,  // Top face
        12, 13, 14,   12, 14, 15, // Bottom face
        16, 17, 18,   16, 18, 19, // Right face
        20, 21, 22,   20, 22, 23  // Left face
      ]
      webGLContext.bufferData(webGLContext.ELEMENT_ARRAY_BUFFER,  
          new Uint16Array(cubeVertexIndices), webGLContext.STATIC_DRAW);
      cubeVertexIndexBuffer.itemSize = 1;
      cubeVertexIndexBuffer.numItems = 36;

     }

}

</script>
</head>
<body>
<div style="position: absolute; top: 50px; left: 50px;">
<canvas id="canvasOne" width="500" height="500">
 Your browser does not support HTML5 Canvas or WebGLContext.
</canvas>
</div>
</body>
</html>

Further Explorations with WebGL

Obviously, we cannot teach you all about WebGL in this chapter. We opted to include this demo and short discussion to introduce you to WebGL and show you what it looks like. In reality, a full discussion of WebGL, even the basic concepts, could take up an entire volume.

If you are interested in WebGL, we strongly recommend you consult http://learningwebgl.com for more examples and the latest information about this exciting yet still experimental context for HTML5 Canvas.

WebGL JavaScript Libraries

At the start of this section, we promised to show you some libraries that can be used with WebGL to make it easier to develop applications. Here are some of the more interesting libraries and projects.

Google O3D

Google’s O3D library (http://code.google.com/p/o3d/) was once a browser plug-in but has now been released as a standalone JavaScript library for WebGL. The examples of using O3D with JavaScript—including a fairly spectacular 3D pool game—are very impressive. O3D allows you to load COLLADA 3D models created with Google SketchUp (as well as other 3D packages).

The required code looks about as complex as straight WebGL code, so while this is very powerful, you might want to look at some of the other libraries here first if you are just starting out.

GLGE

“WebGL for the lazy” is the tagline for this JavaScript library (http://www.glge.org/). The author of the library, Paul Brunt, says this about GLGE:

The aim of GLGE is to mask the involved nature of WebGL from the web developer, who can then spend his/her time creating richer content for the Web.

This is a high-level API that is still in development. Just like O3D, it has the ability to load COLLADA models. Applications written with GLGE are created with a combination of XML and JavaScript. It looks very promising.

C3DL

The tagline for C3DL (http://www.c3dl.org/) is “WebGL made easy!” C3DL, or “Canvas 3D JS Library,” is similar to GLGE, but it seems to have a head start thanks to a larger API and more support. This library also appears to be slanted toward games, as a real-time strategy (RTS) and an arcade game are featured as its more prominent demos. The library supports COLLADA models, and the code also appears very straightforward to implement.

SpiderGL

“3D Graphics for Next-Generation WWW” is how SpiderGL (http://spidergl.org/) bills itself to the world. This library appears to be very similar to GLGE and C3DL, except that the demos focus more on lighting, color, and textures than on games and applications. It also supports COLLADA models.

SceneJS

SceneJS (http://scenejs.org/) is geared toward rendering 3D scenes built as COLLADA JSON models in WebGL. You can also define and manipulate 3D scenes. Loading and rendering the models is a straightforward process, and the results are quite impressive.

CopperLicht

This commercial library (http://www.ambiera.com/copperlicht/) advertises itself as the “fast WebGL JavaScript 3D Engine.” All the demos are game-oriented, and the library supports many commercial 3D formats. It has both collision detection and physics built in. The demos are fast and are fun to play. This library appears to be centered around loading and using external 3D assets, so if that is what you are looking for, this might be your best choice.

The best content for your career. Discover unlimited learning on demand for around $1/day.