Creating a Keyboard Sensitive 3D Twisted Images in WebGl

Tutorials

 

Keyboard Sensitive 3D Twisted Images with WebGL. Today we continue HTML5 canvas examples. And today is our second tutorial for WebGL. We will creating animated twisting images. Also we will add handlers to manipulate with mouse and keyboard.

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

01 <!DOCTYPE html>
02 <html lang="en" >
03 <head>
04     <link href="css/main.css" rel="stylesheet" type="text/css" />
05     <script type="text/javascript" src="js/glMatrix-0.9.5.min.js"></script>
06     <script type="text/javascript" src="js/webgl-utils.js"></script>
07     <script type="text/javascript" src="js/script.js"></script>
08     <title>Creating a Keyboard Sensitive 3D Twisted Images in WebGl | Script Tutorials</title>
09 </head>
10 <body onload="initWebGl();">
11     <div class="example">
12         <h3><a href="https://www.script-tutorials.com/twisting-images-webgl/">Creating a Keyboard Sensitive 3D Twisted Images in WebGl | Script Tutorials</a></h3>
13         <h3>You can use your mouse + arrow keys + page up / down</h3>
14         <canvas id="panel" width="500" height="333"></canvas>
15         <div style="clear:both;"></div>
16     </div>
17 </body>
18 </html>

Step 2. CSS

Here are used CSS styles.

css/main.css

01 body {
02     background:#eee;
03     font-family:VerdanaHelveticaArialsans-serif;
04     margin:0;
05     padding:0
06 }
07 .example {
08     background:#fff;
09     width:500px;
10     font-size:80%;
11     border:1px #000 solid;
12     margin:20px auto;
13     padding:15px;
14     position:relative;
15     -moz-border-radius: 3px;
16     -webkit-border-radius:3px
17 }
18 h3 {
19     text-align:center;
20 }

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

And again – long code, but most important. I separated all code to 3 sides: initializations, handlers and drawing of scene. Hope that you already read our previos WebGL lesson. In this case it will more easy to understand today`s code. Just make attention that instead color buffer we will using texture buffer (objVertexTextureCoordBuffer). Also, this demo able to work with any amouht of used images (better – more than 3).

Step 4. Images

All these images we will using for twisting:

    1
    2
    3
    4
    5
    6
    7

Live Demo

Conclusion

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

Rate article