Creating a 3D Animated Box HTML5 WebGL Photo Slideshow

Tutorials

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
download in package

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

01 <!DOCTYPE html>
02 <html lang="en" >
03     <head>
04         <meta charset="utf-8" />
05         <title>WebGL Box photo slideshow | Script Tutorials</title>
06
07         <link href="css/main.css" rel="stylesheet" type="text/css" />
08         <script src="js/glMatrix-0.9.5.min.js"></script>
09         <script src="js/webgl-utils.js"></script>
10         <script src="js/script.js"></script>
11     </head>
12     <body onload="initWebGl()">
13         <div class="container">
14             <canvas id="panel" width="800" height="600"></canvas>
15         </div>
16         <footer>
17             <h2>WebGL Box photo slideshow</h2>
18             <a href="http://www.script-tutorials.com/webgl-box-photo-slideshow/" class="stuts">Back to original tutorial on <span>Script Tutorials</span></a>
19         </footer>
20     </body>
21 </html>

Step 2. CSS

Here are used CSS styles.

css/main.css

01 *{
02     margin:0;
03     padding:0;
04 }
05 body {
06     background-repeat:no-repeat;
07     background-color:#bababa;
08     background-image: -webkit-radial-gradient(600px 200pxcircle#eee#bababa 40%);
09     background-image: -moz-radial-gradient(600px 200pxcircle#eee#bababa 40%);
10     background-image: -o-radial-gradient(600px 200pxcircle#eee#bababa 40%);
11     background-image: radial-gradient(600px 200pxcircle#eee#bababa 40%);
12     color:#fff;
13     font:14px/1.3 Arial,sans-serif;
14     min-height:600px;
15 }
16 footer {
17     background-color:#212121;
18     bottom:0;
19     box-shadow: 0 -1px 2px #111111;
20     display:block;
21     height:70px;
22     left:0;
23     position:fixed;
24     width:100%;
25     z-index:100;
26 }
27 footer h2{
28     font-size:22px;
29     font-weight:normal;
30     left:50%;
31     margin-left:-400px;
32     padding:22px 0;
33     position:absolute;
34     width:540px;
35 }
36 footer a.stuts,a.stuts:visited{
37     border:none;
38     text-decoration:none;
39     color:#fcfcfc;
40     font-size:14px;
41     left:50%;
42     line-height:31px;
43     margin:23px 0 0 110px;
44     position:absolute;
45     top:0;
46 }
47 footer .stuts span {
48     font-size:22px;
49     font-weight:bold;
50     margin-left:5px;
51 }
52 .container {
53     border:3px #111 solid;
54     margin:20px auto;
55     padding:20px;
56     position:relative;
57     width:800px;
58
59     border-radius:15px;
60     -moz-border-radius:15px;
61     -webkit-border-radius:15px;
62 }

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

001 var gl; // global WebGL object
002 var shaderProgram;
003
004 var pics_names=[
005     'images/0.png',
006     'images/1.jpg',
007     'images/2.jpg',
008     'images/3.jpg',
009     'images/4.jpg',
010     'images/5.jpg',
011     'images/6.jpg',
012     'images/7.jpg',
013     'images/8.jpg',
014     'images/9.jpg'
015 ];
016 var pics_num=pics_names.length;
017
018 // diffirent initializations
019
020 function initGL(canvas) {
021     try {
022         gl = canvas.getContext('experimental-webgl');
023         gl.viewportWidth = canvas.width;
024         gl.viewportHeight = canvas.height;
025     catch (e) {}
026     if (! gl) {
027         alert('Can`t initialise WebGL, not supported');
028     }
029 }
030
031 function getShader(gl, type) {
032     var str = '';
033     var shader;
034
035     if (type == 'x-fragment') {
036         str = "#ifdef GL_ES\n"+
037 "precision highp float;\n"+
038 "#endif\n"+
039 "varying vec2 vTextureCoord;\n"+
040 "uniform sampler2D uSampler;\n"+
041 "void main(void) {\n"+
042 "    gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));\n"+
043 "}\n";
044         shader = gl.createShader(gl.FRAGMENT_SHADER);
045     else if (type == 'x-vertex') {
046         str = "attribute vec3 aVertexPosition;\n"+
047 "attribute vec2 aTextureCoord;\n"+
048 "uniform mat4 uMVMatrix;\n"+
049 "uniform mat4 uPMatrix;\n"+
050 "varying vec2 vTextureCoord;\n"+
051 "void main(void) {\n"+
052 "    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);\n"+
053 "    vTextureCoord = aTextureCoord;\n"+
054 "}\n";
055         shader = gl.createShader(gl.VERTEX_SHADER);
056     else {
057         return null;
058     }
059
060     gl.shaderSource(shader, str);
061     gl.compileShader(shader);
062
063     if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
064         alert(gl.getShaderInfoLog(shader));
065         return null;
066     }
067     return shader;
068 }
069
070 function initShaders() {
071     var fragmentShader = getShader(gl, 'x-fragment');
072     var vertexShader = getShader(gl, 'x-vertex');
073
074     shaderProgram = gl.createProgram();
075     gl.attachShader(shaderProgram, vertexShader);
076     gl.attachShader(shaderProgram, fragmentShader);
077     gl.linkProgram(shaderProgram);
078
079     if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
080         alert('Can`t initialise shaders');
081     }
082
083     gl.useProgram(shaderProgram);
084
085     shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, 'aVertexPosition');
086     gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
087
088     shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, 'aTextureCoord');
089     gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
090
091     shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, 'uPMatrix');
092     shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, 'uMVMatrix');
093     shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, 'uSampler');
094 }
095
096 var objVertexPositionBuffer=new Array();
097 var objVertexTextureCoordBuffer=new Array();
098 var objVertexIndexBuffer=new Array();
099
100 function initObjBuffers() {
101     for (var i=0;i<4;i=i+1) {
102         objVertexPositionBuffer[i] = gl.createBuffer();
103         gl.bindBuffer(gl.ARRAY_BUFFER, objVertexPositionBuffer[i]);
104         vertices = [
105             Math.cos(i*((2*Math.PI)/4)), -0.5,  Math.sin(i*((2*Math.PI)/4)),
106             Math.cos(i*((2*Math.PI)/4)), 0.5,  Math.sin(i*((2*Math.PI)/4)),
107             Math.cos((i+1)*((2*Math.PI)/4)), 0.5, Math.sin((i+1)*((2*Math.PI)/4)),
108             Math.cos((i+1)*((2*Math.PI)/4)), -0.5,  Math.sin((i+1)*((2*Math.PI)/4)),
109         ];
110         gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
111         objVertexPositionBuffer[i].itemSize = 3;
112         objVertexPositionBuffer[i].numItems = 4;
113
114         objVertexTextureCoordBuffer[i] = gl.createBuffer();
115         gl.bindBuffer(gl.ARRAY_BUFFER,  objVertexTextureCoordBuffer[i] );
116         var textureCoords = [
117             1.0, 0.0,
118             1.0, 1.0,
119             0.0, 1.0,
120             0.0, 0.0,
121         ];
122         gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
123         objVertexTextureCoordBuffer[i].itemSize = 2;
124         objVertexTextureCoordBuffer[i].numItems = 4;
125
126         objVertexIndexBuffer[i] = gl.createBuffer();
127         gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, objVertexIndexBuffer[i]);
128         var objVertexIndices = [
129             0, 1, 2,
130             0, 2, 3,
131         ];
132         gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(objVertexIndices), gl.STATIC_DRAW);
133         objVertexIndexBuffer[i].itemSize = 1;
134         objVertexIndexBuffer[i].numItems = 6;
135     }
136 }
137
138 function handleLoadedTexture(texture) {
139     gl.bindTexture(gl.TEXTURE_2D, texture);
140     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
141     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
142     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
143     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
144     gl.bindTexture(gl.TEXTURE_2D, null);
145 }
146
147 var crateTextures = Array();
148 var usedTextures = Array();
149 function initTexture(image) {
150     var texture = gl.createTexture(); // allocate texture
151     texture.image = new Image();
152
153     texture.image.onload = function () {
154         handleLoadedTexture(texture);
155     }
156     texture.image.src = image;
157     return texture;
158 }
159
160 function initTextures() {
161     for (var i=0; i < pics_num; i++) {
162         crateTextures[i]=initTexture(pics_names[i]);
163     }
164     usedTextures = crateTextures.slice(0, 2);
165 }
166
167 var mvMatrix = mat4.create();
168 var mvMatrixStack = [];
169 var pMatrix = mat4.create();
170
171 function setMatrixUniforms() {
172     gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
173     gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
174 }
175
176 function degToRad(degrees) {
177     return degrees * Math.PI / 180;
178 }
179
180 var yRot = -45;
181 var ySpeed = 40;
182 var z = -2.5;
183 var iPause = 1000;
184 var bPause = false;
185
186 var RotationMatrix = mat4.create();
187 mat4.identity(RotationMatrix);
188
189 // Draw scene and initialization
190
191 var MoveMatrix = mat4.create();
192 mat4.identity(MoveMatrix);
193
194 var iStep = 0;
195 var iCurStep = 0;
196 var bStep = 0;
197
198 function drawScene() {
199     gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
200     gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
201
202     mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
203     mat4.identity(mvMatrix);
204     mat4.translate(mvMatrix, [0.0, 0.0, z]);
205     mat4.rotate(mvMatrix, degToRad(yRot), [0, 1, 0]);
206     mat4.multiply(mvMatrix, MoveMatrix);
207     mat4.multiply(mvMatrix, RotationMatrix);
208
209     iStep = (parseInt((yRot+45) / 90)) % pics_num;
210
211     if (iCurStep != iStep) {
212         iCurStep = iStep;
213
214         var bChange = (iCurStep) % 2;
215         var bChange2 = (iCurStep+1) % 2;
216
217         if (iCurStep+2 > pics_num) {
218             iCurStep = 1;
219             usedTextures[0] = crateTextures[0];
220         else {
221             bPause = true;
222
223             if (bChange)
224                 usedTextures[0] = crateTextures[iCurStep+1];
225
226             if (bChange2)
227                 usedTextures[1] = crateTextures[iCurStep+1];
228         }
229     }
230
231     for (var i=0;i<4;i=i+1) {
232         gl.bindBuffer(gl.ARRAY_BUFFER, objVertexPositionBuffer[i]);
233         gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, objVertexPositionBuffer[i].itemSize, gl.FLOAT, false, 0, 0);
234
235         gl.bindBuffer(gl.ARRAY_BUFFER, objVertexTextureCoordBuffer[i]);
236         gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, objVertexTextureCoordBuffer[i].itemSize, gl.FLOAT, false, 0, 0);
237
238         gl.activeTexture(gl.TEXTURE0);
239         gl.bindTexture(gl.TEXTURE_2D, usedTextures[i % 2]);
240         gl.uniform1i(shaderProgram.samplerUniform, 0);
241
242         gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, objVertexIndexBuffer[i]);
243         setMatrixUniforms();
244         gl.drawElements(gl.TRIANGLES, objVertexIndexBuffer[i].numItems, gl.UNSIGNED_SHORT, 0);
245     }
246 }
247
248 var lastTime = 0;
249 var iElapsed = 0;
250 function animate() {
251     var timeNow = new Date().getTime();
252     if (lastTime != 0 && bPause == false) {
253         var elapsed = timeNow - lastTime;
254         yRot += (ySpeed * elapsed) / 1000.0;
255     }
256     if (bPause == true) {
257         var elapsed = timeNow - lastTime;
258         iElapsed += elapsed;
259         if (iElapsed > iPause) {
260             bPause = false;
261             iElapsed = 0;
262         }
263     }
264     lastTime = timeNow;
265 }
266
267 function drawFrame() {
268     requestAnimFrame(drawFrame);
269     drawScene();
270     animate();
271 }
272
273 function initWebGl() {
274     var canvas = document.getElementById('panel');
275     initGL(canvas);
276     initShaders();
277     initObjBuffers();
278     initTextures();
279
280     gl.clearColor(0.0, 0.0, 0.0, 1.0);
281     gl.enable(gl.DEPTH_TEST);
282
283     drawFrame();
284 }

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
download in package

Conclusion

I hope you enjoyed today`s result. If you have any suggestions or ideas – share it :-) Welcome back our friends!

Rate article