Skip to content

Instantly share code, notes, and snippets.

@40thieves
Created December 3, 2013 23:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 40thieves/7779934 to your computer and use it in GitHub Desktop.
Save 40thieves/7779934 to your computer and use it in GitHub Desktop.
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Animated WebGL Scene with Key and Mouse Input.</title>
<meta charset="utf-8">
<script src="lib/webgl-debug.js"></script>
<script type="text/javascript" src="lib/glMatrix.js"></script>
<script src="lib/webgl-utils.js"></script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoordinates;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying vec2 vTextureCoordinates;
void main() {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vTextureCoordinates = aTextureCoordinates;
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
varying vec2 vTextureCoordinates;
uniform sampler2D uSampler;
void main() {
gl_FragColor = texture2D(uSampler, vTextureCoordinates);
}
</script>
<script type="text/javascript">
// globals
var gl
, pwgl = {}
, canvas
// variables for translations and rotations
, transY = 0, transZ = 0
, xRot = yRot = zRot = xOffs = yOffs = drag = 0
;
pwgl.ongoingImageLoads = [];
pwgl.listOfPressedKeys = []; // Keep track of pressed down keys in a list
function createGLContext(canvas) {
var names = ['webgl', 'experimental-webgl']
, context = null
;
for (var i = 0; i < names.length; i++) {
try {
context = canvas.getContext(names[i]);
}
catch(e) {}
if (context) {
break;
}
}
if (context) {
context.viewportWidth = canvas.width;
context.viewportHeight = canvas.height;
}
else {
alert('Failed to create WebGL context!');
}
return context;
}
function loadShaderFromDOM(id) {
var shaderScript = document.getElementById(id);
// If the element with the specified id is not found, exit
if ( ! shaderScript) {
return null;
}
// Loop through the children for the found DOM element and
// build up the shader source code as a string
var shaderSource = ""
, currentChild = shaderScript.firstChild
;
while (currentChild) {
if (currentChild.nodeType == 3) { // 3 corresponds to TEXT_NODE
shaderSource += currentChild.textContent;
}
currentChild = currentChild.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, shaderSource);
gl.compileShader(shader);
if ( ! gl.getShaderParameter(shader, gl.COMPILE_STATUS) && ! gl.isContextLost()) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
function setupShaders() {
var vertexShader = loadShaderFromDOM('shader-vs')
, fragmentShader = loadShaderFromDOM('shader-fs')
, shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if ( ! gl.getProgramParameter(shaderProgram, gl.LINK_STATUS) && ! gl.isContextLost()) {
alert('Failed to link shaders: ' + gl.getProgramInfoLog(shaderProgram));
}
gl.useProgram(shaderProgram);
pwgl.vertexPositionAttributeLoc = gl.getAttribLocation(shaderProgram, 'aVertexPosition');
pwgl.vertexTextureAttributeLoc = gl.getAttribLocation(shaderProgram, 'aTextureCoordinates');
pwgl.uniformMVMatrixLoc = gl.getUniformLocation(shaderProgram, 'uMVMatrix');
pwgl.uniformProjMatrixLoc = gl.getUniformLocation(shaderProgram, 'uPMatrix');
pwgl.uniformSamplerLoc = gl.getUniformLocation(shaderProgram, 'uSampler');
gl.enableVertexAttribArray(pwgl.vertexPositionAttributeLoc);
gl.enableVertexAttribArray(pwgl.vertexTextureAttributeLoc);
pwgl.modelViewMatrix = mat4.create();
pwgl.projectionMatrix = mat4.create();
pwgl.modelViewMatrixStack = [];
}
function pushModelViewMatrix() {
var copyToPush = mat4.create(pwgl.modelViewMatrix);
pwgl.modelViewMatrixStack.push(copyToPush);
}
function popModelViewMatrix() {
if (pwgl.modelViewMatrixStack.length == 0) {
throw 'Error popModelViewMatrix() - Stack was empty';
}
pwgl.modelViewMatrix = pwgl.modelViewMatrixStack.pop();
}
function setupFloorBuffers() {
pwgl.floorVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, pwgl.floorVertexPositionBuffer);
var floorVertexPosition = [
// Plane in y=0
5.0, 0.0, 5.0, //v0
5.0, 0.0, -5.0, //v1
-5.0, 0.0, -5.0, //v2
-5.0, 0.0, 5.0 //v3
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(floorVertexPosition), gl.STATIC_DRAW);
pwgl.FLOOR_VERTEX_POS_BUF_ITEM_SIZE = 3;
pwgl.FLOOR_VERTEX_POS_BUF_NUM_ITEMS = 4;
pwgl.floorVertexTextureCoordinateBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, pwgl.floorVertexTextureCoordinateBuffer);
var floorVertexTextureCoordinates = [
2.0, 0.0,
2.0, 2.0,
0.0, 2.0,
0.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(floorVertexTextureCoordinates), gl.STATIC_DRAW);
pwgl.FLOOR_VERTEX_TEX_COORD_BUF_ITEM_SIZE = 2;
pwgl.FLOOR_VERTEX_TEX_COORD_BUF_NUM_ITEMS = 4;
pwgl.floorVertexIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, pwgl.floorVertexIndexBuffer);
var floorVertexIndices = [0, 1, 2, 3];
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(floorVertexIndices), gl.STATIC_DRAW);
pwgl.FLOOR_VERTEX_INDEX_BUF_ITEM_SIZE = 1;
pwgl.FLOOR_VERTEX_INDEX_BUF_NUM_ITEMS = 4;
}
function setupCubeBuffers() {
pwgl.cubeVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, pwgl.cubeVertexPositionBuffer);
var cubeVertexPosition = [
// Front face
1.0, 1.0, 1.0, //v0
-1.0, 1.0, 1.0, //v1
-1.0, -1.0, 1.0, //v2
1.0, -1.0, 1.0, //v3
// Back face
1.0, 1.0, -1.0, //v4
-1.0, 1.0, -1.0, //v5
-1.0, -1.0, -1.0, //v6
1.0, -1.0, -1.0, //v7
// Left face
-1.0, 1.0, 1.0, //v8
-1.0, 1.0, -1.0, //v9
-1.0, -1.0, -1.0, //v10
-1.0, -1.0, 1.0, //v11
// Right face
1.0, 1.0, 1.0, //12
1.0, -1.0, 1.0, //13
1.0, -1.0, -1.0, //14
1.0, 1.0, -1.0, //15
// Top face
1.0, 1.0, 1.0, //v16
1.0, 1.0, -1.0, //v17
-1.0, 1.0, -1.0, //v18
-1.0, 1.0, 1.0, //v19
// Bottom face
1.0, -1.0, 1.0, //v20
1.0, -1.0, -1.0, //v21
-1.0, -1.0, -1.0, //v22
-1.0, -1.0, 1.0, //v23
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeVertexPosition), gl.STATIC_DRAW);
pwgl.CUBE_VERTEX_POS_BUF_ITEM_SIZE = 3;
pwgl.CUBE_VERTEX_POS_BUF_NUM_ITEMS = 24;
// Setup buffer with texture coordinates
pwgl.cubeVertexTextureCoordinateBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, pwgl.cubeVertexTextureCoordinateBuffer);
var textureCoordinates = [
//Front face
0.0, 0.0, //v0
1.0, 0.0, //v1
1.0, 1.0, //v2
0.0, 1.0, //v3
// Back face
0.0, 1.0, //v4
1.0, 1.0, //v5
1.0, 0.0, //v6
0.0, 0.0, //v7
// Left face
0.0, 1.0, //v8
1.0, 1.0, //v9
1.0, 0.0, //v10
0.0, 0.0, //v11
// Right face
0.0, 1.0, //v12
1.0, 1.0, //v13
1.0, 0.0, //v14
0.0, 0.0, //v15
// Top face
0.0, 1.0, //v16
1.0, 1.0, //v17
1.0, 0.0, //v18
0.0, 0.0, //v19
// Bottom face
0.0, 1.0, //v20
1.0, 1.0, //v21
1.0, 0.0, //v22
0.0, 0.0, //v23
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW);
pwgl.CUBE_VERTEX_TEX_COORD_BUF_ITEM_SIZE = 2;
pwgl.CUBE_VERTEX_TEX_COORD_BUF_NUM_ITEMS = 24;
pwgl.cubeVertexIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, pwgl.cubeVertexIndexBuffer);
var cubeVertexIndices = [
0, 1, 2, 0, 2, 3, // Front face
4, 6, 5, 4, 7, 6, // Back face
8, 9, 10, 8, 10, 11, // Left face
12, 13, 14, 12, 14, 15, // Right face
16, 17, 18, 16, 18, 19, // Top face
20, 22, 21, 20, 23, 22 // Bottom face
];
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
pwgl.CUBE_VERTEX_INDEX_BUF_ITEM_SIZE = 1;
pwgl.CUBE_VERTEX_INDEX_BUF_NUM_ITEMS = 36;
}
function textureFinishedLoading(image, texture) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT);
gl.bindTexture(gl.TEXTURE_2D, null);
}
function loadImageForTexture(url, texture) {
var image = new Image();
image.onload = function() {
pwgl.ongoingImageLoads.splice(pwgl.ongoingImageLoads.indexOf(image), 1);
textureFinishedLoading(image, texture);
}
pwgl.ongoingImageLoads.push(image);
image.src = url;
}
function setupTextures() {
// Texture for the table
pwgl.woodTexture = gl.createTexture();
loadImageForTexture('assets/wood_128x128.jpg', pwgl.woodTexture);
// Texture for the floor
pwgl.groundTexture = gl.createTexture();
loadImageForTexture('assets/wood_floor_256.jpg',pwgl.groundTexture);
// Texture for the box on the table
pwgl.boxTexture = gl.createTexture();
loadImageForTexture('assets/wicker_256.jpg', pwgl.boxTexture);
}
function setupBuffers() {
setupFloorBuffers();
setupCubeBuffers();
}
function uploadModelViewMatrixToShader() {
gl.uniformMatrix4fv(pwgl.uniformMVMatrixLoc, false, pwgl.modelViewMatrix);
}
function uploadProjectionMatrixToShader() {
gl.uniformMatrix4fv(pwgl.uniformProjMatrixLoc, false, pwgl.projectionMatrix);
}
function drawFloor() {
// Bind position buffer
gl.bindBuffer(gl.ARRAY_BUFFER, pwgl.floorVertexPositionBuffer);
gl.vertexAttribPointer(pwgl.vertexPositionAttributeLoc, pwgl.FLOOR_VERTEX_POS_BUF_ITEM_SIZE, gl.FLOAT, false, 0, 0);
// Bind texture coordinate buffer
gl.bindBuffer(gl.ARRAY_BUFFER, pwgl.floorVertexTextureCoordinateBuffer);
gl.vertexAttribPointer(pwgl.vertexTextureAttributeLoc, pwgl.FLOOR_VERTEX_TEX_COORD_BUF_ITEM_SIZE, gl.FLOAT, false, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, pwgl.groundTexture);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, pwgl.floorVertexIndexBuffer);
gl.drawElements(gl.TRIANGLE_FAN, pwgl.FLOOR_VERTEX_INDEX_BUF_NUM_ITEMS, gl.UNSIGNED_SHORT, 0);
}
function drawCube(texture) {
// Bind position buffer
gl.bindBuffer(gl.ARRAY_BUFFER, pwgl.cubeVertexPositionBuffer);
gl.vertexAttribPointer(pwgl.vertexPositionAttributeLoc, pwgl.CUBE_VERTEX_POS_BUF_ITEM_SIZE, gl.FLOAT, false, 0, 0);
// bind texture coordinate buffer
gl.bindBuffer(gl.ARRAY_BUFFER, pwgl.cubeVertexTextureCoordinateBuffer);
gl.vertexAttribPointer(pwgl.vertexTextureAttributeLoc, pwgl.CUBE_VERTEX_TEX_COORD_BUF_ITEM_SIZE,gl.FLOAT,false, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
// Bind index buffer and draw cube
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, pwgl.cubeVertexIndexBuffer);
gl.drawElements(gl.TRIANGLES, pwgl.CUBE_VERTEX_INDEX_BUF_NUM_ITEMS, gl.UNSIGNED_SHORT, 0);
}
function drawTable() {
// setup transformations for table top
pushModelViewMatrix();
mat4.translate(pwgl.modelViewMatrix, [0.0, 1.0, 0.0], pwgl.modelViewMatrix);
mat4.scale(pwgl.modelViewMatrix, [2.0, 0.1, 2.0], pwgl.modelViewMatrix);
uploadModelViewMatrixToShader();
// Draw the table top with woodTexture
drawCube(pwgl.woodTexture);
popModelViewMatrix();
// Draw the table legs
for (var i =- 1; i <= 1; i += 2) {
for (var j = -1; j <= 1; j += 2) {
pushModelViewMatrix();
mat4.translate(pwgl.modelViewMatrix, [i * 1.9, -0.1, j * 1.9], pwgl.modelViewMatrix);
mat4.scale(pwgl.modelViewMatrix, [0.1, 1.0, 0.1], pwgl.modelViewMatrix);
uploadModelViewMatrixToShader();
drawCube(pwgl.woodTexture);
popModelViewMatrix();
}
}
}
function init() {
// Initialization that is performed during first startup and when the
// event webglcontextrestored is received is included in this function.
setupShaders();
setupBuffers();
setupTextures();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
// Initialize some varibles for the moving box
pwgl.x = 0.0;
pwgl.y = 2.7;
pwgl.z = 0.0;
pwgl.circleRadius = 4.0;
pwgl.angle = 0;
// Initialize some variables related to the animation
pwgl.animationStartTime = undefined;
pwgl.nbrOfFramesForFPS = 0;
pwgl.previousFrameTimeStamp = Date.now();
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(60, gl.viewportWidth / gl.viewportHeight, 1, 100.0, pwgl.projectionMatrix);
mat4.identity(pwgl.modelViewMatrix);
mat4.lookAt([8, 12, 8], [0, 0, 0], [0, 1, 0], pwgl.modelViewMatrix);
}
function draw() {
pwgl.requestId = requestAnimFrame(draw);
var currentTime = Date.now();
handlePressedDownKeys();
if (currentTime - pwgl.previousFrameTimeStamp >= 1000) {
pwgl.fpsCounter.innerHTML = pwgl.nbrOfFramesForFPS;
pwgl.nbrOfFramesForFPS = 0;
pwgl.previousFrameTimeStamp = currentTime;
}
mat4.translate(pwgl.modelViewMatrix, [0.0, transY, transZ], pwgl.modelViewMatrix);
mat4.rotateX(pwgl.modelViewMatrix, xRot / 50, pwgl.modelViewMatrix);
mat4.rotateY(pwgl.modelViewMatrix, yRot / 50, pwgl.modelViewMatrix);
yRot = xRot = zRot = transY = transZ = 0;
uploadModelViewMatrixToShader();
uploadProjectionMatrixToShader();
gl.uniform1i(pwgl.uniformSamplerLoc, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Draw floor
drawFloor();
// Draw table
pushModelViewMatrix();
mat4.translate(pwgl.modelViewMatrix, [0.0, 1.1, 0.0], pwgl.modelViewMatrix);
uploadModelViewMatrixToShader();
drawTable();
popModelViewMatrix();
// Draw box
pushModelViewMatrix();
// if (currentTime === undefined) {
// currentTime = Date.now();
// }
if (pwgl.animationStartTime === undefined) {
pwgl.animationStartTime = currentTime;
}
if (pwgl.y < 5) {
// console.log('before', pwgl.y);
// console.log(pwgl.animationStartTime);
// console.log(currentTime);
pwgl.y = 2.7 + (currentTime - pwgl.animationStartTime) / 3000 * (5.0 - 2.7);
// console.log('after', pwgl.y);
}
else {
pwgl.angle = (currentTime - pwgl.animationStartTime) / 2000 * 2 * Math.PI % (2 * Math.PI);
pwgl.x = Math.cos(pwgl.angle) * pwgl.circleRadius;
pwgl.z = Math.sin(pwgl.angle) * pwgl.circleRadius;
}
mat4.translate(pwgl.modelViewMatrix, [pwgl.x, pwgl.y, pwgl.z], pwgl.modelViewMatrix);
mat4.scale(pwgl.modelViewMatrix, [0.5, 0.5, 0.5], pwgl.modelViewMatrix);
uploadModelViewMatrixToShader();
drawCube(pwgl.boxTexture);
popModelViewMatrix();
// Update number of drawn frames to be able to count fps
pwgl.nbrOfFramesForFPS++;
}
function handleKeyDown(e) {
pwgl.listOfPressedKeys[e.keyCode] = true;
}
function handleKeyUp(e) {
pwgl.listOfPressedKeys[e.keyCode] = false;
}
function handlePressedDownKeys() {
if (pwgl.listOfPressedKeys[38]) {
// Arrow up, increase radius of circle
pwgl.circleRadius += 0.1;
}
if (pwgl.listOfPressedKeys[40]) {
// Arrow down, decrease radius of circle
pwgl.circleRadius -= 0.1;
if (pwgl.circleRadius < 0) {
pwgl.circleRadius = 0;
}
}
}
function mymousedown(e) {
drag = 1;
xOffs = e.clientX;
yOffs = e.clientY;
}
function mymouseup(e) {
drag = 0;
}
function mymousemove(e) {
if (drag == 0)
return;
if (e.shiftKey) {
transZ = (e.clientY - yOffs)/10;
//zRot = (xOffs - e.clientX)*.3;
}
else if (e.altKey) {
transY = -(e.clientY - yOffs)/10;
}
else {
yRot = - xOffs + e.clientX;
xRot = - yOffs + e.clientY;
}
xOffs = e.clientX;
yOffs = e.clientY;
//console.log("xOff= "+xOffs+" yOff="+yOffs);
}
function wheelHandler(e) {
e.preventDefault();
if (e.delta) {
if (e.altKey)
transY = -e.detail / 10;
else
transZ = e.detail / 10;
}
else if (e.wheelDelta) {
if (e.altKey)
transY = -e.wheelDelta / 120;
else
transZ = e.wheelDelta / 120;
}
}
function startup() {
canvas = document.getElementById('myGLCanvas');
canvas = WebGLDebugUtils.makeLostContextSimulatingCanvas(canvas);
canvas.addEventListener('webglcontextlost', handleContextLost, false);
canvas.addEventListener('webglcontextrestored', handleContextRestored, false);
document.addEventListener('keydown', handleKeyDown, false);
document.addEventListener('keyup', handleKeyUp, false);
canvas.addEventListener('mousemove', mymousemove, false);
canvas.addEventListener('mousedown', mymousedown, false);
canvas.addEventListener('mouseup', mymouseup, false);
canvas.addEventListener('mousewheel', wheelHandler, false);
canvas.addEventListener('DOMMouseScroll', wheelHandler, false);
gl = createGLContext(canvas);
init();
pwgl.fpsCounter = document.getElementById('fps');
// Draw the complete scene
draw();
}
function handleContextLost(e) {
e.preventDefault();
cancelRequestAnimFrame(pwgl.requestId);
// Ignore all ongoing image loads by removing their onload handler
for (var i = 0; i < pwgl.ongoingImageLoads.length; i++) {
pwgl.ongoingImageLoads[i].onload = undefined;
}
pwgl.ongoingImageLoads = [];
}
function handleContextRestored(e) {
init();
pwgl.requestId = requestAnimFrame(draw, canvas);
}
</script>
</head>
<body onload="startup();">
<canvas id="myGLCanvas" width="500" height="500"></canvas>
<div id="fps-counter">FPS: <span id="fps">--</span></div>
</body>
</html>
@40thieves
Copy link
Author

Note: I put my lib scripts in lib dir to clean it up a bit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment