WebGL Box photo slideshow. Today we continue HTML5 canvas examples. I use WebGL technology in order to map a sequence of images on a rotating cube. Cube is not just spinning all the time, but also makes a little pause between images.
Here are our demo and downloadable package:
Live Demo
[sociallocker]
download in package
[/sociallocker]
Ok, download the example files and lets start coding !
Step 1. HTML
Here are html sources of our demo. As you can see – just empty page.
index.html
<!DOCTYPE html> <html lang="en" > <head> <meta charset="utf-8" /> <title>WebGL Box photo slideshow | Script Tutorials</title> <link href="css/main.css" rel="stylesheet" type="text/css" /> <script src="js/glMatrix-0.9.5.min.js"></script> <script src="js/webgl-utils.js"></script> <script src="js/script.js"></script> </head> <body onload="initWebGl()"> <div class="container"> <canvas id="panel" width="800" height="600"></canvas> </div> <footer> <h2>WebGL Box photo slideshow</h2> <a href="https://www.script-tutorials.com/webgl-box-photo-slideshow/" class="stuts">Back to original tutorial on <span>Script Tutorials</span></a> </footer> </body> </html>
Step 2. CSS
Here are used CSS styles.
css/main.css
*{ margin:0; padding:0; } body { background-repeat:no-repeat; background-color:#bababa; background-image: -webkit-radial-gradient(600px 200px, circle, #eee, #bababa 40%); background-image: -moz-radial-gradient(600px 200px, circle, #eee, #bababa 40%); background-image: -o-radial-gradient(600px 200px, circle, #eee, #bababa 40%); background-image: radial-gradient(600px 200px, circle, #eee, #bababa 40%); color:#fff; font:14px/1.3 Arial,sans-serif; min-height:600px; } footer { background-color:#212121; bottom:0; box-shadow: 0 -1px 2px #111111; display:block; height:70px; left:0; position:fixed; width:100%; z-index:100; } footer h2{ font-size:22px; font-weight:normal; left:50%; margin-left:-400px; padding:22px 0; position:absolute; width:540px; } footer a.stuts,a.stuts:visited{ border:none; text-decoration:none; color:#fcfcfc; font-size:14px; left:50%; line-height:31px; margin:23px 0 0 110px; position:absolute; top:0; } footer .stuts span { font-size:22px; font-weight:bold; margin-left:5px; } .container { border:3px #111 solid; margin:20px auto; padding:20px; position:relative; width:800px; border-radius:15px; -moz-border-radius:15px; -webkit-border-radius:15px; }
Step 3. JS
js/webgl-utils.js and js/glMatrix-0.9.5.min.js
These files we will use in project for working with WebGL. Both files will in our package.
js/script.js
var gl; // global WebGL object var shaderProgram; var pics_names=[ 'images/0.png', 'images/1.jpg', 'images/2.jpg', 'images/3.jpg', 'images/4.jpg', 'images/5.jpg', 'images/6.jpg', 'images/7.jpg', 'images/8.jpg', 'images/9.jpg' ]; var pics_num=pics_names.length; // diffirent initializations function initGL(canvas) { try { gl = canvas.getContext('experimental-webgl'); gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; } catch (e) {} if (! gl) { alert('Can`t initialise WebGL, not supported'); } } function getShader(gl, type) { var str = ''; var shader; if (type == 'x-fragment') { str = "#ifdef GL_ES\n"+ "precision highp float;\n"+ "#endif\n"+ "varying vec2 vTextureCoord;\n"+ "uniform sampler2D uSampler;\n"+ "void main(void) {\n"+ " gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));\n"+ "}\n"; shader = gl.createShader(gl.FRAGMENT_SHADER); } else if (type == 'x-vertex') { str = "attribute vec3 aVertexPosition;\n"+ "attribute vec2 aTextureCoord;\n"+ "uniform mat4 uMVMatrix;\n"+ "uniform mat4 uPMatrix;\n"+ "varying vec2 vTextureCoord;\n"+ "void main(void) {\n"+ " gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);\n"+ " vTextureCoord = aTextureCoord;\n"+ "}\n"; 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; } function initShaders() { var fragmentShader = getShader(gl, 'x-fragment'); var vertexShader = getShader(gl, 'x-vertex'); shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { alert('Can`t initialise shaders'); } gl.useProgram(shaderProgram); shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, 'aVertexPosition'); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, 'aTextureCoord'); gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute); shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, 'uPMatrix'); shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, 'uMVMatrix'); shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, 'uSampler'); } var objVertexPositionBuffer=new Array(); var objVertexTextureCoordBuffer=new Array(); var objVertexIndexBuffer=new Array(); function initObjBuffers() { for (var i=0;i<4;i=i+1) { objVertexPositionBuffer[i] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, objVertexPositionBuffer[i]); vertices = [ Math.cos(i*((2*Math.PI)/4)), -0.5, Math.sin(i*((2*Math.PI)/4)), Math.cos(i*((2*Math.PI)/4)), 0.5, Math.sin(i*((2*Math.PI)/4)), Math.cos((i+1)*((2*Math.PI)/4)), 0.5, Math.sin((i+1)*((2*Math.PI)/4)), Math.cos((i+1)*((2*Math.PI)/4)), -0.5, Math.sin((i+1)*((2*Math.PI)/4)), ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); objVertexPositionBuffer[i].itemSize = 3; objVertexPositionBuffer[i].numItems = 4; objVertexTextureCoordBuffer[i] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, objVertexTextureCoordBuffer[i] ); var textureCoords = [ 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW); objVertexTextureCoordBuffer[i].itemSize = 2; objVertexTextureCoordBuffer[i].numItems = 4; objVertexIndexBuffer[i] = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, objVertexIndexBuffer[i]); var objVertexIndices = [ 0, 1, 2, 0, 2, 3, ]; gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(objVertexIndices), gl.STATIC_DRAW); objVertexIndexBuffer[i].itemSize = 1; objVertexIndexBuffer[i].numItems = 6; } } function handleLoadedTexture(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, texture.image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.bindTexture(gl.TEXTURE_2D, null); } var crateTextures = Array(); var usedTextures = Array(); function initTexture(image) { var texture = gl.createTexture(); // allocate texture texture.image = new Image(); texture.image.onload = function () { handleLoadedTexture(texture); } texture.image.src = image; return texture; } function initTextures() { for (var i=0; i < pics_num; i++) { crateTextures[i]=initTexture(pics_names[i]); } usedTextures = crateTextures.slice(0, 2); } var mvMatrix = mat4.create(); var mvMatrixStack = []; var pMatrix = mat4.create(); function setMatrixUniforms() { gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix); gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix); } function degToRad(degrees) { return degrees * Math.PI / 180; } var yRot = -45; var ySpeed = 40; var z = -2.5; var iPause = 1000; var bPause = false; var RotationMatrix = mat4.create(); mat4.identity(RotationMatrix); // Draw scene and initialization var MoveMatrix = mat4.create(); mat4.identity(MoveMatrix); var iStep = 0; var iCurStep = 0; var bStep = 0; function drawScene() { gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix); mat4.identity(mvMatrix); mat4.translate(mvMatrix, [0.0, 0.0, z]); mat4.rotate(mvMatrix, degToRad(yRot), [0, 1, 0]); mat4.multiply(mvMatrix, MoveMatrix); mat4.multiply(mvMatrix, RotationMatrix); iStep = (parseInt((yRot+45) / 90)) % pics_num; if (iCurStep != iStep) { iCurStep = iStep; var bChange = (iCurStep) % 2; var bChange2 = (iCurStep+1) % 2; if (iCurStep+2 > pics_num) { iCurStep = 1; usedTextures[0] = crateTextures[0]; } else { bPause = true; if (bChange) usedTextures[0] = crateTextures[iCurStep+1]; if (bChange2) usedTextures[1] = crateTextures[iCurStep+1]; } } for (var i=0;i<4;i=i+1) { gl.bindBuffer(gl.ARRAY_BUFFER, objVertexPositionBuffer[i]); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, objVertexPositionBuffer[i].itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, objVertexTextureCoordBuffer[i]); gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, objVertexTextureCoordBuffer[i].itemSize, gl.FLOAT, false, 0, 0); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, usedTextures[i % 2]); gl.uniform1i(shaderProgram.samplerUniform, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, objVertexIndexBuffer[i]); setMatrixUniforms(); gl.drawElements(gl.TRIANGLES, objVertexIndexBuffer[i].numItems, gl.UNSIGNED_SHORT, 0); } } var lastTime = 0; var iElapsed = 0; function animate() { var timeNow = new Date().getTime(); if (lastTime != 0 && bPause == false) { var elapsed = timeNow - lastTime; yRot += (ySpeed * elapsed) / 1000.0; } if (bPause == true) { var elapsed = timeNow - lastTime; iElapsed += elapsed; if (iElapsed > iPause) { bPause = false; iElapsed = 0; } } lastTime = timeNow; } function drawFrame() { requestAnimFrame(drawFrame); drawScene(); animate(); } function initWebGl() { var canvas = document.getElementById('panel'); initGL(canvas); initShaders(); initObjBuffers(); initTextures(); gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.enable(gl.DEPTH_TEST); drawFrame(); }
Hope that you already read similar tutorial – Creating a Photo Array in WebGL. In this case it will more easy to understand today`s code. I have made several changes in this code: I have removed all mouse/keyboard handlers, and have done most of changes in drawScene function.
Live Demo
Conclusion
I hope you enjoyed today`s result. If you have any suggestions or ideas – share it :-) Welcome back our friends!
I think the stuff you’re doing are great. However, I would wish that you explain your code a little better. There’s not really a point in reviewing past tutorials, because it’s pretty much crawling through lines of .js code anyway.
Hello mate,
Thanks for your suggestion, I will try follow your advice to explain the code in details.
not work in chrome
It does, but it doesn’t work in IE (because of webgl)