Сегодня мы продолжаем примеры HTML5 canvas. И сегодня наш второй учебник для WebGL. Мы будем создавать анимированные скручивающиеся изображения. Также мы добавим обработчики для манипулирования изображениями с помощью мыши и клавиатуры.
Вот наш демонстрационный и загружаемый пакет:
Live Demo
скачать в упаковке
Хорошо, скачайте файлы примеров и давайте начнем кодировать!
Шаг 1. HTML
Вот html источники нашего демо. Как видите — просто пустая страница.
index.html
<!DOCTYPE html> <html lang="en" > <head> <link href="css/main.css" rel="stylesheet" type="text/css" /> <script type="text/javascript" src="js/glMatrix-0.9.5.min.js"></script> <script type="text/javascript" src="js/webgl-utils.js"></script> <script type="text/javascript" src="js/script.js"></script> <title>Creating a Keyboard Sensitive 3D Twisted Images in WebGl | Script Tutorials</title> </head> <body onload="initWebGl();"> <div class="example"> <h3><a href="http://www.script-tutorials.com/twisting-images-webgl/">Creating a Keyboard Sensitive 3D Twisted Images in WebGl | Script Tutorials</a></h3> <h3>You can use your mouse + arrow keys + page up / down</h3> <canvas id="panel" width="500" height="333"></canvas> <div style="clear:both;"></div> </div> </body> </html>
Шаг 2. CSS
Здесь используются стили CSS.
CSS / main.css
body { background:#eee; font-family:Verdana, Helvetica, Arial, sans-serif; margin:0; padding:0 } .example { background:#fff; width:500px; font-size:80%; border:1px #000 solid; margin:20px auto; padding:15px; position:relative; -moz-border-radius: 3px; -webkit-border-radius:3px } h3 { text-align:center; }
Шаг 3. JS
js / webgl-utils.js и js / glMatrix-0.9.5.min.js
Эти файлы мы будем использовать в проекте для работы с WebGL. Оба файла будут в нашей упаковке.
JS / script.js
var gl; // global WebGL object var shaderProgram; var pics_names=['1.png', '2.png', '3.png', '4.png', '5.png', '6.png', '7.png']; 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<pics_num;i=i+1) { objVertexPositionBuffer[i] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, objVertexPositionBuffer[i]); vertices = [ Math.cos(i*((2*Math.PI)/pics_num)), -0.5, Math.sin(i*((2*Math.PI)/pics_num)), Math.cos(i*((2*Math.PI)/pics_num)), 0.5, Math.sin(i*((2*Math.PI)/pics_num)), Math.cos((i+1)*((2*Math.PI)/pics_num)), 0.5, Math.sin((i+1)*((2*Math.PI)/pics_num)), Math.cos((i+1)*((2*Math.PI)/pics_num)), -0.5, Math.sin((i+1)*((2*Math.PI)/pics_num)), ]; 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 = [ 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.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(); function initTexture(image) { var crateImage = new Image(); var texture = gl.createTexture(); texture.image = crateImage; crateImage.src = image; crateImage.onload = function () { handleLoadedTexture(texture) } return texture; } function initTextures() { for (var i=0; i < pics_num; i++) { crateTextures[i]=initTexture(pics_names[i]); } } 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; } // mouse and keyboard handlers var xRot = 0; var xSpeed = 0; var yRot = 0; var ySpeed = 10; var z = -3.0; var currentlyPressedKeys = {}; function handleKeyDown(event) { currentlyPressedKeys[event.keyCode] = true; } function handleKeyUp(event) { currentlyPressedKeys[event.keyCode] = false; } function handleKeys() { if (currentlyPressedKeys[33]) { // Page Up z -= 0.05; } if (currentlyPressedKeys[34]) { // Page Down z += 0.05; } if (currentlyPressedKeys[37]) { // Left cursor key ySpeed -= 1; } if (currentlyPressedKeys[39]) { // Right cursor key ySpeed += 1; } if (currentlyPressedKeys[38]) { // Up cursor key xSpeed -= 1; } if (currentlyPressedKeys[40]) { // Down cursor key xSpeed += 1; } } var mouseDown = false; var lastMouseX = null; var lastMouseY = null; var RotationMatrix = mat4.create(); mat4.identity(RotationMatrix); function handleMouseDown(event) { mouseDown = true; lastMouseX = event.clientX; lastMouseY = event.clientY; } function handleMouseUp(event) { mouseDown = false; } function handleMouseMove(event) { if (!mouseDown) { return; } var newX = event.clientX; var newY = event.clientY; var deltaX = newX - lastMouseX; var newRotationMatrix = mat4.create(); mat4.identity(newRotationMatrix); mat4.rotate(newRotationMatrix, degToRad(deltaX / 5), [0, 1, 0]); var deltaY = newY - lastMouseY; mat4.rotate(newRotationMatrix, degToRad(deltaY / 5), [1, 0, 0]); mat4.multiply(newRotationMatrix, RotationMatrix, RotationMatrix); lastMouseX = newX lastMouseY = newY; } // Draw scene and initialization var MoveMatrix = mat4.create(); mat4.identity(MoveMatrix); 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(xRot), [1, 0, 0]); mat4.rotate(mvMatrix, degToRad(yRot), [0, 1, 0]); mat4.multiply(mvMatrix, MoveMatrix); mat4.multiply(mvMatrix, RotationMatrix); for (var i=0;i<pics_num;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, crateTextures[i]); 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; function animate() { var timeNow = new Date().getTime(); if (lastTime != 0) { var elapsed = timeNow - lastTime; xRot += (xSpeed * elapsed) / 1000.0; yRot += (ySpeed * elapsed) / 1000.0; } lastTime = timeNow; } function drawFrame() { requestAnimFrame(drawFrame); handleKeys(); drawScene(); animate(); } function initWebGl() { var canvas = document.getElementById('panel'); initGL(canvas); initShaders(); initObjBuffers(); initTextures(); gl.clearColor(1.0, 1.0, 1.0, 1.0); gl.enable(gl.DEPTH_TEST); document.onkeydown = handleKeyDown; document.onkeyup = handleKeyUp; canvas.onmousedown = handleMouseDown; document.onmouseup = handleMouseUp; document.onmousemove = handleMouseMove; drawFrame(); }
И снова — длинный код, но самое главное. Я разделил весь код на 3 стороны: инициализации, обработчики и рисование сцены. Надеюсь, что вы уже прочитали наш предыдущий урок WebGL . В этом случае будет проще понять сегодняшний код. Просто обратите внимание, что вместо цветового буфера мы будем использовать текстурный буфер (objVertexTextureCoordBuffer). Также эта демоверсия способна работать с любым количеством использованных изображений (лучше — более 3-х).
Шаг 4. Изображения
Все эти изображения мы будем использовать для скручивания:
Live Demo
скачать в упаковке
Вывод
Надеюсь, вам понравился сегодняшний результат. Если у вас есть предложения или идеи — поделитесь ими,
добро пожаловать, друзья!