Spec-Zone.ru › Web APIs

Точки зрения и зрители: Моделирование камер в WebXR

Первое и самое важное, что нужно понять при рассмотрении кода для управления точкой обзора и камерами в вашем приложении, заключается в следующем: в WebXR нет камер. Нет волшебного объекта, предоставляемого API WebGL или WebXR, который представляет зрителя, которого можно вращать и перемещать, чтобы автоматически изменять отображаемое на экране. В этом руководстве показано, как использовать WebGL для моделирования перемещения камеры без наличия самой камеры. Эти методы можно использовать в любом проекте WebGL (или WebXR).

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

Существуют несколько статей о фундаментальной математике, геометрии и других концепциях, лежащих в основе WebGL и WebXR, которые могут быть полезны для прочтения до или во время чтения этой статьи, включая:

  • Объяснение основных теорий 3D
  • Матричная математика для веб
  • WebGL-модель, представление и проекция
  • Геометрия и пространства отсчета в WebXR

Примечание редактора: Большинство диаграмм, используемых в этой статье для демонстрации перемещения камеры при выполнении стандартных операций, были взяты из статьи на сайте FilmmakerIQ , а именно из этой изображения, которая встречается повсюду в сети. Мы предполагаем, что из-за частого повторного использования они доступны по лицензии с разрешенными условиями использования, но собственность не подтверждена. Надеемся, что это свободно используемый контент; если это не так, и вы являетесь правообладателем, сообщите нам, и мы найдем или создадим новые диаграммы. Или, если вы согласны, чтобы мы продолжали использовать изображения, сообщите нам, чтобы мы могли должным образом вас упомянуть!

Камеры и относительное перемещение

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

Виртуальные камеры

В WebGL (и, по аналогии, в WebXR) нет объекта камеры, который мы можем перемещать и вращать, поэтому нам нужно найти способ имитировать эти движения. Поскольку камеры нет, нам нужно найти способ ее смоделировать. К счастью, физики, такие как Галилей, Ньютон, Лоренц и Эйнштейн, дали нам принцип относительности, который гласит, что законы физики имеют одинаковую форму в любой системе отсчета. То есть, независимо от того, где вы находитесь, законы физики действуют одинаково.

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

И это наше решение. Поскольку мы не можем перемещать камеру, мы перемещаем мир вокруг нее. Нашему рендереру нужно знать, где, по нашему предположению, находится камера, а затем изменить положение каждого видимого объекта, чтобы смоделировать это положение и ориентацию. Таким образом, вместо ссылки на фактический объект камеры в программировании WebGL и WebXR используется термин камера для обозначения объекта, описывающего положение и направление взгляда гипотетического наблюдателя сцены, независимо от того, присутствует ли реальный объект в 3D-пространстве или нет.

Точки зрения

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

Обобщенные камеры

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

  • При отрисовке анимации (будь то для кинопроизводства или использования в презентации или игре) виртуальная камера используется так же, как и реальная кинокамера. По возможности применяются стандартные кинематографические приемы, поскольку зритель, вероятно, вырос, смотря фильмы, использующие эти приемы, и имеет подсознательные ожидания, что фильм или анимация будут следовать этим методам. Отклонение от них может вывести зрителя из сюжета.
  • В бизнес-приложениях 3D-камера используется для задания кажущегося размера и перспективы при визуализации таких элементов, как графики и диаграммы.
  • В приложениях для работы с картами камера может быть размещена непосредственно над сценой или может использовать различные углы для отображения перспективы. В 3D-решениях GPS камера позиционируется так, чтобы показать область вокруг пользователя, при этом большая часть дисплея отображает область перед путем движения пользователя.
  • При использовании WebGL для ускорения отрисовки 2D-графики камера обычно размещается непосредственно над центром сцены, а расстояние и поле зрения устанавливаются так, чтобы вся сцена была представлена.
  • При ускорении отрисовки растровых графических изображений рендерер нарисует 2D-изображение в буфер текстуры WebGL, а затем перерисует текстуру, чтобы обновить экран. Это по сути использует текстуру как буфер заднего плана для реализации буферизации в вашем приложении 2D-графики.

Камеры в играх

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

  • В игре от первого лица камера расположена внутри головы аватара игрока, направленная в ту же сторону, что и глаза аватара. Таким образом, вид на экране игрока или шлеме виртуальной реальности соответствует тому, что видит его аватар.
  • В некоторых играх от третьего лица камера расположена на некотором расстоянии позади аватара или транспортного средства игрока, показывая его со спины, по мере движения по игровому миру. Это используется во многих многопользовательских онлайн-ролевых играх, некоторых шутерах и так далее. Примеры включают World of Warcraft, Tomb Raider и Fortnite. Эта категория также включает игры, в которых камера расположена чуть выше плеча игрока.
  • Некоторые 3D-игры предлагают возможность изменения точки зрения, например, чтобы посмотреть из различных окон самолета в симуляторе полетов или увидеть виды со всех камер наблюдения внутри уровня игры (распространенная функция в шпионских и скрытно-ориентированных играх). Эта возможность также используется играми, предлагающими оружие со сверхдальними прицелами, где вид больше не определяется исключительно положением головы в том же смысле.
  • 3D-игры могут также предложить возможность наблюдения за действием для неигровых персонажей, либо размещая невидимый аватар, либо выбирая неподвижную виртуальную камеру для наблюдения.
  • В продвинутых 3D-играх камера или подобный объект может использоваться для определения того, что видит неигровой персонаж, используя тот же движок отрисовки и физики, что и у игровых персонажей, для неигровых персонажей.
  • В играх с одним экраном и 2D-графикой камера не связана напрямую с игроком или любым другим персонажем в игре, а вместо этого закреплена над или рядом с игровым полем или следует за действиями, когда действия перемещаются по скроллируемому игровому миру. Например, в классической аркадной игре, такой как Pac-Man, происходит на фиксированной игровой карте, поэтому камера остается на фиксированном расстоянии над картой, всегда направленной прямо вниз на игровой мир.
  • В игре со скроллинг-графикой слева направо или сверху вниз, например, в Super Mario Bros., камера перемещается влево и вправо (или вверх и вниз, или то и другое) для обеспечения видимости действий, даже если уровень игры намного больше, чем область просмотра.

Позиционирование камеры

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

Есть ещё один аспект пространственных отношений объекта к началу координат в пространстве: перспектива. Перспектива, правильно применённая к объектам в сцене, может сделать сцену, которая в противном случае выглядела бы плоской, как обычный 2D-экран, объёмной, как если бы она была действительно 3D. Существует несколько видов перспективы; они определены, и их математика объяснена в статье WebGL модель-представление-проекция. Важно, что эффект перспективы на вектор можно представить, добавив четвёртую компоненту к вектору: компоненту перспективы, называемую w.

Значение w применяется путём деления каждой из трёх других компонент на него, чтобы получить конечное положение или вектор; то есть, для координаты, заданной как (x, y, z, w), точка в 3D-пространстве фактически является (x/w, y/w, z/w, 1) или (x/w, y/w, z/w). Если вы не используете перспективу, w всегда равно 1. В этом случае полные координаты объекта, расположенного в точке (1, 0, 3), будут (1, 0, 3, 1).

Но местоположения недостаточно для описания объекта в 3D-пространстве, потому что состояние объекта в пространстве не только о его местоположении, но и о его вращении или направлении взгляда, иначе известном как его ориентация. Ориентацию можно представить с помощью 3D-вектора, который обычно нормируется, так что его длина равна 1,0. Например, если объект смотрит на объект, расположенный в точке (3, 1, -2) — то есть, на три метра вправо, на один метр вверх и на два метра от точки начала координат — результатом будет:

[31-2]\left [ \begin{matrix} 3 \\ 1 \\ -2 \end{matrix} \right ]

Это также можно представить как массив:

let directionVector = [3, 1, -2];

Для выполнения операций, включающих как координаты, так и вектор направления взгляда, вектор должен включать w компонент. Значение w для векторов всегда равно 0, поэтому указанный вектор также можно представить с помощью [3, 1, -2, 0] или:

[31-20]\left [ \begin{matrix} 3 \\ 1 \\ -2 \\ 0 \end{matrix} \right ]

WebXR автоматически нормирует векторы, чтобы их длина составляла 1 метр; однако вы можете обнаружить, что это имеет смысл сделать самостоятельно по разным причинам, например, для повышения производительности вычислений, избегая необходимости повторного нормализации.

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

Вот почему объект XRRigidTransform, используемый WebXR для представления преобразований, включает свойство inverse. Свойство inverse — это другой объект XRRigidTransform, являющийся обратным преобразованием родительского преобразования. Поскольку XRView, представляющий вид, имеет свойство transform, которое является XRRigidTransform, предоставляющим вид камеры, вы можете получить матрицу модель-представление — матрицу преобразования, необходимую для перемещения мира для моделирования желаемого положения камеры — так:

let viewMatrix = view.transform.inverse.matrix;

Если используемая вами библиотека принимает объект XRRigidTransform напрямую, вы можете получить view.transform.inverse, а не просто извлечь массив, представляющий матрицу вида.

Создание нескольких преобразований

Если вашей камере требуется выполнение нескольких преобразований одновременно, например, масштабирование и перемещение в одно и то же время, вы можете умножить матрицы преобразования друг на друга, чтобы объединить их в одну матрицу, которая применит оба изменения одновременно. См. Умножение двух матриц в статье Матричные вычисления для веб для чёткой, но удобочитаемой функции, выполняющей это, или используйте вашу предпочтительную библиотеку матричных вычислений, такую как glMatrix, для выполнения работы.

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

Применение преобразования

Для применения преобразования умножьте точку или вектор на преобразование или композицию преобразований.

Это очень краткий обзор концепций положения с точки зрения физического местоположения, ориентации или направления взгляда и перспективы. Для более подробной информации об этой теме см. статьи Геометрия и справочные пространства, WebGL модель-представление-проекция и Матричные вычисления для веб.

Моделирование классической кинематографии

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

Помните, что большинство движений камеры описываются относительно пространственной системы отсчёта камеры.

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

Таким образом, матрица, которая выглядит так:

[a1a5a9a13a2a6a10a14a3a7a11a15a4a8a12a16]\left [ \begin{matrix} a_{1} & a_{5} & a_{9} & a_{13} \\ a_{2} & a_{6} & a_{10} & a_{14} \\ a_{3} & a_{7} & a_{11} & a_{15} \\ a_{4} & a_{8} & a_{12} & a_{16} \end{matrix} \right ]

представляется в виде массива так:

let matrixArray = [
  a1, a2, a3, a4,
  a5, a6, a7, a8,
  a9, a10, a11, a12,
  a13, a14, a15, a16,
];

В этом массиве самый левый столбец содержит записи a1, a2, a3, и a4. Самая верхняя строка содержит записи a1, a5, a9, и a13.

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

Примечание: Хотя мы используем фразы типа «переместить камеру», на самом деле мы перемещаем весь мир вокруг камеры. Это влияет на работу определенных значений, что будет отмечено, когда они появятся ниже.

Масштабирование

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

Поскольку зум не перемещает положение камеры, полученный эффект неестественен. У человеческого глаза нет зум-объектива. Мы делаем объекты меньше или больше, отдаляясь или приближаясь к ним. В кинематографе это называется кадром долли.

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

Изменение зума путем регулировки поля зрения

Вы можете сделать что-то более похожее на настоящий «зум», изменив поле зрения (FOV) камеры. Поле зрения — это угол, определяющий длину дуги на всей видимой области вокруг камеры, которая должна быть видна одновременно. Это эффект фокусного расстояния в физической камере, поэтому, поскольку настоящей камеры нет, изменение FOV — это приемлемая замена.

Напомним, что окружность круга составляет 2π⋅r радиан (360°); таким образом, это теоретический максимальный FOV. Однако на практике люди видят не так много, а такие устройства, как мониторы и VR-очки, имеют еще меньшее поле зрения. У людей поле зрения обычно составляет около 135° (примерно 2,356 радиан) по горизонтали и около 180° (π или примерно 3,142 радиан) по вертикали.

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

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

function createPerspectiveMatrix(viewport, fovDegrees, nearClip, farClip) {
  const fovRadians = fovDegrees * (Math.PI / 180.0);
  const aspectRatio = viewport.width / viewport.height;

  const transform = mat4.create();
  mat4.perspective(transform, fovRadians, aspectRatio, nearClip, farClip);
  return transform;
}

После преобразования угла FOV, fovDegrees, из градусов в радианы и вычисления соотношения сторон XRViewport, заданного параметром viewport, эта функция использует библиотеку glMatrix glMatrix для вычисления матрицы перспективы с помощью функции mat4.perspective().

Матрица перспективы включает в себя поле зрения (технически, это вертикальное поле зрения), соотношение сторон и ближнюю и дальнюю плоскости обрезания в 4x4 матрице transform, которая затем возвращается вызывающей стороне.

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

Для зумирования с помощью коэффициента масштабирования или процента вы можете отобразить 1x (100% от нормального размера) на наибольшее значение FOV, которое вы разрешаете (что приводит к максимальному отображению контента), а затем отобразить максимальное увеличение на максимальное значение FOV, которое вы поддерживаете, и отобразить соответствующие значения между ними.

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

const transform = createPerspectiveMatrix(viewport, 130, 1, 100);
const translateVec = vec3.fromValues(
  -trackDistance,
  -craneDistance,
  pushDistance,
);
mat4.translate(transform, transform, translateVec);

Это начинается с матрицы перспективы, представляющей вертикальное поле зрения 130°, а затем применяется преобразование смещения, перемещающее камеру таким образом, что оно включает слежение, кран и толкание движений.

Масштабирующие преобразования

В отличие от истинного «зума», масштабирование включает умножение каждого из x, y, и z координатных значений в позиции или вершине на коэффициент масштабирования для этой оси. Эти значения могут или не могут быть идентичны для каждой оси, хотя самый близкий результат эффекта зума будет получен при использовании одного и того же значения для каждой. Это необходимо применить ко всем вершинам в сцене — в идеале в шейдере вершин.

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

let scaleTransform = [
  Sx, 0, 0, 0,
  0, Sy, 0, 0,
  0, 0, Sz, 0,
  0, 0, 0, 1
];

Эта матрица представляет преобразование, которое увеличивает или уменьшает масштаб с помощью коэффициента, указанного в (Sx, Sy, Sz), где Sx указывает коэффициент масштабирования по оси X, Sy — коэффициент масштабирования по оси Y, а Sz — коэффициент для оси Z. Если любое из этих значений отличается от других, результат будет искажением или сжатием, которое отличается по некоторым измерениям от других.

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

function createScalingMatrix(f) {
  return [f, 0, 0, 0, 0, f, 0, 0, 0, 0, f, 0, 0, 0, 0, 1];
}

С матрицей преобразования в руках мы применяем преобразование scaleTransform к вектору (или вершине) myVector.

let myVector = [2, 1, -3];
let scaleTransform = [2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1];
vec4.transformMat4(myVector, myVector, scaleTransform);

Или, используя масштабирование по каждой оси на один и тот же коэффициент, используя функцию createScalingMatrix() , показанную выше:

let myVector = [2, 1, -3];
vec4.transformMat4(myVector, myVector, createScalingMatrix(2.0));

Панорамирование (наклонение влево или вправо)

Панорамирование или поворот — это вращение камеры влево или вправо, при этом ее основание остается на месте. Положение камеры в пространстве не меняется, меняется только направление, в котором она смотрит. И это направление не меняется, кроме как по горизонтали. Панорамирование отлично подходит для установления сцены или создания чувства масштаба в обширном пространстве или на обширном объекте. Или просто для поворота влево и вправо, например, для моделирования поворота головы игрока в интерактивной или VR-среде.

A diagram showing a camera panning left or right

Для этого нам нужно повернуть вокруг оси Y, чтобы смоделировать поворот камеры влево и вправо. Используя библиотеку glMatrix, которую мы использовали ранее, это можно сделать с помощью метода rotateY() в классе mat4, который представляет стандартную 4x4 матрицу. Для поворота точки обзора, определенной матрицей viewMatrix, на panAngle радиан:

mat4.rotateY(viewMatrix, viewMatrix, panAngle);

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

Наклон (наклонение вверх или вниз)

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

A diagram showing a camera tilting up and down

Таким образом, наклон камеры можно получить путем вращения камеры вокруг оси X, чтобы она поворачивалась вверх и вниз. Это можно сделать с помощью соответствующего метода в вашей библиотеке для работы с матрицами, например, метода rotateX() в классе mat4 glMatrix:

mat4.rotateX(viewMatrix, viewMatrix, angle);

Положительные значения для angle наклонят камеру вниз, а отрицательные значения для angle — вверх.

Доллинг (движение вперед или назад)

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

A diagram showing how a camera moves for a dolly shot

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

Для выполнения операции долли переместите точку обзора камеры вперед и назад вдоль оси Z:

mat4.translate(viewMatrix, viewMatrix, [0, 0, dollyDistance]);

Здесь [0, 0, dollyDistance] — вектор, где dollyDistance — это расстояние для перемещения камеры. Поскольку это работает путем перемещения всего мира вокруг камеры, то, что происходит на самом деле, заключается в том, что весь мир перемещается вдоль оси Z на dollyDistance метра относительно камеры. Если dollyDistance положительно, мир перемещается к пользователю на это расстояние, что приводит к приближению камеры к сцене. И наоборот, отрицательные значения dollyDistance перемещают мир от пользователя, заставляя камеру, кажется, двигаться назад от цели.

Тракинг (перемещение влево или вправо)

Перевозка с использованием физической камеры использует тот же вид монтажа, что и доллинг, но вместо перемещения камеры вперёд и назад, она перемещается влево или вправо и наоборот. Камера совсем не вращается, поэтому фокус кадра медленно сходит со экрана. Это может указывать на концентрацию, течение времени или размышления при попытке установить эмоции в сцене. Также часто используется в сценах «ходьба и разговор», где камера движется рядом с персонажами, и они проходят по сцене.

A diagram showing how a camera trucks left and right

Чтобы переместить камеру влево и вправо, необходимо выполнить перевод матрицы вида вдоль оси X в направлении, противоположном желаемому движению камеры:

mat4.translate(viewMatrix, viewMatrix, [-truckDistance, 0, 0]);

Обратите внимание на вектор [-truckDistance, 0, 0]. Это компенсирует тот факт, что работа с грузовиком осуществляется путём перемещения мира, а не камеры. Перемещая весь мир в направлении, противоположном указанному в truckDistance, мы достигаем эффекта перемещения камеры в нужном направлении. Таким образом, положительные значения truckDistance будут перемещать камеру вправо (перемещая мир влево), а отрицательные значения truckDistance будут перемещать камеру влево, перемещая мир вправо.

Подъём (перемещение вверх или вниз)

Кадр подъёма — это кадр, в котором камера закреплена по горизонтали относительно пола, но перемещается прямо вверх или вниз. Представьте камеру на подставке (или стойке), которая становится выше или ниже. Это полезно при отслеживании объекта, который становится выше или ниже, или встаёт или садится с стула, или движется прямо вверх или вниз.

A diagram showing a camera moving up and down using a pedestal motion

Это похоже на кадр крана, который включает в себя перемещение камеры, закреплённой на кране, вверх и вниз. Для выполнения движения подъёма или движения краном необходимо выполнить перевод вида вдоль оси Y в направлении, противоположном направлению движения камеры:

mat4.translate(viewMatrix, viewMatrix, [0, -pedestalDistance, 0]);

Путем отрицания значения pedestalDistance, мы компенсируем тот факт, что мы на самом деле перемещаем мир, а не камеру. Таким образом, положительные значения pedestalDistance будут перемещать камеру вверх, а отрицательные — вниз.

Наклёнивание (перекатывание влево и вправо)

Наклон (или перекатывание) — это вращение камеры вокруг оси перекатывания; то есть камера остаётся неподвижной в пространстве и остаётся направленной на то же место, но вращается так, что верх камеры направлен в другом направлении.

A diagram showing a camera rolling left and right

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

Для выполнения этого вращения вокруг оси Z с помощью glMatrix:

mat4.rotateZ(viewMatrix, viewMatrix, cantAngle);

Комбинирование движений

Вы можете выполнять несколько движений одновременно, например, зуммирование при панорамировании или наклонение и наклёнивание одновременно.

Перевод вдоль нескольких осей

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

mat4.translate(viewMatrix, viewMatrix, [-truckDistance, 0, 0]);
mat4.translate(viewMatrix, viewMatrix, [0, -pedestalDistance, 0]);
mat4.translate(viewMatrix, viewMatrix, [0, 0, dollyDistance]);

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

mat4.translate(viewMatrix, viewMatrix, [
  -truckDistance,
  -pedestalDistance,
  dollyDistance,
]);

Это сдвинет начало матрицы viewMatrix на указанное количество единиц вдоль каждой оси.

Вращение вокруг нескольких осей

Вы также можете объединить вращения вокруг нескольких осей в одно вращение вокруг кватерниона, представляющего общую ось вращений. Для выполнения вращений по отдельности вы используете углы Эйлера (отдельные углы вокруг каждой оси), чтобы применить тангаж, рыскание и крен следующим образом:

mat4.rotateX(viewMatrix, viewMatrix, pitchAngle);
mat4.rotateY(viewMatrix, viewMatrix, yawAngle);
mat4.rotateZ(viewMatrix, viewMatrix, rollAngle);

Вместо этого вы можете создать кватернион, представляющий объединённую ось вращения из углов Эйлера, а затем повернуть матрицу с помощью умножения, как это:

const axisQuat = quat.create();
const rotateMatrix = mat4.create();
quat.fromEuler(axisQuat, pitchAngle, yawAngle, rollAngle);
mat4.fromQuat(rotateMatrix, axisQuat);
mat4.multiply(viewMatrix, viewMatrix, rotateMatrix);

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

Представление 3D с помощью WebXR

WebXR продвигает 3D графику дальше, позволяя представлять её с помощью специального визуального оборудования, такого как очки или шлем виртуальной реальности, чтобы создать 3D графику, которая, кажется, реально существует в трёх измерениях, потенциально в контексте реального мира (в случае дополненной реальности).

Для восприятия глубины необходимо иметь две точки зрения на сцену. Сравнивая два вида, можно определить глубину объектов и, соответственно, расстояние между наблюдателем и объектами, которые видны. Вот почему у нас есть два глаза, немного разнесённых. Вы можете напомнить себе об этом, закрывая по очереди один глаз, переключаясь между двумя глазами. Обратите внимание, как ваш левый глаз может видеть левую сторону вашего носа, но не правую, а ваш правый глаз видит правую сторону носа, но не левую. Это всего лишь одно из многих различий между тем, что видит каждый из ваших глаз.

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

Отрисовка сцены

Устройство XR (аббревиатура, охватывающая как виртуальную реальность (VR), так и дополненную реальность (AR)) отображает 3D изображения, рисуя два вида сцены, немного смещённые друг относительно друга, так же, как и изображения, полученные нашими двумя глазами. Эти виды затем по отдельности передаются каждому глазу, чтобы позволить им собрать данные, необходимые нашему мозгу для построения 3D изображения в наших умах.

Для этого WebXR просит ваш рендерер нарисовать сцену дважды для каждого кадра видео — один раз для каждого глаза. Два вида отображаются в одном буфере кадров, один слева, а другой справа. Устройство XR затем использует экраны и линзы, чтобы отобразить левую половину полученного изображения на наш левый глаз, а правую половину — на наш правый глаз.

Например, рассмотрим устройство, которое использует буфер кадров размером 2560x1440 пикселей. Разделив его на две части — по половине на каждый глаз — мы получим изображение каждого глаза с разрешением 1280x1440 пикселей. Вот как это выглядит концептуально:

Diagram showing how a framebuffer is divided between two eyes' viewpoints

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

Эта информация включает XRViewerPose, описывающий положение и направление взгляда зрителя в сцене, а также список XRView объектов, каждый из которых представляет одну точку зрения на сцену. В текущих реализациях WebXR в этом списке никогда не будет более двух элементов: один описывает положение и угол обзора левого глаза, а другой — то же самое для правого. Вы можете определить, какой глаз представлен данным XRView, проверив значение его свойства eye, которое является строкой со значением left или right (теоретически может использоваться третье возможное значение none, чтобы представить другую точку зрения, но поддержка этого не полностью доступна в текущем API).

Пример функции обратного вызова кадра

Довольно базовая (но типичная) функция обратного вызова для отрисовки кадров может выглядеть так:

function myAnimationFrameCallback(time, frame) {
  const adjustedRefSpace = applyPositionOffsets(xrReferenceSpace);
  const pose = frame.getViewerPose(adjustedRefSpace);

  animationFrameRequestID = frame.session.requestAnimationFrame(
    myAnimationFrameCallback,
  );

  if (pose) {
    const glLayer = frame.session.renderState.baseLayer;
    gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer);
    CheckGLError("Binding the framebuffer");

    gl.clearColor(0, 0, 0, 1.0);
    gl.clearDepth(1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    CheckGLError("Clearing the framebuffer");

    const deltaTime = (time - lastFrameTime) * 0.001;
    lastFrameTime = time;

    for (const view of pose.views) {
      const viewport = glLayer.getViewport(view);
      gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
      CheckGLError(`Setting viewport for eye: ${view.eye}`);

      myRenderScene(gl, view, sceneData, deltaTime);
    }
  }
}

Функция обратного вызова начинается с вызова пользовательской функции applyPositionOffsets(), которая принимает ссылку на пространство и применяет к её матрице преобразований любые необходимые изменения, учитывая такие факторы, как пользовательский ввод с устройств, не управляемых WebXR, таких как клавиатура и мышь. Полученное изменённое XRReferenceSpace пространство затем передаётся в метод XRFrame getViewerPose() для получения XRViewerPose, представляющего положение и угол обзора зрителя.

Далее мы ставим в очередь запрос на отрисовку следующего кадра видео, чтобы не беспокоиться об этом позже, вызвав requestAnimationFrame() ещё раз.

Теперь пришло время отрисовать сцену. Если мы успешно получили позицию, мы получаем XRWebGLLayer, необходимую для отрисовки, из свойства baseLayer объекта renderState сессии. Мы привязываем это к целевому объекту WebGL gl.FRAMEBUFFER с помощью метода WebGLRenderingContext gl.bindFrameBuffer().

Затем мы очищаем буфер кадра, чтобы гарантировать, что мы начинаем с известного состояния, так как наш рендерер не будет затрагивать каждый пиксель. Мы устанавливаем цвет очистки в непрозрачный чёрный с помощью gl.clearColor() и значение для очистки буфера глубины в 1.0, вызвав метод WebGLRenderingContext gl.clearDepth(). Затем мы вызываем метод WebGLRenderingContext gl.clear(), который очищает буфер кадра (поскольку мы включаем gl.COLOR_BUFFER_BIT в параметр маски) и буфер глубины (поскольку мы включаем gl.DEPTH_BUFFER_BIT).

Затем мы определяем, сколько времени прошло с момента отрисовки предыдущего кадра, сравнивая желаемое время отрисовки кадра с временем отрисовки последнего кадра. Поскольку это значение в миллисекундах, мы преобразуем его в секунды, умножив на 0,001 (или разделив на 1000).

Теперь мы проходим циклом по взглядам позиции, которые находятся в массиве XRViewerPose views. Для каждого взгляда мы запрашиваем у XRWebGLLayer соответствующий viewport, настраиваем viewport WebGL, передавая информацию о позиции и размере в gl.viewport(). Это ограничивает отрисовку так, что мы можем отрисовать только в ту часть буфера кадра, которая представляет изображение, видимое глазом, идентифицированным с помощью view.eye.

Установив эти ограничения и подготовив всё необходимое, мы вызываем пользовательскую функцию myRenderScene(), чтобы фактически выполнить вычисления и отрисовку WebGL для рендеринга кадра. В этом случае мы передаём контекст WebGL, gl, XRView view, объект sceneData (который содержит такие вещи, как вершинные и фрагментные шейдеры, списки вершин, текстуры и так далее) и deltaTime, которое указывает, сколько времени прошло с момента предыдущего кадра, чтобы мы знали, насколько продвигать анимацию.

Когда эта функция возвращает результат, в используемом WebGL буфере кадра WebXR теперь находятся две копии сцены, каждая из которых занимает половину кадра: одна для левого глаза, а другая для правого глаза. Это передаётся через программное обеспечение и драйверы XR в гарнитуру, где каждая половина отображается соответствующему глазу.

См. также

  • Геометрия и пространства ссылок
  • WebGL модель-представление-проекция
  • Матричная математика для веб
  • Движение, ориентация и движение: пример WebXR

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API/Cameras

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API