Одна нога в Three.js

three.js
Одна нога в Three.js

предисловие

Как говорится: ни мотания, ни фронтенда. Не занимайтесь WebGL, а чем отличается соленая рыба!

На официальном языке: Three.js — 3D-библиотека Javascript.

Давайте сегодня познакомимся с концепциями дизайна и идеями Three.js.

Декартова правая система координат

При создании 3D мы должны сначала понять его основные принципы: трехмерную систему координат.

Все мы знаем, что в CSS3 трехмерное пространство — это левосторонняя система координат. (Если вы не понимаете, вы можете прочитать статью, которую я написал ранее«Трехмерное преобразование в CSS3»)

Но в Three.js наше пространство представлено в правосторонней декартовой системе координат. следующим образом:

Поняв систему координат, мы можем создать нужную сцену в этом трехмерном пространстве.

Создать сцену

Если вы хотите использовать трехмерное пространство, вы должны сначала открыть контейнер трехмерного пространства. Чтобы открыть трехмерное пространство, вам нужно только создать экземпляр объекта THREE.Scene.

var scene = new THREE.Scene();

Сцена — это трехмерное пространство, в котором можно размещать объекты, камеры и источники света.Как и во вселенной, здесь нет границ, нет света и есть бесконечная тьма.

Компоненты сцены можно условно разделить на три категории: камеры, источники света и объекты.

Прежде чем мы перейдем к компонентам Thee.js, давайте взглянем на фотографию:

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

тема

Прежде чем использовать тему, давайте объясним шаблон проектирования создания темы с помощью Three.js:

First Three.js разбивает любой объект на маленькие треугольники. Будь то двухмерная графика или трехмерная графика, треугольники могут использоваться как наименьшая единица структуры. Структура представляет собой сетку нашего предмета.

Ниже представлена ​​структура сетки двумерной плоскости:

Ниже показана трехмерная структура сферической сетки:

Вы можете видеть, что треугольник — это наименьшая единица деления в Three.js. Это структура сетки.

Конечно, иметь сетчатую структуру недостаточно. Так же, как человеческое тело, поскольку сетчатая структура подобна скелету, ему также нужны материалы на его поверхности. Материал — это оболочка объекта, определяющая внешний вид геометрии.

Геометрия

В Three.js для нас предустановлено множество геометрических сеточных структур:

  • 2D:

    • THREE.PlaneGeometry(Плоскость)

      Эта геометрия была показана ранее.

    • THREE.CircleGeometry(круг)

    • THREE.RingGeometry(Кольцо)

  • трехмерный

    • THREE.BoxGeometry (Кубоид)

    • THREE.SphereGeometry (сфера)

      Эта геометрия была показана ранее.

    • THREE.CylinderGeometry(Цилиндр)

    • ТРИ.Тор (кольцо)

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

В частности, в качестве примера возьмем создание экземпляра куба:

var geometry = new THREE.BoxGeometry(4, 4, 4);

Здесь мы сначала объявляем и создаем экземпляр объекта BoxGeometry. При создании объекта мы устанавливаем длину, ширину и высоту равными 4 соответственно.

Создается такой куб. Но иметь такую ​​сетчатую структуру недостаточно. Следующим шагом будет добавление текстур к нему.

Материал

В материальном компоненте Three.js также предустановляет несколько материальных объектов, здесь мы кратко представим два наиболее часто используемых:

  1. MeshBasicMaterial

    Этот материал является основным материалом Three.js. Используется для придания геометрической сетке простого цвета или для отображения структуры геометрической сетки. (Он может отображаться даже при отсутствии источника света.)

  2. MeshLambertMaterial

    Это материал, учитывающий световые эффекты. Используется для создания тусклых, неосвещенных объектов.

Стоит отметить, что мы можем складывать несколько материалов в одну и ту же сетчатую структуру.

Здесь мы используем MeshBasicMaterial и MeshLambertMaterial, чтобы подготовить два разных материала для куба, который мы создали ранее:

var geometryMeshBasicMaterial = new THREE.MeshBasicMaterial({
  color: 0xff0000,
  wireframe: true
});
var geometryMeshLambertMaterial = new THREE.MeshLambertMaterial({
  color: 0x242424
});

Свойство каркаса, если установлено значение true, будет отображать материал как толщину линии. Фоторамки можно рассматривать как замаскированные линии сетки. Например, каркас куба выглядит следующим образом:

Сетка

Когда у нас есть геометрическая сетка и материал, нам нужно объединить их, чтобы создать объект, который мы снимаем.

Здесь мы вводим два разных метода построения объекта:

  • new THREE.Mesh(geometry, material)
  • THREE.SceneUtils.createMultiMaterialObject(geometry,[materials...])

Оба являются методами создания объектов, и первый параметр — это геометрическая модель (Geometry), разница только во втором параметре. Первые могут создавать объекты только из одного материала, вторые могут создаваться из нескольких материалов (передавать массив из нескольких материалов).

Здесь мы создадим мультиматериальный предмет.

var cube = THREE.SceneUtils.createMultiMaterialObject(geometry, [
  geometryMeshBasicMaterial,
  geometryMeshLambertMaterial
]);

Теперь, когда у нас есть объект, нам нужно добавить его в сцену, точно так же, как мы снимаем товар, нам нужно разместить наш товар в съемочной площадке.

В Three.js добавление объектов на сцену можно выполнить напрямую, вызвав метод добавления объекта сцены. Конкретная реализация выглядит следующим образом:

scene.add(cube);

Мы передаем объект, который хотим добавить, в метод add(), который может быть одним или несколькими, разделенными запятыми.

источник света

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

В Three.js источники света разделены на несколько типов, и далее мы кратко представим наиболее часто используемые из них.

  1. THREE.AmbientLight

    Это основной источник света, который будет накладываться на цвета существующих объектов в сцене.

    Этот источник света не имеет определенного направления и не создает теней.

    Мы часто используем его вместе с другими источниками света, чтобы смягчить тени или добавить дополнительный цвет сцене.

  2. THREE.SpotLight

    Этот источник света имеет эффект прожектора, подобный настольным лампам, фонарикам и сценическим прожекторам.

    Этот источник света может отбрасывать тени.

  3. THREE.DirectionalLight

    Этот источник света, также называемый бесконечным светом, подобен солнечному свету.

    Световые лучи от этого источника света можно рассматривать как параллельные.

    Этот источник света также может отбрасывать тени.

В нашем примере мы будем использовать SpotLight для создания света.

Прежде всего, нам нужно создать экземпляр объекта SpotLight, как обычные объекты съемки ранее, и использовать шестнадцатеричное значение цвета в качестве параметра в качестве цвета нашего света.

var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(0, 20, 20);
spotLight.intensity = 5;
scene.add(spotLight);

После того, как у нас есть объект источника света, мы вызовем метод position.set(), чтобы установить положение в 3D-пространстве.

Свойство интенсивности используется для установки интенсивности освещения источника света, значение по умолчанию равно 1.

Наконец, мы также должны поместить источник света в пространство нашей сцены. Это дает нашей сцене SpotLight.

камера

В THREE.js есть два типа камер:

  • THREE.PerspectiveCamera (перспективная камера)

    В соответствии со здравым смыслом ближнего большого и дальнего малого. Визуализируйте сцену с перспективой, близкой к реальному миру.

  • THREE.OrthographicCamera (ортографическая камера)

    Обеспечивает псевдотрехмерный эффект.

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

Здесь я сосредоточусь на PerspectiveCamera:

Сначала посмотрим на картинку:

Для перспективной камеры нам необходимо установить следующие параметры:

  • fov (поле зрения)竖直方向Открытый угол (в градусах, а не в радианах)
  • Аспект (соотношение сторон) — это соотношение горизонтальной и вертикальной длины камеры
  • вблизи (ближнее расстояние до лица) ближайшее расстояние от камеры до объема просмотра
  • дальнее (дальнее расстояние) самое дальнее расстояние от камеры до поля зрения
  • зум

Здесь мы также создадим собственную перспективную камеру.

var camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.x = 5;
camera.position.y = 10;
camera.position.z = 10;
camera.lookAt(cube.position);

Во-первых, когда мы инстанцировали перспективный объект камеры, мы передавали ему несколько параметров: угол раскрытия по вертикали равен 75 градусам, соотношение сторон такое же, как у окна, а самое близкое и самое дальнее расстояние от камеры до тела зрителя соответственно 0,1 и 1000.

Наконец, мы заставляем камеру смотреть на положение куба объекта камеры, который мы создали ранее, вызывая метод lookAt(). (По умолчанию камера будет указывать на исходную точку трехмерной системы координат.)

Рендерер

С объектами выше мы в нескольких шагах от успеха.

Читая заголовок этого раздела, вы можете спросить: что такое рендерер?

С точки зрения непрофессионала: то, что мы делаем на камеру, — это негатив, а не реальная фотография. Нетрудно понять, если вас все еще впечатляют винтажные камеры.

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

Можно сказать, что это роль рендерера — проявка фотографий. Помните, что когда мы раньше задавали параметры камеры, мы задавали не ширину и высоту камеры, а только соотношение сторон камеры. Это похоже на наш негатив, который небольшой, но показывает основное соотношение сторон нашей фотографии.

Способ, которым мы создаем визуализатор, такой же, как и другие объекты в THREE, нам нужно сначала создать экземпляр объекта.

Three.js предоставляет нам несколько разных рендереров, здесь мы будем использовать рендерер THREE.WebGLRenderer в качестве примера.

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.render(scene, camera);
  • Мы устанавливаем отображаемый размер и ширину, вызывая метод setSize().
  • Элемент domElement рендерера представляет холст в рендерере, и весь рендеринг рисуется на domElement, поэтому appendChild здесь означает присоединение domElement под телом, чтобы результат рендеринга можно было отобразить на странице.
  • Передача нашей сцены и камеры в метод render() эквивалентна передаче фильма со сценой, захваченной камерой, который отобразит изображение на нашем холсте.

На этом этапе вы получите вот такую ​​форму:

Здесь мы добавляем объект системы координат для удобства наблюдения.

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

var axes = new THREE.AxisHelper(7);
scene.add(axes);

Здесь длина оси нашей системы координат установлена ​​на 7.

В это время вы обнаружите, что это изображение все еще статично, а трехмерные характеристики не проявляются полностью.

Анимация

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

Понимание циклов событий

Механизм работы асинхронного выполнения следующий:

  1. Все задачи синхронизации выполняются в основном потоке, образуя стек контекста выполнения.
  2. Помимо основного потока, существует еще «очередь задач» (task queue). Пока выполняются условия выполнения асинхронной задачи, она находится в «очереди задач».放置一个事件.
  3. Как только все задачи синхронизации в «стеке выполнения» будут выполнены, система прочитает «очередь задач», чтобы увидеть, какие события в ней находятся. Затем соответствующие асинхронные задачи завершают состояние ожидания, входят в стек выполнения и начинают выполнение.

Основной поток продолжает повторять третий шаг выше. Основной поток считывает события из «очереди задач», и этот процесс цикличен, поэтому весь механизм работы также называется Event Loop. Пока основной поток пуст, он будет читать «очередь задач», как работает JavaScript. Этот процесс повторяется снова и снова.

Принцип анимации

На самом деле анимация — это иллюзия, создаваемая серией картинок, воспроизводимых с определенной частотой в течение определенного периода времени.

Важной особенностью глаза является зрительная инерция, т. е. как только на сетчатке образуется световой образ, зрение будет поддерживать восприятие этого светового образа в течение ограниченного времени, это физиологическое явление называется зрительной инерционностью. Для умеренно ярких световых стимулов постоянство зрения составляет примерно 0,1–0,4 с.

Чтобы анимация переходила последовательно и плавно, мы обычно визуализируем анимацию со скоростью 60 кадров в секунду или выше.

Почему бы не реализовать анимацию с помощью setInterval()?

  • Время выполнения setInterval() не является детерминированным. В Javascript задача setInterval() помещается в асинхронную очередь, и только после того, как задача в основном потоке будет выполнена, она проверит, нужно ли начинать выполнение задачи в очереди, поэтомуФактическое время выполнения setInterval() обычно позже установленного времени.
  • setInterval() может установить только фиксированный интервал времени, который не обязательно совпадает со временем обновления экрана.

Две вышеупомянутые ситуации приведут к тому, что темп выполнения setInterval() будет несовместим с темпом обновления экрана, что приведет квыпадающая рамкаФеномен. Так почему же непостоянный темп вызывает выпадение кадров?

Первое, что нужно понять, это то, что выполнение setInterval() изменяет только свойства изображения в памяти, и это изменение не должно быть обновлено на экране до следующего обновления экрана. Если темп двух несовместим, это может привести к пропуску операции определенного кадра в середине и непосредственному обновлению изображения следующего кадра. Если предположить, что экран обновляется каждые 16,7 мс (60 кадров), а setInterval() устанавливает перемещение изображения на 1 пиксель влево каждые 10 мс, будет происходить следующий процесс рисования:

  • 0 мс: экран не обновляется, ожидание, setInterval() не выполнено, ожидание;
  • 10 мс: экран не обновляется, ожидание, setInterval() начинает выполняться и устанавливает свойство изображения left=1px;
  • 16,7 мс: экран начинает обновляться, и изображение на экране перемещается влево1px, setInterval() не выполняется и продолжает ждать;
  • 20 мс: экран не обновляется, ожидание, setInterval() начинает выполняться и устанавливает left=2px;
  • 30 мс: экран не обновляется, ожидание, setInterval() начинает выполняться и устанавливает left=3px;
  • 33,4 мс: экран начинает обновляться, и изображение на экране перемещается влево3px, setInterval() не выполняется и продолжает ждать;

Из приведенного выше процесса рисования видно, что экран не обновляет кадр слева=2 пикселя, а изображение переходит непосредственно из положения 1 пикселя в положение 3 пикселя, что является явлением потери кадра, которое вызывает анимация заморозить. .

requestAnimationFrame()

Преимущества requestAnimationFrame()

По сравнению с setInterval() самым большим преимуществом requestAnimationFrame() является то, что система определяет время выполнения функции обратного вызова. **В частности, если частота обновления экрана составляет 60 кадров, то функция обратного вызова выполняется каждые 16,7 мс.Если частота обновления составляет 75 Гц, то временной интервал становится равным 1000/75=13,3 мс, другими словами, темп requestAnimationFrame() следует за темпом обновления системы.Это гарантирует, что функция обратного вызова выполняется только один раз в каждом интервале обновления экрана., так что это не приведет к пропущенным кадрам или зависанию анимации.

Кроме того, requestAnimationFrame() имеет следующие два преимущества:

  • Энергосбережение процессора: Для анимаций, реализованных с помощью setInterval(), когда страница скрыта или свернута, setInterval() по-прежнему выполняет задачи анимации в фоновом режиме. Поскольку в это время страница невидима или недоступна, обновление анимации бессмысленно и является пустой тратой ресурсов процессора. Ресурсы. Функция requestAnimationFrame() полностью отличается. Когда обработка страницы не активирована, задача обновления экрана страницы также будет приостановлена ​​системой, поэтому requestAnimationFrame(), который следует за системой, также остановит рендеринг. Когда страница активирована , анимация продолжает выполнение с того места, где оно было остановлено в прошлый раз, эффективно снижая нагрузку на ЦП.

  • регулирование функции: в высокочастотных событиях (изменение размера, прокрутка и т. д.), чтобы предотвратить выполнение нескольких функций в интервале обновления, используйте requestAnimationFrame(), чтобы гарантировать, что функция выполняется только один раз в каждом интервале обновления, что не только обеспечивает беглость , что также может лучше сократить накладные расходы на выполнение функции. Для функции нет смысла выполнять несколько раз в течение интервала обновления, потому что дисплей обновляется каждые 16,7 мс, и многократные отрисовки не будут отображаться на экране.

Как работает requestAnimationFrame():

Давайте сначала посмотрим на исходный код Chrome:

int Document::requestAnimationFrame(PassRefPtr<RequestAnimationFrameCallback> callback)
{
  if (!m_scriptedAnimationController) {
    m_scriptedAnimationController = ScriptedAnimationController::create(this);
    // We need to make sure that we don't start up the animation controller on a background tab, for example.
      if (!page())
        m_scriptedAnimationController->suspend();
  }

  return m_scriptedAnimationController->registerCallback(callback);
}

Более пристальный взгляд показывает, что базовая реализация на удивление проста: создается экземпляр ScriptedAnimationController для хранения зарегистрированного события, а затем регистрируется обратный вызов.

Принцип реализации requestAnimationFrame очевиден:

  • зарегистрировать функцию обратного вызова
  • Когда браузер обновляется с определенной частотой кадров, он запускает все зарегистрированные обратные вызовы.

Рабочий механизм здесь можно понимать как передачу права собственности.Власть за время, которое запускает обновление кадра, передается ядру браузера, чтобы идти в ногу с обновлением браузера. Это позволит не только избежать асинхронности между обновлением браузера и обновлением кадра анимации, но и даст браузеру достаточно места для оптимизации.

Создание анимации с помощью requestAnimationFrame()

Нам нужно создать функцию рендеринга цикла и вызвать ее:

// a render loop
function render() {
  requestAnimationFrame(render);

  // Update Properties

  // render the scene
  renderer.render(scene, camera);
}

Мы обновляем и визуализируем соответствующие свойства внутри тела функции и позволяем браузеру управлять обновлением кадра анимации.

сделать анимацию

Здесь мы создадим нашу анимацию через requestAnimationFrame(). Предоставление браузеру возможности контролировать обновление кадров анимации больше всего улучшило нашу производительность.

var animate = function() {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
};
animate();

В методе animate() мы используем requestAnimationFrame(animate), чтобы браузер вызывал метод анимации при каждом обновлении страницы. И каждый раз при его вызове свойства куба соответственно изменяются: каждый вызов поворачивает на 0,01 радиана по осям X и Y предыдущего вызова и отрисовывает его на холсте.

Итак, наша анимация сгенерирована:

THREE.Цвет объекта

Здесь я добавляю описание встроенных цветовых объектов в Three.js.

Обычно мы можем использовать шестнадцатеричную строку ("#000000") или шестнадцатеричное значение (0x000000) для создания объекта указанного цвета. Мы также могли бы использовать значения цвета RGB для создания (0,2, 0,3, 0,4), но стоит отметить, что каждое значение находится в диапазоне от 0 до 1.

Например:

var color = new THREE.Color(0x000000);

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

Имя функции описывать
set(value) Устанавливает текущий цвет в указанное шестнадцатеричное значение. Значение может быть строкой, числом или существующим экземпляром THREE.Color.
setHex(value) Устанавливает текущий цвет в указанное шестнадцатеричное числовое значение.
setRGB(r,g,b) Устанавливает цвет в соответствии с предоставленным значением RGB. Диапазон параметров от 0 до 1.
setHSL(h,s,l) Устанавливает цвет в соответствии с предоставленным значением HSL. Диапазон параметров от 0 до 1.
setStyle(style) Установите цвет в соответствии с тем, как CSS устанавливает цвет. Например: «rgb(25, 0, 0)», «#ff0000», «#ff» или «красный».
copy(color) Копирует значения цвета из предоставленного объекта цвета в текущий объект.
getHex() Получите значение цвета из объекта цвета в виде шестнадцатеричного значения: 435241.
getHexString() Получите значение цвета из объекта цвета в виде шестнадцатеричной строки: «0c0c0c».
getStyle() Получите значение цвета из объекта цвета в виде значения css: «rgb (112, 0, 0)»
getHSL(optionalTarget) Получите значение цвета из объекта цвета как значение HSL. Если предоставлен объект optionTarget, Three.js установит свойства h, s и l для этого объекта.
toArray Возвращает массив из трех элементов: [r,g,b].
clone() Дублировать текущий цвет.

Суммировать

Вы можете сказать, что:

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

Постоянно изменяя свойства наших объектов и постоянно рисуя нашу сцену, мы создаем анимацию!

С исходным кодом

<html>
  <head>
    <title>Cube</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }

      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>

  <body>
    <script src="https://cdn.bootcss.com/three.js/r83/three.min.js"></script>
    <script>
      var scene = new THREE.Scene();

      var axes = new THREE.AxisHelper(7);
      scene.add(axes);

      var geometry = new THREE.BoxGeometry(4, 4, 4);
      var geometryMeshBasicMaterial = new THREE.MeshBasicMaterial({
        color: 0xff0000,
        wireframe: true
      });
      var geometryMeshLambertMaterial = new THREE.MeshLambertMaterial({
        color: 0x242424
      });
      var cube = THREE.SceneUtils.createMultiMaterialObject(geometry, [
        geometryMeshBasicMaterial,
        geometryMeshLambertMaterial
      ]);
      scene.add(cube);

      var spotLight = new THREE.SpotLight(0xffffff);
      spotLight.position.set(0, 20, 20);
      spotLight.intensity = 5;
      scene.add(spotLight);

      var camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      camera.position.x = 5;
      camera.position.y = 10;
      camera.position.z = 10;
      camera.lookAt(cube.position);

      var renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      var animate = function() {
        requestAnimationFrame(animate);
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
        renderer.render(scene, camera);
      };
      animate();
    </script>
  </body>
</html>

-EFO-


Автор специально создал хранилище на github для записи навыков, трудностей и ошибок при обучении разработке с полным стеком.Вы можете щелкнуть ссылку ниже, чтобы просмотреть. Если вы думаете, что это не плохо, пожалуйста, дайте ему маленькую звезду! 👍


2019/04/14

AJie