|
@@ -0,0 +1,323 @@
|
|
|
+Title: Основы Three.js
|
|
|
+Description: Твой первый урок по Three.js начинаетсся с основ
|
|
|
+
|
|
|
+Это первая статья в серии статей о three.js.
|
|
|
+[Three.js](http://threejs.org) это 3D-библиотека, которая максимально
|
|
|
+упрощает создание 3D-контента на веб-странице.
|
|
|
+
|
|
|
+Three.js часто путают с WebGL, поскольку чаще всего,
|
|
|
+но не всегда, three.js использует WebGL для рисования 3D.
|
|
|
+[WebGL - это очень низкоуровневое api, рисующее только точки, линии и треугольники](https://webglfundamentals.org).
|
|
|
+Чтобы сделать что-нибудь полезное с WebGL, как правило, требуется немало кода,
|
|
|
+и именно здесь приходит Three.js. Он обрабатывает такие вещи, как сцены,
|
|
|
+источники света, тени, материалы, текстуры, 3D-математику, все,
|
|
|
+что вам нужно было бы написать самостоятельно, если бы вы использовали WebGL напрямую.
|
|
|
+
|
|
|
+В этих руководствах предполагается, что вы уже знаете JavaScript,
|
|
|
+и по большей части они будут использовать стандарт ES6+. [Смотрите здесь
|
|
|
+краткий список вещей, которые вы, как ожидается, уже знаете](threejs-prerequisites.html).
|
|
|
+Большинство браузеров, которые поддерживают three.js,
|
|
|
+обновляются автоматически, поэтому большинство пользователей
|
|
|
+должны иметь возможность запускать этот код. Если вы хотите,
|
|
|
+чтобы этот код запускался в действительно старых браузерах,
|
|
|
+посмотрите на транспайлер, такой как [Babel](http://babel.io).
|
|
|
+Конечно, пользователи, использующие действительно старые браузеры,
|
|
|
+вероятно, имеют машины, которые не могут запускать three.js.
|
|
|
+
|
|
|
+При изучении большинства языков программирования первое, что делают люди,
|
|
|
+это заставляют компьютер напечатать `"Hello World!"`. Для 3D одна из самых
|
|
|
+распространенных задач - создать 3D-куб, так что давайте начнем с `"Hello Cube!"`
|
|
|
+
|
|
|
+Первое, что нам нужно, это тэг `<canvas>`:
|
|
|
+
|
|
|
+```
|
|
|
+<body>
|
|
|
+ <canvas id="c"></canvas>
|
|
|
+</body>
|
|
|
+```
|
|
|
+
|
|
|
+Three.js будет рисовать на этом холсте, так что нам нужно найти
|
|
|
+его и передать three.js.
|
|
|
+
|
|
|
+```
|
|
|
+<script>
|
|
|
+'use strict';
|
|
|
+
|
|
|
+/* global THREE */
|
|
|
+
|
|
|
+function main() {
|
|
|
+ const canvas = document.querySelector('#c');
|
|
|
+ const renderer = new THREE.WebGLRenderer({canvas: canvas});
|
|
|
+ ...
|
|
|
+</script>
|
|
|
+```
|
|
|
+
|
|
|
+Обратите внимание, что здесь есть некоторые не явные детали.
|
|
|
+Если вы не передадите холст в three.js, библиотека создаст его за вас,
|
|
|
+но затем нужно будет добавить его в DOM. Место добавления
|
|
|
+может меняться в зависимости от вашего варианта использования,
|
|
|
+и вам придется изменить свой код, поэтому я считаю, что передача canvas
|
|
|
+в three.js выглядит немного более гибкой. Я могу поместить холст где угодно,
|
|
|
+и код найдет его там, как если бы у меня был код для вставки холста в документ,
|
|
|
+и мне, вероятно, пришлось бы изменить этот код, если бы изменился мой вариант
|
|
|
+использования.
|
|
|
+
|
|
|
+Когда канвас найден, мы создаем `WebGLRenderer`. Renderer - это то, что отвечает
|
|
|
+за фактическое получение всех предоставленных вами данных и их отрисовку
|
|
|
+на холст. В прошлом были другие рендеры, такие как `CSSRenderer`,
|
|
|
+`CanvasRenderer`, а в будущем могут быть
|
|
|
+`WebGL2Renderer` или `WebGPURenderer`. На данный момент есть `WebGLRenderer`,
|
|
|
+который использует WebGL для рисования 3D на холсте.
|
|
|
+
|
|
|
+Далее нам нужна камера.
|
|
|
+
|
|
|
+```
|
|
|
+const fov = 75;
|
|
|
+const aspect = 2; // значение для canvas по умолчанию
|
|
|
+const near = 0.1;
|
|
|
+const far = 5;
|
|
|
+const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
|
|
|
+```
|
|
|
+
|
|
|
+`fov` сокращение от `field of view`, [поле зрения](https://en.wikipedia.org/wiki/Field_of_view). В этом случае 75 градусов в
|
|
|
+вертикальном измерении. Обратите внимание, что большинство углов в Three.js
|
|
|
+указаны в радианах, но по какой-то причине перспективная камера принимает градусы.
|
|
|
+
|
|
|
+`aspect` это [соотношение сторон холста](https://ru.wikipedia.org/wiki/Соотношение_сторон_экрана) (англ. aspect ratio). Мы рассмотрим детали
|
|
|
+в другой статье, но по умолчанию холст имеет размер 300x150 пикселей,
|
|
|
+значит соотношение сторон 300/150 или 2.
|
|
|
+
|
|
|
+`near` и `far` представляют пространство перед камерой, которое будет отображаться.
|
|
|
+Все, что находится до или после этого диапазона, будет обрезано (не нарисовано).
|
|
|
+
|
|
|
+Эти 4 параметра определяют [усеченную пирамиду](https://ru.wikipedia.org/wiki/Усечённая_пирамида) *"frustum"*. *Frustum* это
|
|
|
+название 3D фигуры, напоминающей пирамиду с отсеченной верхушкой. Другими словами,
|
|
|
+думайте о слове "frustum" как о трехмерной фигуре,
|
|
|
+такой как сфера, куб и призма.
|
|
|
+
|
|
|
+<img src="../resources/frustum-3d.svg" width="500" class="threejs_center"/>
|
|
|
+
|
|
|
+Высота ближней и дальней плоскостей определяется полем зрения (field of view).
|
|
|
+Ширина обеих плоскостей определяется полем зрения и соотношением сторон (aspect).
|
|
|
+
|
|
|
+Все, что находится внутри определенного усеченного контура, будет нарисовано.
|
|
|
+Снаружи ничего не будет.
|
|
|
+
|
|
|
+По умолчанию камера смотрит вниз по оси -Z и вверх по оси +Y. Мы поместим наш куб
|
|
|
+в начало координат (origin), поэтому нам нужно немного отодвинуть камеру назад,
|
|
|
+чтобы что-то увидеть.
|
|
|
+
|
|
|
+```
|
|
|
+camera.position.z = 2;
|
|
|
+```
|
|
|
+
|
|
|
+Вот как мы её направили.
|
|
|
+
|
|
|
+<img src="../resources/scene-down.svg" width="500" class="threejs_center"/>
|
|
|
+
|
|
|
+На диаграмме выше мы видим, что наша камера находится в `z = 2`. И смотрит вниз по оси -Z.
|
|
|
+Усеченная пирамида начинается с 0.1 единицы спереди камеры и до 5 единиц перед камерой.
|
|
|
+ Our canvas is twice as wide
|
|
|
+as it is tall so across view the field of view will be much wider than
|
|
|
+our specified 75 degrees which is the vertical field of view.
|
|
|
+Поскольку на этой диаграмме мы смотрим вниз, поле зрения (fov) зависит от отношения
|
|
|
+сторон (aspect). Так как ширина холста в 2 раза больще высоты, при просмотре поле обзора
|
|
|
+будет намного шире, чем указанные нами 75 градусов, которые являются вертикальным
|
|
|
+полем зрения.
|
|
|
+
|
|
|
+Далее создадим `Scene`. `Scene` в three.js корень формы графа сцены.
|
|
|
+Все, что вы хотите нарисовать необходимо добавить на сцену. Мы рассмотрим подробнее,
|
|
|
+[как работают сцены, в следующей статье](threejs-scenegraph.html).
|
|
|
+
|
|
|
+```
|
|
|
+const scene = new THREE.Scene();
|
|
|
+```
|
|
|
+
|
|
|
+Далее мы создаем `BoxGeometry` который содержит данные для [прямоугольного параллелепипеда](https://ru.wikipedia.org/wiki/Прямоугольный_параллелепипед).
|
|
|
+Почти все, что мы хотим отобразить в Three.js, нуждается в геометрии,
|
|
|
+которая определяет вершины нашего трехмерного объекта.
|
|
|
+
|
|
|
+```
|
|
|
+const boxWidth = 1;
|
|
|
+const boxHeight = 1;
|
|
|
+const boxDepth = 1;
|
|
|
+const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
|
|
|
+```
|
|
|
+
|
|
|
+Затем мы создаем основной материал и устанавливаем его цвет.
|
|
|
+Цвета могут быть определены с использованием 6-значных шестнадцатеричных
|
|
|
+значений цвета, как в CSS.
|
|
|
+
|
|
|
+```
|
|
|
+const material = new THREE.MeshBasicMaterial({color: 0x44aa88});
|
|
|
+```
|
|
|
+
|
|
|
+Затем мы создаем [полигональную сетку](https://ru.wikipedia.org/wiki/Полигональная_сетка)
|
|
|
+`Mesh`. `Mesh` в three.js представляет комбинацию
|
|
|
+формы объекта `Geometry` и `Material` (как нарисовать объект,
|
|
|
+блестящий или плоский, какой цвет, какую текстуру(ры) применить и т.д.)
|
|
|
+а также положение, ориентацию, и масштаб этого объекта в сцене.
|
|
|
+
|
|
|
+```
|
|
|
+const cube = new THREE.Mesh(geometry, material);
|
|
|
+```
|
|
|
+
|
|
|
+И, наконец, мы добавляем `Mesh` на сцену
|
|
|
+
|
|
|
+```
|
|
|
+scene.add(cube);
|
|
|
+```
|
|
|
+
|
|
|
+Затем мы можем отрендерить сцену, вызвав функцию `render` рендерера
|
|
|
+передав ей сцену и камеру.
|
|
|
+
|
|
|
+```
|
|
|
+renderer.render(scene, camera);
|
|
|
+```
|
|
|
+
|
|
|
+Вот рабочий пример
|
|
|
+
|
|
|
+{{{example url="../threejs-fundamentals.html" }}}
|
|
|
+
|
|
|
+Трудно сказать, что это 3D-куб, так как мы видим его непосредственно по оси
|
|
|
+-Z, а сам куб выровнен по этой оси, поэтому мы видим только одну грань.
|
|
|
+
|
|
|
+Давайте оживим его, и, надеюсь, это прояснит, что он рисуется в 3D. Для его
|
|
|
+анимации мы будем отрисовывать внутри цикла отрисовки, используя
|
|
|
+[`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
|
|
|
+
|
|
|
+Вот наш цикл
|
|
|
+
|
|
|
+```
|
|
|
+function render(time) {
|
|
|
+ time *= 0.001; // конвертировать время в секунды
|
|
|
+
|
|
|
+ cube.rotation.x = time;
|
|
|
+ cube.rotation.y = time;
|
|
|
+
|
|
|
+ renderer.render(scene, camera);
|
|
|
+
|
|
|
+ requestAnimationFrame(render);
|
|
|
+}
|
|
|
+requestAnimationFrame(render);
|
|
|
+```
|
|
|
+
|
|
|
+`requestAnimationFrame` это запрос к браузеру, что вы хотите что-то анимировать.
|
|
|
+Вы передаете ему функцию для вызова. В нашем случае эта функция `render`.
|
|
|
+Браузер вызовет вашу функцию, и если вы обновите что-либо, связанное с
|
|
|
+отображением страницы, браузер выполнит перерисовку страницы.
|
|
|
+В нашем случае мы вызываем `renderer.render`, которая нарисует нашу сцену.
|
|
|
+
|
|
|
+`requestAnimationFrame` передает время с момента загрузки страницы в нашу функцию.
|
|
|
+Это время приходит в миллисекундах. Я считаю, что работать с секундами намного проще,
|
|
|
+поэтому здесь мы конвертируем время в секунды.
|
|
|
+
|
|
|
+Затем мы устанавливаем вращение куба по X и Y на текущее время. Эти повороты в
|
|
|
+[радианах](https://ru.wikipedia.org/wiki/Радиан). В круге 2 пи радиана,
|
|
|
+поэтому наш куб должен повернуться вокруг каждой оси примерно за 6.28
|
|
|
+секунд.
|
|
|
+
|
|
|
+Затем мы отрисовываем сцену и запрашиваем еще один кадр анимации,
|
|
|
+чтобы продолжить наш цикл.
|
|
|
+
|
|
|
+Вне цикла мы вызываем `requestAnimationFrame` один раз, чтобы запустить цикл.
|
|
|
+
|
|
|
+{{{example url="../threejs-fundamentals-with-animation.html" }}}
|
|
|
+
|
|
|
+Это немного лучше, но все еще трудно увидеть 3d. Что может помочь, так это
|
|
|
+добавить немного освещения, поэтому давайте добавим источник света.
|
|
|
+В Three.js есть много разных источников света, о которых мы поговорим в
|
|
|
+следующей статье. А пока давайте создадим направленный свет.
|
|
|
+
|
|
|
+```
|
|
|
+{
|
|
|
+ const color = 0xFFFFFF;
|
|
|
+ const intensity = 1;
|
|
|
+ const light = new THREE.DirectionalLight(color, intensity);
|
|
|
+ light.position.set(-1, 2, 4);
|
|
|
+ scene.add(light);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Направленные источники имеет положение и цель. Оба по умолчанию равны 0, 0, 0. В нашем
|
|
|
+случае мы устанавливаем положение источника света на -1, 2, 4 чтобы оно было немного слева,
|
|
|
+сверху и позади нашей камеры. Цель по-прежнему 0, 0, 0, поэтому они будут светить
|
|
|
+в направлении начала координат.
|
|
|
+
|
|
|
+Нам также нужно изменить материал. `MeshBasicMaterial` не воспреимчив к свету.
|
|
|
+Давайте изменим его на `MeshPhongMaterial`, который отражает свет.
|
|
|
+
|
|
|
+```
|
|
|
+-const material = new THREE.MeshBasicMaterial({color: 0x44aa88}); // greenish blue
|
|
|
++const material = new THREE.MeshPhongMaterial({color: 0x44aa88}); // greenish blue
|
|
|
+```
|
|
|
+
|
|
|
+И вот оно работает.
|
|
|
+
|
|
|
+{{{example url="../threejs-fundamentals-with-light.html" }}}
|
|
|
+
|
|
|
+Теперь должно быть довольно четко видно 3D.
|
|
|
+
|
|
|
+Просто для удовольствия добавим еще 2 кубика.
|
|
|
+
|
|
|
+Мы будем использовать одну и ту же геометрию для каждого куба, но
|
|
|
+создадим другой материал, чтобы каждый куб мог иметь свой цвет.
|
|
|
+
|
|
|
+Сначала мы сделаем функцию, которая создает новый материал с указанным цветом.
|
|
|
+Затем создает mesh, используя указанную геометрию, добавляет ее к сцене и
|
|
|
+устанавливает ей позицию X.
|
|
|
+
|
|
|
+```
|
|
|
+function makeInstance(geometry, color, x) {
|
|
|
+ const material = new THREE.MeshPhongMaterial({color});
|
|
|
+
|
|
|
+ const cube = new THREE.Mesh(geometry, material);
|
|
|
+ scene.add(cube);
|
|
|
+
|
|
|
+ cube.position.x = x;
|
|
|
+
|
|
|
+ return cube;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Затем мы будем вызывать его 3 раза с 3 разными цветами и позициями X,
|
|
|
+сохраняя экземпляры `Mesh` в массив.
|
|
|
+
|
|
|
+```
|
|
|
+const cubes = [
|
|
|
+ makeInstance(geometry, 0x44aa88, 0),
|
|
|
+ makeInstance(geometry, 0x8844aa, -2),
|
|
|
+ makeInstance(geometry, 0xaa8844, 2),
|
|
|
+];
|
|
|
+```
|
|
|
+
|
|
|
+Наконец, мы закрутим все 3 куба в нашей функции отрисовки.
|
|
|
+Мы рассчитываем немного разные коэффициенты вращения для каждого.
|
|
|
+
|
|
|
+```
|
|
|
+function render(time) {
|
|
|
+ time *= 0.001; // конвертировать время в секунды
|
|
|
+
|
|
|
+ cubes.forEach((cube, ndx) => {
|
|
|
+ const speed = 1 + ndx * .1;
|
|
|
+ const rot = time * speed;
|
|
|
+ cube.rotation.x = rot;
|
|
|
+ cube.rotation.y = rot;
|
|
|
+ });
|
|
|
+
|
|
|
+ ...
|
|
|
+```
|
|
|
+
|
|
|
+и вот оно.
|
|
|
+
|
|
|
+{{{example url="../threejs-fundamentals-3-cubes.html" }}}
|
|
|
+
|
|
|
+Если вы сравните его с диаграммой сверху вниз, вы увидите, что она соответствует
|
|
|
+нашим ожиданиям. С кубами в X = -2 и X = +2 они частично находятся вне нашей
|
|
|
+усеченной пирамиды. Они также несколько искривлены, так как
|
|
|
+поле зрения на холсте очень велико.
|
|
|
+
|
|
|
+Я надеюсь, что это короткое вступление поможет вам начать изучение.
|
|
|
+[Далее мы рассмотрим, как сделать отзывчивый дизайн, чтобы код можно было применять
|
|
|
+к различным ситуациям](threejs-responsive.html).
|
|
|
+
|