Использование матриц glMatrix для создания 3D

Последнее обновление: 1.11.2015

Поскольку 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);
} 

Обратите внимание на порядок преобразований: матрица проекции умножается на матрицу модели, а не наоборот, и потом все умножается на вектор координат вершин.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850