Анимация и пользовательский ввод

Анимация объектов. Вращающийся куб

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

В качестве примера анимации создадим вращающийся куб. Итак, полный код веб-странички будет следующим:

<!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 для создания анимации.

Использование 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.

Подобным образом мы можем задать анимацию перемещения или масштабирования. Либо также мы могли бы анимировать перемещение камеры, а не куба. Но общий принцип будет тот же.

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