Title: Основы Three.js Description: Твой первый урок по Three.js начинаетсся с основ TOC: Базовые принципы {{{warning msgId="updateNeeded"}}} Это первая статья в серии статей о 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://babeljs.io). Конечно, пользователи, использующие действительно старые браузеры, вероятно, имеют машины, которые не могут запускать three.js. При изучении большинства языков программирования первое, что делают люди, это заставляют компьютер напечатать `"Hello World!"`. Для 3D одна из самых распространенных задач - создать 3D-куб, так что давайте начнем с `"Hello Cube!"` Первое, что нам нужно, это тэг ``: ``` ``` Three.js будет рисовать на этом холсте, так что нам нужно найти его и передать three.js. ``` ``` Обратите внимание, что здесь есть некоторые не явные детали. Если вы не передадите холст в 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" как о трехмерной фигуре, такой как сфера, куб и призма. Высота ближней и дальней плоскостей определяется полем зрения (field of view). Ширина обеих плоскостей определяется полем зрения и соотношением сторон (aspect). Все, что находится внутри определенного усеченного контура, будет нарисовано. Снаружи ничего не будет. По умолчанию камера смотрит вниз по оси -Z и вверх по оси +Y. Мы поместим наш куб в начало координат (origin), поэтому нам нужно немного отодвинуть камеру назад, чтобы что-то увидеть. ``` camera.position.z = 2; ``` Вот как мы её направили. На диаграмме выше мы видим, что наша камера находится в `z = 2`. И смотрит вниз по оси -Z. Усеченная пирамида начинается с 0.1 единицы спереди камеры и до 5 единиц перед камерой. Поскольку на этой диаграмме мы смотрим вниз, поле зрения (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).