Hello World WebGL示例

原文:http://blog.qunee.com/?p=4429

原文:http://www.sw-engineering-candies.com/snippets/webgl/hello-world

这不是一个平常看到的Hello World示例,因为他不只是简单的打印”Hello World”,这个WebGL示例包含了很多引用代码,但这对于WebGL来说是很典型的,写一个最简单的WebGL应用无法体现WebGL的价值

你会注意到很多代码需要本地WebGL,如果你使用three.js这样的类库,引用代码会减少到最少,在这些文章中你会看到three.js示例:Experimental Visualization of Artificial Neural Network with WebGL or Extremely Fast and Simple WebGL Motion Detector to Rotate 3D Graphic

前提条件&预期结果

目前支持WebGL得浏览器还很少(参见:Can I use WebGL? ),下面的例子在Chrome 16 – 23 for win32以及firefox for android 17下做了测试,如果你的浏览器不支持webGL,你会看到下面的警告

Your browser doesn’t appear to support the HTML5 <canvas>element.

运行效果:

WebGL - hello world

Figure 1: 动态WebGL立方体,”Hello World!” 纹理

 

本例的代码基于 Lighting in WebGL - How to simulate lighting effects in your WebGL context - 一个非常好的教程。示例初始是一个动态的立方体,使用静态位图渲染

下面的代码的要点是:如何在程序中动态的设置立方体的纹理

// TODO #1 新建方法创建纹理

function createCubeTexture(text) {
...
}

使用gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);非常重要,这可以避免文字颠倒,剩下的就简单了

// TODO #2 生成一个纹理对象

cubeTexture = createCubeTexture("Hello World!");

Source Code

// File #1: webgl-demo.htm

<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>&lt;canvas&gt;</code> element.
</canvas>
</body>
</html>

// File #02: webgl-demo.js

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.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 {
return null; // Unknown shader type
}

// Send the source to the shader object

gl.shaderSource(shader, theSource);

// Compile the shader program

gl.compileShader(shader);

// See if it compiled successfully

if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}

return shader;
}

//
// Matrix utility functions
//

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

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

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

function setMatrixUniforms() {
var pUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
gl.uniformMatrix4fv(pUniform, false, new Float32Array(perspectiveMatrix.flatten()));

var mvUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
gl.uniformMatrix4fv(mvUniform, false, new Float32Array(mvMatrix.flatten()));

var normalMatrix = mvMatrix.inverse();
normalMatrix = normalMatrix.transpose();
var nUniform = gl.getUniformLocation(shaderProgram, "uNormalMatrix");
gl.uniformMatrix4fv(nUniform, false, new Float32Array(normalMatrix.flatten()));
}

var mvMatrixStack = [];

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

function mvPopMatrix() {
if (!mvMatrixStack.length) {
throw("Can't pop from an empty matrix stack.");
}

mvMatrix = mvMatrixStack.pop();
return mvMatrix;
}

function mvRotate(angle, v) {
var inRadians = angle * Math.PI / 180.0;

var m = Matrix.Rotation(inRadians, $V([v[0], v[1], v[2]])).ensure4x4();
multMatrix(m);
}

如果你有任何改进的建议或者发现了代码中的bug,请不要犹豫,马上联系我

源代码GitHub


7 − 二 =