Вивчаємо Three.js.Глава 1: Створюємо нашу першу 3D-сцену, використовуючи Three.js

      Всім привіт!
Хочу почати вольний переклад чудової книги «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);
	varplaneGeometry = new THREE.PlaneGeometry(60,20,1,1);
	varplaneMaterial = 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);
	varcubeGeometry = new THREE.CubeGeometry(4,4,4);
	varcubeMaterial = 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);
	varsphereGeometry = new THREE.SphereGeometry(4,20,20);
	varsphereMaterial = 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);
varplaneGeometry = new THREE.PlaneGeometry(60,20);
varplaneMaterial = 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 дуже просто і здійснюється в значній мірі так само, як ми це робили в попередньому розділі.
 
 
varspotLight = new THREE.SpotLight( 0xffffff );
spotLight.position.set( -40, 60, -10 );
scene.add(spotLight );

Метод
SpotLight()
висвітлює нашу сцену з позиції
spotLight.position.set( -40, 60, -10 )
. Якщо ви отрендеріть сцену ще раз, то помітите, що немає ніякої різниці з попереднім прикладом. Причина в тому, що різні матеріали по-різному реагують на світло. Основний матеріал, який ми використовували в попередньому прикладі для об'єктів (за допомогою методу
MeshBasicMaterial
()) ніяк не реагує на джерела світла на сцені. Вони просто візуалізували об'єкти в певному кольорі. Таким чином, ми повинні змінити матеріали для нашої площині, сфери та куба як показано нижче:
 
 
varplaneGeometry = new THREE.PlaneGeometry(60,20);
varplaneMaterial = new THREE.MeshLambertMaterial(
	{color: 0xffffff});
var plane = new THREE.Mesh(planeGeometry,planeMaterial);
...
varcubeGeometry = new THREE.CubeGeometry(4,4,4);
varcubeMaterial = new THREE.MeshLambertMaterial(
	{color: 0xff0000});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
...
varsphereGeometry = new THREE.SphereGeometry(4,20,20);
varsphereMaterial = 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
(). За допомогою цієї функції ви можете вказати функцію, яка буде викликатися в інтервалах, визначених браузером. Ви просто повинні створити функцію, яка буде обробляти рендеринг, як показано нижче:
 
 
functionrenderScene() {
	requestAnimationFrame(renderScene);
	renderer.render(scene, camera);
}

У функції
renderScene
() ми знову викликаємо метод
requestAnimationFrame
() для того, щоб зберегти відбувається анімацію. Єдине, що нам потрібно — це змінити місце виклику методу
renderer.render()
після того як ми створили сцену повністю, ми викликаємо один раз функцію
renderScene
(), щоб почати анімацію:
 
 
... 
$("#WebGL-output").append(renderer.domElement);
renderScene();

Якщо ви запустите даний фрагмент коду, то ви не побачите ніяких змін у порівнянні з попереднім прикладом, тому що ми поки ще нічого не анімували. Перш ніж ми додамо анімацію, мені б хотілося представити невелику допоміжну бібліотеку, яка дає нам інформацію про частоту кадрів, з якою працює анімація. Все що нам знадобиться для її підключення — це прописати в елементі
<head>
наступний рядок коду:
 
 
<script type="text/javascript" src="../libs/stats.js"></script>

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

Єдине, що залишилося зробити — це ініціалізувати статику і додати її в елемент
<div>
, як показано нижче:
 
 
functioninitStats() {
	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 );
	returnstats;
}

Ця функція ініціалізує статику. Інтерес викликає функція
setMode
(). Якщо ми передамо в неї 0, то будемо вимірювати FPS, а якщо передамо 1, то буде вимірюватися час рендеринга. Для цього прикладу нас цікавить FPS, тому ми передамо 0. На початку нашої безіменній функції jQuery ми будемо викликати цю функцію для включення статики:
 
 
$(function () {
	var stats = initStats();
	…
}

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

Якщо ви запустите код з цими доповненнями, то побачите статику у верхньому лівому кутку, як показано на наступному скріншоті:
 
 
 
 

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

За допомогою методу
requestAnimationFrame
() і налаштованої статики у нас з'явилася можливість розміщувати наш анімаційний код. У цьому розділі ми будемо розширювати можливості функції
render
() кодом, який буде обертати наш красний куб навколо своєї осі. Давайте почнемо з наступного:
 
 
functionrender() {
	...
	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 і визначаємо діапазон цих двох властивостей, як показано нижче:
 
 
vargui = 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
  
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.