Простая 3D-проекция и управление ориентацией?

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

Как преобразовать простые позиционные векторы в моем игровом мире в двумерные векторы на экране с учетом следующей информации:

Расположение камер ориентация камер (см. ниже) поле зрения высота и ширина экрана (и соотношение сторон)

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

Есть ли хорошие источники по реализации кватернионов в коде? Придется ли мне писать отдельную библиотеку для комплексных чисел?

Спасибо за ваше время и любую помощь :)


person Fascia    schedule 27.09.2011    source источник
comment
Вы уверены, что хотите сделать это в Lua? Не было бы разумнее передать это OpenGL или что-то в этом роде?   -  person Nicol Bolas    schedule 28.09.2011
comment
Какое это вообще имеет отношение к Lua?   -  person lhf    schedule 28.09.2011
comment
Независимо от того, что вы в конечном итоге делаете, математика, стоящая за 3D-ориентацией и перспективой, не проста.   -  person kikito    schedule 28.09.2011
comment
Я делаю это на Lua, думаю, это не имело отношения к рассматриваемому вопросу (в частности, Love Engine). У меня нет доступа к привязке opengl в love 2d, и, честно говоря, я хотел бы знать точные функции, лежащие в основе того, что происходит в любом случае, для моего личного просветления.   -  person Fascia    schedule 28.09.2011
comment
LÖVE имеет открытый исходный код, поэтому вы можете просто прочитать, что он делает.   -  person lhf    schedule 28.09.2011
comment
У любви нет 3D-библиотек, поэтому я хочу исправить свою собственную. Я уже написал простую векторную библиотеку.   -  person Fascia    schedule 28.09.2011
comment
Вы можете использовать кватернионы. Так что да, следите за мировой трансформацией центра плюс вращение кватерниона, и все в порядке. так что вы можете просто выполнить матричное преобразование в положениях X, Y, Z, но в конечном итоге вы будете использовать кватернион для применения преобразования вращения. Для получения дополнительной информации см. это рэп-видео youtube.com/watch?v=B0aMwrtEliY   -  person FlavorScape    schedule 14.03.2012


Ответы (1)


ВНИМАНИЕ! ДЛИННЫЙ ОТВЕТ!

Я сделал аналогичный проект в Love2D, и он работает очень быстро, поэтому я не вижу проблемы в самостоятельном выполнении математических расчетов в Lua, а не в OpenGL (который в любом случае не отображается).

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

Для ориентации кватернионы, вероятно, излишни. Я обнаружил, что для создания 3D-проекции с вращением нужны только классы Vec2, Vec3 и Camera. Хотя математически есть несколько тонких отличий, на практике Векторы Векторов создают идеально подходящие матрицы преобразования, а матрицы преобразования создают идеально подходящие ориентации. Матрица, являющаяся вектором векторов, имеет то преимущество, что вам нужно написать только один класс для обработки обоих.

Для проецирования вектора v учитывайте 3 параметра камеры:

  • loc, Vec3 для положения камеры
  • trans, a Mat3by3 (also known as a Vec3 of Vec3's) for the inverse of the camera's orientation
    • (disclaimer: using matrices for camera orientation is technically considered harmful, because small rounding errors can accumulate, but in actual use it's fine)
  • zoom, коэффициент масштабирования, используемый для определения перспективы. z (относительно камеры) из zoom эквивалентно нахождению в 2D; то есть без масштабирования с точки зрения.

проекция работает так:

function Camera:project(v)
    local relv -- v positioned relative to the camera, both in orientation and location
    relv = self.trans * (v - self.loc) -- here '*' is vector dot product
    if relv.z > 0 then
        -- v is in front of the camera
        local w -- perspective scaling factor
        w = self.zoom / relv.z
        local projv -- projected vector
        projv = Vec2(relv.x * w, relv.y * w)
        return projv
    else
        -- v is behind the camera
        return nil
    end
end

это предполагает, что Vec2(0, 0) соответствует центру окна, а не углу. Установка этого — простой перевод.

trans должно начинаться как единичная матрица: Vec3(Vec3(1, 0, 0), Vec3(0, 1, 0), Vec3(0, 0, 1)) и вычисляться постепенно, делая небольшие корректировки каждый раз, когда производится изменение ориентации.

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

function Vec3.dot(a, b) return a.x * b.x + a.y + b.y + a.z * b.z end

для матрицы

Vec3(axis1, axis2, axis3)

учитывая определение скалярного произведения, эта матрица, усеянная вектором v, даст

axis1 * v.x + axis2 * v.y + axis3 * v.z

это означает, что первый элемент v указывает, на сколько axis1 нужно пройти, второй элемент говорит, на сколько axis2 нужно пройти, а третий элемент говорит, на сколько axis3 нужно пройти, с конечным результатом, равным v, если его выразить в стандартных координатах вместо координат матрицы. Когда мы умножаем матрицу на вектор, мы меняем значение компонентов вектора. По сути, это математическое выражение утверждения типа «все, что было справа, теперь меньше вправо и больше вперед» или что-то подобное. В одном предложении матрица преобразует пространство.

Возвращаясь к поставленной задаче, чтобы представить вращение в «тангаже» (имеется в виду вокруг оси x) на угол theta с использованием матрицы, вы можете написать:

function pitchrotation(theta)
    return Vec3(
        -- axis 1
        -- rotated x axis
        -- we're rotating *around* the x axis, so it stays the same
        Vec3(
            1,
            0,
            0
        ),
        -- axis 2
        -- rotated y axis
        Vec3(
            0,
            math.cos(theta),
            math.sin(theta)
        ),
        -- axis 3
        -- rotated z axis
        Vec3(
            0,
            -math.sin(theta),
            math.cos(theta)
        )
    )
end

и для "рысканья" (вокруг оси Y):

function yawrotation(theta)
    return Vec3(
        -- axis 1
        -- rotated x axis
        Vec3(
            math.cos(theta),
            0,
            math.sin(theta)
        ),
        -- axis 2
        -- rotated y axis
        -- we're rotating *around* the y axis, so it stays the same
        Vec3(
            0,
            1,
            0
        ),
        -- axis 3
        -- rotated z axis
        Vec3(
            -math.sin(theta),
            0,
            math.cos(theta)
        )
    )
end

и, наконец, «катиться» (вокруг оси Z), что особенно полезно в авиасимуляторах:

function rollrotation(theta)
    return Vec3(
        -- axis 1
        -- rotated x axis
        Vec3(
            math.cos(theta),
            math.sin(theta),
            0
        ),
        -- axis 2
        -- rotated y axis
        Vec3(
            -math.sin(theta),
            math.cos(theta),
            0
        ),
        -- axis 3
        -- rotated z axis
        -- we're rotating *around* the z axis, so it stays the same
        Vec3(
            0,
            0,
            1
        )
    )
end

Если вы представите в уме, что это делает с осями x, y и z, все эти косинусы, синусы и перестановки знаков могут начать обретать смысл. Они все там по какой-то причине.

Наконец, мы подошли к последнему шагу головоломки — применению этих поворотов. Приятной особенностью матриц является то, что их легко составлять. Вы можете очень легко трансформировать трансформацию - вы просто трансформируете каждую ось! Чтобы преобразовать существующую матрицу A в матрицу B:

function combinematrices(a, b)
    return Vec3(b * a.x, b * a.y, b * a.z) -- x y and z are the first second and third axes
end

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

function Camera:rotateyaw(theta)
    self.trans = combinematrices(self.trans, yawrotation(-theta))
end

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

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

Отсутствует одна часть, которая действительно не может быть выполнена без OpenGL, — это тестирование глубины. Если вы рисуете что-либо, кроме точек каркаса, нет хорошего способа убедиться, что все рисуется в правильном порядке. Вы можете сортировать, но это неэффективно, и он не обрабатывает некоторые крайние случаи, когда вы должны делать это попиксельно, что и делает OpenGL.

Удачного кодирования! Надеюсь, это было полезно!

person SelectricSimian    schedule 02.01.2013