Как стать автором
Обновить

Three.js — 3D в браузере своими руками или WebGL становится ближе

Время на прочтение 7 мин
Количество просмотров 32K
Тема про three.js от mrdoob в свое время проскакивала на хабре, но детально еще не рассматривалась. В этой и(возможно) последующих статьях я постараюсь исправить это упущение. К сожалению, three.js не предоставляет никакой внятной документации, поэтому все знания по нему получены экспериментальным путем и ковырянием спеки WebGL, т.е. любые дополнения автору топика только приветствуются.

В этой статье мы создадим простенькое приложение, которое продемонстрирует базовые возможности этого движка, поработаем с камерой, светом и тенью, а также научим наши объекты перемещаться. В конце статьи ссылка на файлы демки и скриншот. Итак, к делу!

Первые шаги


HTML у нас достаточно простенький, не вызывает вопросов и выглядит следующим образом:

<!DOCTYPE html>
<html>
<head>
	<title>Three.js - dice</title>
	<meta charset="utf-8">
	<style>
		body {
			margin: 0;
			padding: 0;
			overflow: hidden;
		}
	</style>
	<!-- libs -->
	<script type="text/javascript" src="js/Three.js"></script>
	<script type="text/javascript" src="js/RequestAnimationFrame.js"></script>
	<script type="text/javascript" src="js/jquery.js"></script>
	<!-- app -->
	<script type="text/javascript" src="js/demoapp.js"></script>
</head>
<body>

</body>
</html>


В данном примере будем использовать jQuery, однако это абсолютно не критично и вполне можно было бы обойтись и без неё. RequestAnimationFrame.js, — это небольшой хак для обеспечения кроссбраузерной работы(как это ни странно) RequestAnimationFrame (об этом немного ниже).

Теперь перейдем непосредственно к нашему файлу скрипта demoapp.js
Вешаем обработку всего кода внутри файла на событие ready документа
 $(document).ready(function() { /**код внутри**/});

Для поддержания простоты примера оставим стандартные для демок three.js функции, — init() и animate(). В первой у нас инициализация всего, что мы хотели бы увидеть, а вторая будет использовать requestAnimationFrame и вызывать функцию render(), которая уже и будет отрисовывать все изменения. По мере усложнения нашего демо-приложения, его архитектура будет переведена на backbone.js (но это уже тема, которая будет рассмотрена в рамках отдельной статьи).

Основные элементы


Основные элементы, которые будут принимать участие в нашей постановке (обратите внимание, основные, но далеко не все, в three.js достаточно богатый выбор действий):

1) Камера ( THREE.Camera)
Это наши “глаза” на импровизированной сцене событий.
Первый параметр показывает удаленность от объекта, второй — отношение сторон ( как правило, используется именно частное от ширины и высоты окна), третий и четвертый, — near/far, параметры, относящиеся к плоскости и используемые при рендеринге.
camera = new THREE.Camera(30, window.innerWidth/window.innerHeight,1, 3000);

Либо же можно сразу создать камеру, которая сможет при зажатой левой кнопке мыши крутиться вокруг сцены, при зажатой правой двигаться вверх-вниз, а при зажатой средней, — зумить сцену. Делается это следующим образом (параметры, передаваемые камере достаточно ясно говорят сами за себя по названиям и показаны в коде приложения):
camera = new THREE.TrackballCamera({/**parameters**/});

Следует также заметить, что используется Декартова система координат, т.е. ось x направлена от верхнего левого угла экрана в нижний правый, ось z от верхнего правого в нижний левый и ось y от нижней части экрана к верхней соответственно.

2) Сцена (THREE.Scene)
Основной элемент. Именно на сцену мы будем добавлять все созданные нами объекты и плоскости (с помощью .addObject или addChild). Инициализация сцены выглядит следующим образом:
scene = new THREE.Scene();

3) Мотор! Мэш (THREE.Mesh)
Именно с помощью мэша составляются почти все объекты в three.js. Для работы с ним нам нужно определить геометрию и материал мэша.
Геометрия, — это объекты, из которых составляется сцена (кубы, сферы, цилиндры, плоскости и прочее. Эта тема является достаточно объемной, поэтому сейчас мы не будем подробно на ней останавливаться). Материал, впрочем, не менее объемный вопрос, поэтому для начала можно лишь уточнить два момента, — для применения материала, указанного в геометрии следует использовать MeshFaceMaterial, для применения в большинстве других случаев вполне может хватить MeshBasicMaterial.
mesh = new THREE.Mesh( geometry, material );

После чего мы можем со спокойной душой добавлять мэш в сцену
scene.addObject( mesh );//или .addChild

Удаляется объект также несложно, с помощью removeObject
scene.removeObject(mesh);


4) Рендерер (THREE.WebGLRenderer / CanvasRenderer)
Нам доступно два вида рендереров, — WebGLRenderer и CanvasRenderer. Уже по названиям можно понять, что к чему. Следует также заметить, что, как и ожидалось, WebGLRenderer заметно сильнее роняет fps на слабеньких машинах. Использовать рендереры также предельно просто:
renderer = new THREE.WebGLRenderer( );

или
renderer = new THREE.CanvasRenderer();


Одним из требуемых действий после инициализации рендерера является установка его размера, — здесь мы вольны выбирать какой угодно размер, исходя из наших нужд.
renderer.setSize( window.innerWidth, window.innerHeight );

И, наконец, нам нужно добавить элемент рендерера на страницу. Делается это следующим образом и одинаково для всех типов рендереров:
container.appendChild( renderer.domElement );

или же для jQuery
container.append(renderer.domElement);


5) Свет.
Да, к сцене можно (а иногда и нужно) применять свет, например, для получения теней от объектов (их можно, конечно, сделать и с помощью параллельно движущихся плоскостей, но это скорее в виде исключения). Существует три вида освещения:
5.1) THREE.AmbientLight — это освещение, которое затрагивает всю сцену. Оно не имеет направления и затрагивает каждый объект сцены в равной степени, независимо от расположения объекта. Соответственно, у этого света нет позиции на оси координат
5.2) THREE.PointLight — освещение, исходящее из одной точки во всех направлениях. Думаю, сравнение с обыкновенной лампочкой будет достаточно уместно.
5.3) THREE.DirectionalLight — свет, движущийся в определенном направлении (от заданной точки и к началу координат). Солнечный свет, например, можно отнести именно к этому типу освещения.
В примере мы будем использовать только DirectionalLight для получения тени от размещенного над плоскостью объекта.

Применяем знания


Теперь можно перейти непосредственно к коду приложения. В нем мы будем использовать все вышеописанные элементы, а также комментировать производимые действия. Для начала стартуем наше приложение посредством вызова функций инициализации и анимации, а также объявим нужные нам глобальные переменные.
//глобальные переменные
var container, camera, scene, renderer, floormesh, cubeMesh, phi = 0; 
 		init();
 		animate();

Рассмотрим функцию инициализации.
function init()
 		{
 				//создаем элемент и вставляем его в тело документа
				container = $( 'div' ).attr('id','cardfield');
				$('body').append( container );
				//создаем камеру
				camera = new THREE.TrackballCamera({
					fov: 45, 
					aspect: window.innerWidth / window.innerHeight,
					near: 1,
					far: 10000, 
					rotateSpeed: 1.0,
					zoomSpeed: 1.2,
					panSpeed: 0.8, 
					noZoom: false,
					noPan: false						
				});

				//устанавливаем камере позицию, немного разворачиваем её, чтобы она смотрела на нашу плоскость
				camera.position.z = 250;
				camera.position.y = 175;
				camera.target.position.y = -75;

				//создаем сцену
				scene = new THREE.Scene();

				//создаем наш "пол". Это будет псевдокуб со сторонами в 600х600 и глубиной 5
				var floorgeo = new THREE.CubeGeometry(600,600,5);
				//создаем мэш для него с материалом заданного цвета и прозрачностью
				floormesh = new THREE.Mesh(floorgeo, new THREE.MeshBasicMaterial({color: 0x248C0F, opacity:0.9}));
				//устанавливаем позицию нашему полу
				floormesh.position.y = -200;
				//и разворачиваем его по оси х так, чтобы он был параллелен ей.
				floormesh.rotation.x = 90 * Math.PI / 180;
				//добавляем к сцене
				scene.addChild(floormesh);

				//обвертка для куба
				 var materials = [
				 //делаем каждую сторону своего цвета
					new THREE.MeshBasicMaterial( { color: 0xE01B4C }), // правая сторона
					new THREE.MeshBasicMaterial( { color: 0x34609E }), // левая сторона
					new THREE.MeshBasicMaterial( { color: 0x7CAD18 }), //верх
					new THREE.MeshBasicMaterial( { color: 0x00EDB2 }), // низ
					new THREE.MeshBasicMaterial( { color: 0xED7700 }), // лицевая сторона
					new THREE.MeshBasicMaterial( { color: 0xB5B1AE }) // задняя сторона
				];

				//создаем куб со стороной 50 и размерами сегментов 1, применяем к нему массив материалов
				var cube = new THREE.CubeGeometry( 50, 50, 50, 1, 1, 1, materials );
				//создаем мэш для куба, в качестве материала мэша 
				//будет браться тот, который применен к кубу
				cubeMesh = new THREE.Mesh( cube, new THREE.MeshFaceMaterial() );
				//указываем позицию по оси y
				cubeMesh.position.y = -10;
				//добавляем к сцене
				scene.addChild( cubeMesh );
 				//добавляем тень кубу
 				new THREE.ShadowVolume( cubeMesh );

 				//устанавливаем белый свет 
				light = new THREE.DirectionalLight( 0xffffff );
				//да, объекты должны отбрасывать тень
				light.castShadow = true;
				//сам пол у нас в -150, свет соотв. ставим выше (в 1 по y и 0 по x и z), чтобы он попадал на наш куб и заставлял его отбрасывать тень
				//напомню, что свет двигается от указанной точки к началу координат
				light.position.set( 0, 1, 0 );
				//добавлям свет
				scene.addChild( light ); 
 
				//рендерер 
				renderer = new THREE.WebGLRenderer();
				//устанавливаем ему размеры экрана
				renderer.setSize( window.innerWidth, window.innerHeight );
				//и добавляем в наш созданный элемент
				container.append( renderer.domElement );
 		}

Выполнение анимации:
function animate() {
		requestAnimationFrame( animate );
		render();
 }

Эта функция является стандартной частью большинства приложений на three.js. Рассмотрим её поближе. requestAnimationFrame (animate) представляет собой функцию для обеспечения кроссбраузерной работы анимации (подробнее даже вот тут) и обеспечивает рекурсивный вызов функции render(), которая, собственно, и рендерит всё это наше счастье.

Рендеринг
Именно здесь нужно настраивать перемещения камеры и объектов (разумеется, заранее нужно настроить область видимости переменных таким образом, чтобы они были видны в этой функции)
function render()
 		{
 			//вращаем куб по всем трем осям (переменная мэша куба доступна глобально)
 			cubeMesh.rotation.x += 0.5 * Math.PI / 90;
			cubeMesh.rotation.y += 1.0 * Math.PI / 90;
			cubeMesh.rotation.z += 1.5 * Math.PI / 90;
			//двигаем куб по кругу, изменяя координаты его позиции по осям x и y
			cubeMesh.position.x = Math.sin( phi ) * 50;
			cubeMesh.position.y = Math.cos( phi ) * 50;
			//итерируем глобальную переменную
			phi+= 0.05;
			//рендерим
 			renderer.render(scene, camera);
 		}


Следует обратить внимание, что в примере использовалась билд версия three.js, взятая с github (по сравнению с обычной версией изменен вызов геометрии фигур, вместо THREE.xxx THREE.xxxGeometry, например THREE.CubeGeometry на старой версии three.js работать не будет, но будет работать THREE.Cube. Также старая версия не понимает THREE.TrackBallCamera).

Итоги:


Мы создали наше первое простенькое демо, ознакомились с базовыми компонентами, без которых невозможно создать ни одно приложение, основанное на three.js, поставили свет и увидели в действии цикл анимации. В дальнейшем мы разберемся с добавлением собственных текстур и моделей объектов, построением сложных сцен, добавлением действий над объектами сцены и переведем это все счастье на backbone.js. Исходный код приложения доступен по ссылке.
Теги:
Хабы:
+38
Комментарии 22
Комментарии Комментарии 22

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн