В качестве примера анимации создадим вращающийся куб. Итак, полный код веб-странички будет следующим:
<!DOCTYPE html> <html> <head> <title>3D in WebGL!</title> <meta charset="utf-8" /> </head> <body> <canvas id="canvas3D" width="400" height="300">Ваш браузер не поддерживает элемент canvas</canvas> <script type="text/javascript" src="gl-matrix-min.js"></script> <script id="shader-fs" type="x-shader/x-fragment"> void main(void) { gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); } </script> <script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); } </script> <script type="text/javascript"> var gl; var shaderProgram; var vertexBuffer; var indexBuffer; var angle = 0.0; // угол поворота var mvMatrix = mat4.create(); var pMatrix = mat4.create(); function initShaders() { var fragmentShader = getShader(gl.FRAGMENT_SHADER, 'shader-fs'); var vertexShader = getShader(gl.VERTEX_SHADER, 'shader-vs'); shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { alert("Не удалось установить шейдеры"); } gl.useProgram(shaderProgram); shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.MVMatrix = gl.getUniformLocation(shaderProgram, "uMVMatrix"); shaderProgram.ProjMatrix = gl.getUniformLocation(shaderProgram, "uPMatrix"); } function setMatrixUniforms(){ gl.uniformMatrix4fv(shaderProgram.ProjMatrix,false, pMatrix); gl.uniformMatrix4fv(shaderProgram.MVMatrix, false, mvMatrix); } function getShader(type,id) { var source = document.getElementById(id).innerHTML; var shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { alert("Ошибка компиляции шейдера: " + gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } function initBuffers() { var vertices =[ -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5 ]; var indices = [0, 1, 1, 2, 2, 3, 3, 0, 0, 4, 4, 5, 5, 6, 6,7, 7,4, 1, 5, 2, 6, 3, 7]; vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); vertexBuffer.itemSize = 3; indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW); indexBuffer.numberOfItems = indices.length; } function draw() { gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, vertexBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.drawElements(gl.LINES, indexBuffer.numberOfItems, gl.UNSIGNED_SHORT,0); } function setupWebGL() { gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); angle += 0.01; gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); mat4.perspective(pMatrix, 1.04, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0); mat4.identity(mvMatrix); mat4.translate(mvMatrix,mvMatrix,[0, 0, -2.0]); mat4.rotate(mvMatrix,mvMatrix, angle, [0, 1, 0]); } window.onload=function(){ var canvas = document.getElementById("canvas3D"); try { gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); } catch(e) {} if (!gl) { alert("Ваш браузер не поддерживает WebGL"); } if(gl){ gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; initShaders(); initBuffers(); // функция анимации (function animloop(){ setupWebGL(); setMatrixUniforms(); draw(); requestAnimFrame(animloop, canvas); })(); } } // настройка анимации window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback, element) { return window.setTimeout(callback, 1000/60); }; })(); </script> </body> </html>
В отличие от примера со статическим кубом здесь есть несколько изменений. Во-первых, мы добавили переменную angle
, которая будет представлять
угол поворота. С каждым интервалом времени данная переменная будет изменяться, и соответственно будет меняться угол поворота куба.
Во-вторых, в самом конце кода мы добавили функцию requestAnimationFrame для создания анимации.
Для создания анимации раньше разработчики могли использовать специальные функции javascript:
setTimeout(callback, timeoutInMilliseconds)
setInterval(callback, timeoutInMilliseconds)
Обе функции в качестве параметра callback принимают функцию, которая срабатывала через определенное время timeoutInMilliseconds. Ну допустим относительно нашей задачи мы могли бы написать:
window.onload=function(){ //код опущен для ясности //....................... initShaders(); initBuffers(); setInterval(drawloop, 16.7); } function drawloop() { setupWebGL(); setMatrixUniforms(); draw(); }
Здесь функция setInterval вызывает функцию drawloop каждые 16,7 миллисекунд, то есть 60 раз в секунду. По сути данный подход будет равноценен предыдущему. Но.. Разработчики браузеров в последних версиях стали внедрять новое решение - метод requestAnimationFrame, который является рекомендуемым способом для создания скриптовой анимации. Здесь, в отличие от использования методов setInterval() и setTimeout(), браузер сам оптимизирует анимацию. Если в случае с setInterval() мы сами устанавливаем интервал анимации, то при использовании requestAnimationFrame() браузер сам определяет оптимальный интервал - ведь в одно и тоже время в браузере могут выполняться сразу несколько анимаций, которые влияют друг на друга. Поэтому для создания более плавного эффекта браузер может замедлять анимации, эффективно определяя нужный интервал.
В данном случае мы задействуем requestAnimationFrame, и в качестве параметра данный метод получает функцию, выполняемую перед перерисовкой.
window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback, element) { return window.setTimeout(callback, 1000/60); }; })();
Это кроссбраузерное определение метода. Далее в функции настройки контекста setupWebGL() увеличиваем угол на некоторое число и применяем его при повороте матрицы:
angle += 0.01; //......................... mat4.rotate(mvMatrix,mvMatrix, angle, [0, 1, 0]);
В главной функции мы используем следующую конструкцию, в которую выносим функции по настройке матриц и отрисовке:
(function animloop(){ setupWebGL(); setMatrixUniforms(); draw(); requestAnimFrame(animloop, canvas); })();
Функция requestAnimFrame(animloop, canvas) как раз обращается к window.requestAnimationFrame, передавая в параметрах функцию обратного вызова и элемент, который содержит всю анимацию, то есть в данном случае canvas.
Подобным образом мы можем задать анимацию перемещения или масштабирования. Либо также мы могли бы анимировать перемещение камеры, а не куба. Но общий принцип будет тот же.