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:
[sociallocker]
[/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
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> |
10 | <body onload="initWebGl();"> |
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> |
Step 2. CSS
Here are used CSS styles.
css/main.css
03 | font-family:Verdana, Helvetica, Arial, sans-serif; |
11 | border:1px #000 solid; |
15 | -moz-border-radius: 3px; |
16 | -webkit-border-radius:3px |
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
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; |
006 | function initGL(canvas) { |
008 | gl = canvas.getContext('experimental-webgl'); |
009 | gl.viewportWidth = canvas.width; |
010 | gl.viewportHeight = canvas.height; |
013 | alert('Can`t initialise WebGL, not supported'); |
016 | function getShader(gl, type) { |
019 | if (type == 'x-fragment') { |
020 | str = "#ifdef GL_ES\n"+ |
021 | "precision highp float;\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"+ |
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"+ |
039 | shader = gl.createShader(gl.VERTEX_SHADER); |
043 | gl.shaderSource(shader, str); |
044 | gl.compileShader(shader); |
045 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { |
046 | alert(gl.getShaderInfoLog(shader)); |
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'); |
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'); |
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]); |
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)), |
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 = [ |
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 = [ |
103 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(objVertexIndices), gl.STATIC_DRAW); |
104 | objVertexIndexBuffer[i].itemSize = 1; |
105 | objVertexIndexBuffer[i].numItems = 6; |
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); |
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) |
127 | function initTextures() { |
128 | for (var i=0; i < pics_num; i++) { |
129 | crateTextures[i]=initTexture(pics_names[i]); |
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); |
139 | function degToRad(degrees) { |
140 | return degrees * Math.PI / 180; |
148 | var currentlyPressedKeys = {}; |
149 | function handleKeyDown(event) { |
150 | currentlyPressedKeys[event.keyCode] = true; |
152 | function handleKeyUp(event) { |
153 | currentlyPressedKeys[event.keyCode] = false; |
155 | function handleKeys() { |
156 | if (currentlyPressedKeys[33]) { |
159 | if (currentlyPressedKeys[34]) { |
162 | if (currentlyPressedKeys[37]) { |
165 | if (currentlyPressedKeys[39]) { |
168 | if (currentlyPressedKeys[38]) { |
171 | if (currentlyPressedKeys[40]) { |
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) { |
182 | lastMouseX = event.clientX; |
183 | lastMouseY = event.clientY; |
185 | function handleMouseUp(event) { |
188 | function handleMouseMove(event) { |
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); |
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]); |
227 | gl.drawElements(gl.TRIANGLES, objVertexIndexBuffer[i].numItems, gl.UNSIGNED_SHORT, 0); |
232 | var timeNow = new Date().getTime(); |
234 | var elapsed = timeNow - lastTime; |
235 | xRot += (xSpeed * elapsed) / 1000.0; |
236 | yRot += (ySpeed * elapsed) / 1000.0; |
240 | function drawFrame() { |
241 | requestAnimFrame(drawFrame); |
246 | function initWebGl() { |
247 | var canvas = document.getElementById('panel'); |
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; |
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:
Conclusion
I hope you enjoyed today`s result. If you have any suggestions or ideas – share it 🙂 Welcome back our friends!