ВНИМАНИЕ! ДЛИННЫЙ ОТВЕТ!
Я сделал аналогичный проект в 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