Pull to refresh

Изучаем Three.js.Глава 1: Создаем нашу первую 3D-сцену, используя Three.js

Reading time 13 min
Views 115K
Original author: Jos Dirksen
Всем привет!
Хочу начать вольный перевод замечательной книги «Learning Three.js- The JavaScript 3D Library for WebGL». Я уверен, что эта книга будет интересна не только новичкам, но и профессионалам своего дела. Ну не буду долго затягивать вступление, только приведу пример того, что мы совсем скоро сможем делать:





Создаем структуру HTML страницы


Первое, что нам нужно сделать, это создать пустую HTML страницу, которую можно будет использовать в качестве основы для всех наших примеров. Это HTML структура представлена следующим образом:

<!DOCTYPE html>
<html>	
	<head>
		<title>Example 01.01 - Basic skeleton</title>
		<script type="text/javascript"
		src="../libs/three.js">
		</script>
		<script type="text/javascript" 
		src="../libs/jquery-1.9.0.js">
		</script>
		<style>
		body{
		/* set margin to 0 and overflow to hidden, 
		to use the complete page */
		margin: 0;
		overflow: hidden;
		}
		</style>
	</head>
	<body>
		<!--Div which will hold the Output -->
		<div id="WebGL-output">
		</div>
		<!--Javascript code that runs our Three.js examples -->
		<script type="text/javascript">
		// once everything is loaded, we run our Three.js stuff.
		$(function () {
		// here we'll put the Three.js stuff
		});
		</script>
	</body>
</html>

В этом листинге представлена структура очень простой HTML-страницы, только с парой элементов. В блоке мы загружаем внешние библиотеки JavaScript, которые мы будем использовать в примерах. Например, в приведенном листинге мы пытаемся подключить две библиотеки: Three.js и jquery-1.9.0.js. Также в этом блоке мы прописываем пару строк для CSS оформления. В предыдущем фрагменте вы можете увидеть немного JavaScript кода. Этот небольшой фрагмент кода использует JQuery при вызове безымянной JavaScript функции, когда страница полностью загружена. Мы поместим весь код Three.js внутрь этой функции.
Three.js бывает двух версий:
  • Three.min.js: Эту библиотеку вы обычно используете при открытии Three.js сайтов. Это минимизированная версия Three.js, созданная с использованием UglifyJS, которая в два раза меньше обычной библиотеки Three.js. Все примеры и код, используемый тут, основаны на проекте Three.js r60, который был выпущен в августе 2013г.
  • Three.js: Это нормальная библиотека Three.js. Мы будем использовать эту библиотеку в наших примерах, так как это делает отладку намного проще, потому что код становится более читабельнее.

Если мы откроем только что написанную страницу в браузере, то будем не очень шокированы. Как и следовало ожидать, все, что мы увидим это пустая страница.



В следующем разделе вы узнаете, как добавить первые пару 3D-объектов на нашу сцену.

Рендеринг и просмотр 3D-объектов


На этом этапе мы создадим нашу первую сцену и добавим пару объектов и камеру. Наш первый пример будет содержать следующие объекты:
Плоскость – двумерный прямоугольник, который будет служить в качестве нашей основной площадки. Она будет отображаться как серый прямоугольник в середине сцены.
Куб – трехмерный куб, который мы будем рендеритьв красный.
Сфера – трехмерная сфера, которую мы будем рендерить в синий.
Камера – она определяет, что мы увидим в выходных данных.
Оси – х, у и z. Это полезный инструмент отладки, чтобы видеть, где рендерятся объекты.
Сначала посмотрим, как это выглядит в коде, а потом постараемся разобраться.

<script type="text/javascript">
$(function () {
	var scene = new THREE.Scene();
	var camera = new THREE.PerspectiveCamera(45 
		, window.innerWidth / window.innerHeight , 0.1, 1000);
	var renderer = new THREE.WebGLRenderer();
	renderer.setClearColorHex(0xEEEEEE);
	renderer.setSize(window.innerWidth, window.innerHeight);
	var axes = new THREE.AxisHelper( 20 );
	scene.add(axes);
	var planeGeometry = new THREE.PlaneGeometry(60,20,1,1);
	var planeMaterial = new THREE.MeshBasicMaterial({color: 0xcccccc});
	var plane = new THREE.Mesh(planeGeometry,planeMaterial);
	plane.rotation.x=-0.5*Math.PI;
	plane.position.x = 15;
	plane.position.y = 0;
	plane.position.z = 0;
	scene.add(plane);
	var cubeGeometry = new THREE.CubeGeometry(4,4,4);
	var cubeMaterial = new THREE.MeshBasicMaterial(
		{color: 0xff0000, wireframe: true});
	var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
	cube.position.x = -4;
	cube.position.y = 3;
	cube.position.z = 0;
	scene.add(cube);
	var sphereGeometry = new THREE.SphereGeometry(4,20,20);
	var sphereMaterial = new THREE.MeshBasicMaterial(
		{color: 0x7777ff, wireframe: true});
	var sphere = new THREE.Mesh(sphereGeometry,sphereMaterial);
	sphere.position.x = 20;
	sphere.position.y = 4;
	sphere.position.z = 2;
	scene.add(sphere);
	camera.position.x = -30;
	camera.position.y = 40;
	camera.position.z = 30;
	camera.lookAt(scene.position);
	$("#WebGL-output").append(renderer.domElement);
	renderer.render(scene, camera);
});

Если мы откроем этот пример в браузере, то увидим нечто похожее на то, что мы хотели, но по-прежнему далеко от идеала.



Прежде чем мы начнем делать сцену более симпатичной, давайте пройдемся и пошагово посмотрим, что же мы сделали:

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45 
	, window.innerWidth / window.innerHeight
	, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColorHex(0xEEEEEE);
renderer.setSize(window.innerWidth, window.innerHeight);

В данном примере мы определили сцену, камеру и визуализатор. Переменная сцены представляет собой контейнер, который используется для хранения и отслеживания всех объектов, которые мы хотим отобразить. Сфера и куб, которые мы хотим отобразить, будут добавлены в этом примере немного позже. В этом фрагменте мы также создали переменную камеры. Эта переменная определяет, что мы увидим, когда визуализируем сцену. Далее мы определим объект визуализации(рендеринга). Рендеринг отвечает за расчеты, за то, как будет выглядеть сцена в браузере в том или ином ракурсе. Так же мы создали объект WebGLRenderer, который будет использовать видеокарту для визуализации сцены.
Здесь, с помощью функции setClearColorHex(), мы устанавливаем цвет фона renderer до почти белого (0XEEEEEE), а так же задаем размер визуализированной сцены с помощью функции setSize().
До сих пор у нас были только базовые элементы, такие как пустая сцена, render и camera. Но, однако, пока еще нечего визуализировать. Следующий фрагмент кода добавляет вспомогательные оси и плоскость.

var axes = new THREE.AxisHelper( 20 );
scene.add(axes);
var planeGeometry = new THREE.PlaneGeometry(60,20);
var planeMaterial = new THREE.MeshBasicMaterial(
	{color: 0xcccccc});
var plane = new THREE.Mesh(planeGeometry,planeMaterial);
plane.rotation.x = -0.5*Math.PI;
plane.position.x = 15;
plane.position.y = 0;
plane.position.z = 0;
scene.add(plane);

Вы можете увидеть, что мы создали объект axes и испльзовали функцию scene.add() для добавления осей на нашу сцену. Теперь мы можем создать плоскость. Это делается в два шага. Сначала определяем, что плоскость будет отображаться с использованием new THREE. В коде будет PlaneGeometry(60,20). В данном случае наша плоскость будет иметь ширину 60 и высоту 20. Так же мы должны сказать Three.js как должна выглядеть наша плоскость(например ее цвет или прозрачность). В Three.js мы делаем это, создавая материальный объект. Для первого примера мы создадим основной материал(с помощью метода MeshBasicMaterial()) цвета 0xcccccc. Далее мы объединяем эти два объекта в один Mesh объект с именем plane. Прежде чем мы добавим plane на сцену мы должны поставить его в правильное положение; мы делаем это, во-первых, повернув его на 90 градусов вокруг оси х, а дальше мы определяем его положение на сцене с помощью свойства position. Ну и наконец, мы должны добавить этот объект на сцену, так же как мы делали с объектом axes.
Куб и сфера добавляются таким же образом, но значение свойства wireframe будет true, так что давайте перейдем к заключительной части этого примера:

camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 30;
camera.lookAt(scene.position);
$("#WebGL-output").append(renderer.domElement);
renderer.render(scene, camera);

На данном этапе все элементы, которые мы хотели визуализировать добавлены на сцену в надлежащие им места. Мы уже говорили о том, что камера определяет то, что будет отображено. В этом куске кода мы позиционируем камеру с помощью значений х, у и z атрибута position и она начинает парить над нашей сценой. Чтобы убедиться, что камера смотрит на наши объекты, мы используем функцию lookAt(), чтобы указать на центр нашей сцены.

Добавление материалов, освещения и теней


Добавление новых материалов и освещения в Three.js очень просто и осуществляется в значительной степени так же, как мы это делали в предыдущем разделе.

var spotLight = new THREE.SpotLight( 0xffffff );
spotLight.position.set( -40, 60, -10 );
scene.add(spotLight );

Метод SpotLight() освещает нашу сцену из позиции spotLight.position.set( -40, 60, -10 ).Если вы отрендерите сцену еще раз, то заметите, что нет никакой разницы с предыдущим примером. Причина в том, что различные материалы по-разному реагируют на свет. Основной материал, который мы использовали в предыдущем примере для объектов(с помощью метода MeshBasicMaterial()) никак не реагирует на источники света на сцене. Они просто визуализировали объекты в определенном цвете. Таким образом, мы должны изменить материалы для нашей плоскости, сферы и куба как показано ниже:

var planeGeometry = new THREE.PlaneGeometry(60,20);
var planeMaterial = new THREE.MeshLambertMaterial(
	{color: 0xffffff});
var plane = new THREE.Mesh(planeGeometry,planeMaterial);
...
var cubeGeometry = new THREE.CubeGeometry(4,4,4);
var cubeMaterial = new THREE.MeshLambertMaterial(
	{color: 0xff0000});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
...
var sphereGeometry = new THREE.SphereGeometry(4,20,20);
var sphereMaterial = new THREE.MeshLambertMaterial(
	{color: 0x7777ff});
var sphere = new THREE.Mesh(sphereGeometry,sphereMaterial);

В этом участке кода мы изменили свойство материала для наших объектов с помощью функции MeshLambertMaterial. Three.js предоставляет два материала, которые воспринимают источники света: MeshLambertMaterialиMeshPhongMaterial.
Однако, как показано на следующем скриншоте, это все еще не то, к чему мы стремимся:



Куб и сфера получились немного лучше, но пока все еще отсутствуют тени. Визуализация тени занимает много вычислительной мощности и по этой причине тени отключены по умолчанию в Three.js. Но их включение на самом деле очень просто. Для теней мы должны изменить исходники в нескольких местах, как показано в следующем фрагменте кода:

renderer.setClearColorHex(0xEEEEEE, 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMapEnabled = true;

Первое, что мы должны сделать это сказать render, что мы хотим включить тени. Вы можете сделать это, установив свойство shadowMapEnabled в true. Если посмотреть на результат этого действия, то мы ничего не заметим. Это происходит потому что мы должны явно задать какие объекты будут отбрасывать тени и на какие объекты она будет падать. В нашем примере мы хотим, чтобы сфера и куб отбрасывали тень на нашу плоскость. Вы можете сделать это, установив соответствующие свойства в истинное значение:

plane.receiveShadow = true;
...
cube.castShadow = true;
...
sphere.castShadow = true;

Теперь осталась еще одна вещь, которую вы должны сделать, чтобы появились тени. Нам необходимо определить какой из источников света будет вызывать тени. Они не все могут это делать, об этом будет рассказано в следующей части, в нашем же примере мы будем использовать метод SpotLight(). Теперь нам осталось только установить правильное свойство и, наконец, визуализировать тени.

spotLight.castShadow = true;

То, что получилось можно увидеть на следующем скриншоте:



В следующем разделе мы добавим простую анимацию.

Усложним нашу сцену с помощью анимации


Если мы хотим как-то оживить нашу сцену, то первое что мы должны сделать — это найти способ повторно перерисовывать сцену с определенным интервалом. Мы можем сделать это, используя функцию setInterval(function,interval). С помощью этой функции мы можем определить функцию, которая будет вызываться каждые 100 миллисекунд. Проблема этой функции заключается в том, что она не принимает во внимание то, что происходит в браузере. Если вы просматриваете другую вкладку, то эта функция все еще будет вызываться каждые несколько миллисекунд. Кроме того, метод setInterval() не синхронизирован с перерисовкой экрана. Все это может привести к высокой загрузке процессора и низкой эффективности.

Знакомство с методом requestAnimationFrame()


К счастью, у современных браузеров есть решение проблем, связанных с функцией setInterval(): метод requestAnimationFrame(). С помощью этой функции вы можете указать функцию, которая будет вызываться в интервалах, определенных браузером. Вы просто должны создать функцию, которая будет обрабатывать рендеринг, как показано ниже:

function renderScene() {
	requestAnimationFrame(renderScene);
	renderer.render(scene, camera);
}

В функции renderScene() мы снова вызываем метод requestAnimationFrame() для того, чтобы сохранить происходящую анимацию. Единственное, что нам нужно – это изменить место вызова метода renderer.render() после того как мы создали сцену полностью, мы вызываем один раз функцию renderScene(), чтобы начать анимацию:

... 
$("#WebGL-output").append(renderer.domElement);
renderScene();

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

<script type="text/javascript" src="../libs/stats.js"></script>

Так же мы добавим элемент
 , который будет использоваться в качестве выхода статической графики
<div id="Stats-output"></div>

Единственное, что осталось сделать – это инициализировать статику и добавить ее в элемент
, как показано ниже:

function initStats() { var stats = new Stats(); stats.setMode(0); stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px'; stats.domElement.style.top = '0px'; $("#Stats-output").append(stats.domElement ); return stats; }

Эта функция инициализирует статику. Интерес вызывает функция setMode(). Если мы передадим в нее 0, то будем измерять FPS, а если передадим 1, то будет измеряться время рендеринга. Для этого примера нас интересует FPS, поэтому мы передадим 0. В начале нашей безымянной функции jQuery мы будем вызывать эту функцию для включения статики:

$(function () {
	var stats = initStats();
	…
}

И еще одна деталь для функции render():
function render() {
	stats.update();
	...
	requestAnimationFrame(render);
	renderer.render(scene, camera);
}

Если вы запустите код с этими дополнениями, то увидите статику в верхнем левом углу, как показано на следующем скриншоте:



Анимация куба


С помощью метода requestAnimationFrame() и настроенной статики у нас появилась возможность размещать наш анимационный код. В этом разделе мы будем расширять возможности функции render() кодом, который будет вращать наш красный куб вокруг своей оси. Давайте начнем со следующего:

function render() {
	...
	cube.rotation.x += 0.02;
	cube.rotation.y += 0.02;
	cube.rotation.z += 0.02;
	...
	requestAnimationFrame(render);
	renderer.render(scene, camera);
}

Это выглядит просто, не так ли? Все что мы сделали – это увеличили свойство rotation каждой оси на 0,02 каждый раз при вызове функции render(), и куб будет плавно вращаться вокруг своей оси. Сделаем так, чтобы синий шар подскакивал, но это будет немного сложнее.

Подскакивание шара


Для отскока шара мы добавим еще пару строк кода в нашу функцию render() следующим образом:

var step=0;
function render() {
	...
	step+=0.04;
	sphere.position.x = 20+( 10*(Math.cos(step)));
	sphere.position.y = 2 +( 10*Math.abs(Math.sin(step))); 
	...
	requestAnimationFrame(render);
	renderer.render(scene, camera);
}

У куба мы изменили его свойство rotation; для сферы мы намерены изменить ее свойство position на сцене. Мы хотим, чтобы сфера подпрыгнула от одной точки сцены и приземлилась в другой точке сцены, переместившись по некоторой кривой. Для этого мы должны изменить свою позицию по оси х и положение по оси у. Функции Math.cos() и Math.sin() помогают нам в создании гладкой траектории с помощью переменной step. Здесь я не буду вдаваться в подробности как это работает. Пока все что вам нужно знать это то, что step+=0.04 определяет скорость подпрыгивания нашей сферы. На следующем скриншоте показана сцена с включенной анимацией:



Вот ссылка на работающий пример.
Прежде чем закончить эту главу, мне хочется добавить еще один элемент к основной сцене. При работе с 3D-сценой, анимацией, цветами и другими свойствами требуется немного поэкспериментировать, чтобы получить правильный цвет или скорость. Было бы удобно, если бы вы могли иметь простой графический интерфейс, который позволял бы изменять такого рода свойства на лету. К счастью, он есть.

Использование библиотеки dat.GUI делает экспериментирование более простым


Пара ребят из Google создали библиотеку под названием dat.GUI(вы можете найти документацию на нее на сайте ), которая позволяет вам легко создавать компоненты пользовательского интерфейса. В этой части главы мы будем использовать эту библиотеку, добавляя пользовательский интерфейс к нашему примеру, что позволит нам:
  • контролировать скорость вращения прыгающего шара
  • управлять вращением куба

Так же как и для других библиотек, мы сначала добавим dat.GUI в элемент на нашей странице, используя следующий код:

<script type="text/javascript" src="../libs/dat.gui.js"></script>

Следующее что нам необходимо сделать – это сконфигурировать JavaScript объект, который будет анализировать свойства, которые мы хотим изменить, используя библиотеку dat.GUI. В основной части нашего кода мы добавим следующий JavaScript объект:

var controls = new function() {
	this.rotationSpeed = 0.02;
	this.bouncingSpeed = 0.03;
}

В этом объекте мы определим два свойства: this.rotationSpeed и this.bouncingSpeed вместе с их значениями по умолчанию. Далее мы передаем этот объект в новый объект dat.GUI и определяем диапазон этих двух свойств, как показано ниже:

var gui = new dat.GUI();
gui.add(controls, 'rotationSpeed',0,0.5);
gui.add(controls, 'bouncingSpeed',0,0.5);

Свойства rotationSpeed и bouncingSpeed оба установлены в диапазоне от 0 до 0,5. Все, что нам нужно сейчас сделать – это убедиться, что внутри цикла нашей функции render мы ссылаемся на эти два свойства напрямую, так что, когда мы вносим изменения с помощью пользовательского интерфейса dat.GUI, они сразу же влияют на вращение и скорость подскакивания наших объектов. Это делается следующим образом:

function render() {
	...
	cube.rotation.x += controls.rotationSpeed;
	cube.rotation.y += controls.rotationSpeed;
	cube.rotation.z += controls.rotationSpeed;
	step+=controls.bouncingSpeed;
	sphere.position.x = 20+( 10*(Math.cos(step)));
	sphere.position.y = 2 +( 10*Math.abs(Math.sin(step)));
	...
}

Теперь, при запуске нашего примера, вы увидите простой пользовательский интерфейс, который можно использовать для управления скоростью подпрыгивания и вращения объектов:



Вот ссылка на работающий пример.

Использование ASCII эффектов


На протяжении этой главы мы работали над созданием довольно прогнозируемого 3D-рендеринга с использованием самых современных функций браузера. Three.js также имеет несколько интересных функций, которые вы можете использовать для того, чтобы сделать отображение более необычным. Перед тем как закончить эту главу, я хочу познакомить вас с одним из этих эффектов: ASCII эффект. С этим эффектом вы можете изменить нашу анимацию, сделав ее в стиле ретро арт-ASCII, обойдясь всего парой строк кода. Для этого мы должны будем изменить несколько последних строк нашего главного цикла:

$("#WebGL-output").append(renderer.domElement);

На следующие:
var effect = new THREE.AsciiEffect( renderer );
effect.setSize(window.innerWidth, window.innerHeight );
$("#WebGL-output").append(effect.domElement);

Мы также должны будем сделать небольшие изменения в цикле функции рендеринга. Вместо вызова метода renderer.render(scene, camera), необходимо вызвать метод effect.render(scene,camera). В результате всего этого мы получим следующее:



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

Заключение


Ну вот и все для первой главы. В этой главе мы узнали много об основных понятиях, из которых состоит каждая Three.js сцена и это должно послужить хорошей отправной точкой для последующих глав.
В следующей главе мы расширим пример, который был создан в этой главе. Познакомимся более подробно с самыми важными строительными блоками, которые можно использовать в Three.js.
Ну и как полагается, ссылка на исходники: GitHub
Tags:
Hubs:
+47
Comments 20
Comments Comments 20

Articles