Поскольку javascript не имеет встроенных средств для работы с матрицами и векторами, то воспользуемся сторонними библиотеками. Одной из наиболее используемых подобных библиотек является glMatrix. Ее можно найти на официальном сайте разработчика по следующему адресу: http://glmatrix.net/. Она относительно небольшая - минимизированная версия весит около 27 кБ, полная - 105 кБ.
Это не единственная библиотека javascript, которую можно использовать в WebGL для работы с матрицами. Есть и другие, например, Sylvester, WebGL-mjs, но в данном случае мы будем использовать glMatrix. И сразу на всякий случай обращаю ваше внимание, что API данной библиотеки может меняться, а примеры с ее использованием, которые вы найдете в интернете, могут не совсем работать. В этом случае вы можете обратиться к документации. Я же в данном случае буду ориентироваться на последнюю версию glMatrix 2.0
Итак, все координаты и индексы у нас будут такими же, как и в прошлом примере, только теперь мы применим матрицы:
<!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 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); // создания переменных uniform для передачи матриц в шейдер 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); 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, 1.9, [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(); setupWebGL(); setMatrixUniforms(); draw(); } } </script> </body> </html>
И если мы теперь запустим веб-страничку, то результат уже будет другим:
Теперь разберем узловые моменты кода. Первым делом, конечно, подключаем библиотеку glMatrix, в данном случае я использовал минимизированную версию.
Сначала определяем матрицы модели и проекции:
var mvMatrix = mat4.create(); // матрица модели var pMatrix = mat4.create(); // матрица проекции
Метод mat4.create()
как раз представляет метод библиотеки glMatrix, используемый для создания матрицы 4х4.
В функции установки шейдеров initShaders() создаем две матрицы - по одной для матрицы модели и проекции. Эти матрицы будут передаваться в вершинный шейдер и там применяться к координатам объекта:
shaderProgram.MVMatrix = gl.getUniformLocation(shaderProgram, "uMVMatrix"); shaderProgram.ProjMatrix = gl.getUniformLocation(shaderProgram, "uPMatrix");
Матрицы определяются как свойства в объекте shaderProgram с помощью метода gl.getUniformLocation(), который одинаков для обеих матриц.
Затем в отличие от предыдущих примеров мы добавляем две новых функции: setupWebGL() и setMatrixUniforms(), которые затем вызываем в основной функции программы.
Функция setupWebGL() содержит код по настройке трехмерной сцены, например, установку матриц и т.д. Вначале устанавливается матрица проекции:
mat4.perspective(pMatrix, 1.04, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
. Первый параметр функции - это и есть выше созданная матрица проекции pMatrix,
которая затем будет передаваться в шейдер.
Второй параметр - 1.04
- это угол обзора в радианах. Третий параметр gl.viewportWidth / gl.viewportHeight
задает
аспектное соотношение ширины к длине.
Четвертый и пятый параметр задают размеры видимой области - ближайшую и самую дальнюю ее точку.
Далее устанавливаем матрицу модели. Сначала для нее устанавливаем матрицу идентичности: mat4.identity(mvMatrix);
. А затем применяем
к ней перемещение и вращение:
mat4.translate(mvMatrix,mvMatrix,[0, 0, -2.0]); mat4.rotate(mvMatrix,mvMatrix, 1.9, [0, 1, 0]);
Метод mat4.translate
принимает в качестве первого параметра выходную матрицу, а в качестве второго - входную. Так как мы преобразуем одну матрицу, то
данные параметры у нас совпадают. Третьим параметром идет вектор, на который выполняется перемещение: то есть в данном случае идет перемещение
на 2 единицы по оси Z в сторону от нас.
Метод mat4.rotate
также в качестве первого и второго параметра принимает выходную и входную матрицу. Третьим параметром идет угол
в радианах, на который выполняется вращение, а четвертый параметр - вектор, указывающий вдоль какой оси выполнять вращение.
Поскольку в данном случае для оси Y указана единичка, а для других нули, то вращение будет идти только по оси Y.
И, наконец, метод setMatrixUniforms()
выполняет передачу матриц в вершинный шейдер:
gl.uniformMatrix4fv(shaderProgram.ProjMatrix,false, pMatrix); gl.uniformMatrix4fv(shaderProgram.MVMatrix, false, mvMatrix);
То есть фактически мы связываем матрицу shaderProgram.ProjMatrix с матрицей проекции pMatrix, а матрицу shaderProgram.MVMatrix с mvMatrix. И после этого значения матриц попадут в вершинный шейдер, где будут применены для определения конечных координат вершин:
attribute vec3 aVertexPosition; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); }
Обратите внимание на порядок преобразований: матрица проекции умножается на матрицу модели, а не наоборот, и потом все умножается на вектор координат вершин.