前提条件和预期结果
目前只有少数的浏览器支持 WebGL ,请看我的另外一篇文章:Can I use WebGL?.
下面的例子是在 Windows 下的 Chrome 16/23 以及 Android 下的 Firefox 17 进行测试。如果你使用的是非兼容浏览器访问则会弹出一个警告。

图1:包含 Hello world 文本的动画的 WebGL 立方体
在兼容 HTML5 的浏览器上,你将会看到如下图所示的带动画效果的立方体:

图2: 示例运行的屏幕截图
该代码基于 Lighting in WebGL - How to simulate lighting effects in your WebGL context - 非常感谢这篇教程。在该实例初始运行时,动画的立方体是通过一个静态的 Bitmap 图形对象渲染的。
下面的代码演示如何在程序中动态的渲染文本:
XML/HTML Code
复制内容到剪贴板
// TODO #1 New method to create a texture
function createCubeTexture(text) {
...
}
在这里使用 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 是非常重要的,用来确保写文本时不会前后颠倒。剩下的就很容易理解了:
XML/HTML Code
复制内容到剪贴板
// TODO #2 Assign the created texture for display
cubeTexture
=
createCubeTexture
("Hello World!");
源码
// File #1: webgl-demo.htm
XML/HTML Code
复制内容到剪贴板
<html>
<
head
>
<
title
>
WebGL - Hello World!
</
title
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=utf-8"
>
<
script
src
=
"sylvester.js"
type
=
"text/javascript"
>
</
script
>
<
script
src
=
"glUtils.js"
type
=
"text/javascript"
>
</
script
>
<
script
src
=
"webgl-demo.js"
type
=
"text/javascript"
>
</
script
>
<!-- Fragment shader program -->
<
script
id
=
"shader-fs"
type
=
"x-shader/x-fragment"
>
varying highp vec2 vTextureCoord;
varying highp vec3 vLighting;
uniform sampler2D uSampler;
void main(void) {
highp vec4
texelColor
=
texture2D
(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
gl_FragColor
=
vec4
(texelColor.rgb * vLighting, texelColor.a);
}
</
script
>
<!-- Vertex shader program -->
<
script
id
=
"shader-vs"
type
=
"x-shader/x-vertex"
>
attribute highp vec3 aVertexNormal;
attribute highp vec3 aVertexPosition;
attribute highp vec2 aTextureCoord;
uniform highp mat4 uNormalMatrix;
uniform highp mat4 uMVMatrix;
uniform highp mat4 uPMatrix;
varying highp vec2 vTextureCoord;
varying highp vec3 vLighting;
void main(void) {
gl_Position
=
uPMatrix
* uMVMatrix * vec4(aVertexPosition, 1.0);
vTextureCoord
=
aTextureCoord
;
// Apply lighting effect
highp vec3
ambientLight
=
vec3
(0.6, 0.6, 0.6);
highp vec3
directionalLightColor
=
vec3
(0.5, 0.5, 0.75);
highp vec3
directionalVector
=
vec3
(0.85, 0.8, 0.75);
highp vec4
transformedNormal
=
uNormalMatrix
* vec4(aVertexNormal, 1.0);
highp float
directional
=
max
(dot(transformedNormal.xyz, directionalVector), 0.0);
vLighting
=
ambientLight
(directionalLightColor * directional);
}
</
script
>
</
head
>
<
body
onload
=
"start()"
>
<
canvas
id
=
"glcanvas"
width
=
"640"
height
=
"480"
>
Your browser doesn't appear to support the HTML5
<
code
>
<
canvas
>
</
code
>
element.
</
canvas
>
</
body
>
</
html
>
// File #02: webgl-demo.js
XML/HTML Code
复制内容到剪贴板
var canvas;
var gl;
var cubeVerticesBuffer;
var cubeVerticesTextureCoordBuffer;
var cubeVerticesIndexBuffer;
var cubeVerticesIndexBuffer;
var
cubeRotation
=
0
.0;
var
lastCubeUpdateTime
=
0
;
var cubeImage;
var cubeTexture;
var mvMatrix;
var shaderProgram;
var vertexPositionAttribute;
var vertexNormalAttribute;
var textureCoordAttribute;
var perspectiveMatrix;
//
// start
//
// Called when the canvas is created to get the ball rolling.
//
function start() {
canvas
=
document
.getElementById("glcanvas");
initWebGL(canvas); // Initialize the GL context
// Only continue if WebGL is available and working
if (gl) {
gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque
gl.clearDepth(1.0); // Clear everything
gl.enable(gl.DEPTH_TEST); // Enable depth testing
gl.depthFunc(gl.LEQUAL); // Near things obscure far things
// Initialize the shaders; this is where all the lighting for the
// vertices and so forth is established.
initShaders();
// Here's where we call the routine that builds all the objects
// we'll be drawing.
initBuffers();
// Next, load and set up the textures we'll be using.
// TODO#2 Start
cubeTexture
=
createCubeTexture
("Hello World!");
// TODO#2 End
// Set up to draw the scene periodically.
setInterval(drawScene, 15);
}
}
//
// initWebGL
//
// Initialize WebGL, returning the GL context or null if
// WebGL isn't available or could not be initialized.
//
function initWebGL() {
gl
=
null
;
try {
gl
=
canvas
.getContext("experimental-webgl");
}
catch(e) {
}
// If we don't have a GL context, give up now
if (!gl) {
alert("Unable to initialize WebGL. Your browser may not support it.");
}
}
//
// initBuffers
//
// Initialize the buffers we'll need. For this demo, we just have
// one object -- a simple two-dimensional cube.
//
function initBuffers() {
// Create a buffer for the cube's vertices.
cubeVerticesBuffer
=
gl
.createBuffer();
// Select the cubeVerticesBuffer as the one to apply vertex
// operations to from here out.
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesBuffer);
// Now create an array of vertices for the cube.
var
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
];
// Now pass the list of vertices into WebGL to build the shape. We
// do this by creating a Float32Array from the JavaScript array,
// then use it to fill the current vertex buffer.
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
// Set up the normals for the vertices, so that we can compute lighting.
cubeVerticesNormalBuffer
=
gl
.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer);
var
vertexNormals
= [
// Front
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
// Back
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
// Top
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
// Bottom
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
// Right
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
// Left
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexNormals),
gl.STATIC_DRAW);
// Map the texture onto the cube's faces.
cubeVerticesTextureCoordBuffer
=
gl
.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer);
var
textureCoordinates
= [
// Front
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Back
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Top
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Bottom
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Right
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Left
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates),
gl.STATIC_DRAW);
// Build the element array buffer; this specifies the indices
// into the vertex array for each face's vertices.
cubeVerticesIndexBuffer
=
gl
.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVerticesIndexBuffer);
// This array defines each face as two triangles, using the
// indices into the vertex array to specify each triangle's
// position.
var
cubeVertexIndices
= [
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // back
8, 9, 10, 8, 10, 11, // top
12, 13, 14, 12, 14, 15, // bottom
16, 17, 18, 16, 18, 19, // right
20, 21, 22, 20, 22, 23 // left
]
// Now send the element array to GL
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,
new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
}
//
// initTextures
//
// Initialize the textures we'll be using, then initiate a load of
// the texture images. The handleTextureLoaded() callback will finish
// the job; it gets called each time a texture finishes loading.
//
// TODO#1 Start
function createCubeTexture(text) {
// create a hidden canvas to draw the texture
var
canvas
=
document
.createElement('canvas');
canvas.id
=
"hiddenCanvas"
;
canvas.width
=
512
;
canvas.height
=
512
;
canvas.style.display
=
"none"
;
var
body
=
document
.getElementsByTagName("body")[0];
body.appendChild(canvas);
// draw texture
var
cubeImage
=
document
.getElementById('hiddenCanvas');
var
ctx
=
cubeImage
.getContext('2d');
ctx.beginPath();
ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle
=
'white'
;
ctx.fill();
ctx.fillStyle
=
'black'
;
ctx.font
=
"65px Arial"
;
ctx.textAlign
=
'center'
;
ctx.fillText(text, ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.restore();
// create new texture
var
texture
=
gl
.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
handleTextureLoaded(cubeImage, texture)
return texture;
}
// TODO#1 End
function handleTextureLoaded(image, texture) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
gl.bindTexture(gl.TEXTURE_2D, null);
}
//
// drawScene
//
// Draw the scene.
//
function drawScene() {
// Clear the canvas before we start drawing on it.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Establish the perspective with which we want to view the
// scene. Our field of view is 45 degrees, with a width/height
// ratio of 640:480, and we only want to see objects between 0.1 units
// and 100 units away from the camera.
perspectiveMatrix
=
makePerspective
(45, 640.0/480.0, 0.1, 100.0);
// Set the drawing position to the "identity" point, which is
// the center of the scene.
loadIdentity();
// Now move the drawing position a bit to where we want to start
// drawing the cube.
mvTranslate([0.0, 0.0, -6.0]);
// Save the current matrix, then rotate before we draw.
mvPushMatrix();
mvRotate(cubeRotation, [1, 0, 1]);
// Draw the cube by binding the array buffer to the cube's vertices
// array, setting attributes, and pushing it to GL.
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesBuffer);
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
// Set the texture coordinates attribute for the vertices.
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer);
gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 0, 0);
// Bind the normals buffer to the shader attribute.
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesNormalBuffer);
gl.vertexAttribPointer(vertexNormalAttribute, 3, gl.FLOAT, false, 0, 0);
// Specify the texture to map onto the faces.
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, cubeTexture);
gl.uniform1i(gl.getUniformLocation(shaderProgram, "uSampler"), 0);
// Draw the cube.
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVerticesIndexBuffer);
setMatrixUniforms();
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);
// Restore the original matrix
mvPopMatrix();
// Update the rotation for the next draw, if it's time to do so.
var
currentTime
= (new Date).getTime();
if (lastCubeUpdateTime) {
var
delta
=
currentTime
- lastCubeUpdateTime;
cubeRotation = (30 * delta) / 1000.0;
}
lastCubeUpdateTime
=
currentTime
;
}
//
// initShaders
//
// Initialize the shaders, so WebGL knows how to light our scene.
//
function initShaders() {
var
fragmentShader
=
getShader
(gl, "shader-fs");
var
vertexShader
=
getShader
(gl, "shader-vs");
// Create the shader program
shaderProgram
=
gl
.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// If creating the shader program failed, alert
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Unable to initialize the shader program.");
}
gl.useProgram(shaderProgram);
vertexPositionAttribute
=
gl
.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute);
textureCoordAttribute
=
gl
.getAttribLocation(shaderProgram, "aTextureCoord");
gl.enableVertexAttribArray(textureCoordAttribute);
vertexNormalAttribute
=
gl
.getAttribLocation(shaderProgram, "aVertexNormal");
gl.enableVertexAttribArray(vertexNormalAttribute);
}
//
// getShader
//
// Loads a shader program by scouring the current document,
// looking for a script with the specified ID.
//
function getShader(gl, id) {
var
shaderScript
=
document
.getElementById(id);
// Didn't find an element with the specified ID; abort.
if (!shaderScript) {
return null;
}
// Walk through the source element's children, building the
// shader source string.
var
theSource
=
""
;
var
currentChild
=
shaderScript
.firstChild;
while(currentChild) {
if (
currentChild.nodeType
== 3) {
theSource = currentChild.textContent;
}
currentChild
currentChild
= currentChild.nextSibling;
}
// Now figure out what type of shader script we have,
// based on its MIME type.
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 { <