{{notification.text}}

MirGames

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

Итак, что такое камера в 3D игре? Это виртуальное “око” игрока, то, посредством чего он воспринимает игру визуально. В понятие “камера” входят: угол обзора и положение которое задаётся радиус вектором и 3 углами относительно осей координат.

Реализация простой камеры

Один из самых простых методов выглядит так:

procedure TCamera.SetRender;
begin
   gluLookAt(e.X, e.Y, e.Z, c.X, c.Y, c.Z, Up.X, Up.Y, Up.Z);
end;

В процедуру gluLookAt передаётся всего 3 радиус-вектора:

  • e – точка в которую обращена камера
  • c – положение камеры в пространстве
  • Up – указывает направление “вверх” для камеры.

По-поводу первых двух надеюсь вопросов нет, но вот с вычислением третьего придётся изрядно попотеть…

Однако, зачем что-то вычислять, если это можно доверить графическому API?

procedure TCamera.SetRender;
begin
   glLoadIdentity;
   glRotatef(Angle.Z, 0, 0, 1);
   glRotatef(Angle.X, 1, 0, 0);
   glRotatef(Angle.Y, 0, 1, 0);
   glTranslatef(-Pos.X, -Pos.Y, -Pos.Z);
end;
  • Angle – вектор описывающий углы поворота относительно каждой из осей координат (в градусах)
  • Pos – положение камеры в пространстве, также задающееся радиус-вектором.

Данный метод бесспорно и прост и удобен, но такие операции как glRotate и glTranslate производят умножение видовой матрицы на другую матрицу. Это конечно же не критично для современных компьютеров, но всё же вполне оптимизируемо. Чем мы и займёмся…

Для того чтобы что-либо оптимизировать мы должны понять принцип работы всех трёх операций.

Познаём суть операций

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

Нам же достаточно работы с матрицей вида (MODELVIEW). Сама же матрица вида представляется 16 вещественными числами, т.е. матрица имеет размерность 4х4.

Итак, разберём все операции из предыдущего примера по отдельности:

glLoadIdentity

Преобразует текущую (видовую, проекции, текстуры) матрицу в единичную:

glRotatef

Домножает текущую матрицу вида на матрицу поворота относительно одной из осей координат.

Имея 3 оси координат, соответственно можно вычислить всего 3 матрицы поворота:

  • Относительно оси OX:
  • Относительно оси OY:
  • Относительно оси OZ:

где s и c – соответственно синусы и косинусы угла поворота.

glTranslatef

Производит домножение матрицы вида на матрицу сдвига:

где x, y, z – приращение к соответствующим координатам векторов в новой системе координат.

Расчёт матрицы для камеры

Итак, с сутью операций разобрались, теперь можно приступить к оптимизации, которая будет заключаться в ручном вычислении матрицы вида!

Для этого нам понадобятся 3 угла и позиция камеры.

Нам необходимо перемножить 3 матрицы поворота, и порядок их перемножения которых имеет большое значение.

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

где A = cos(Angle.X);
B = sin(Angle.X);
C = sin(Angle.Y);
D = cos(Angle.Y);
E = cos(Angle.Z);
F = sin(Angle.Z);

Заметьте, что C и D определены “не верно”, т.к. мы попутно приводим матрицу к некоему базису. Это связано с направлением оси Z в OpenGL.

Теперь необходимо рассчитать матрицу вида, которая выглядит так:

где , , – вектора построенные на соответствующих компонентах матрицы:

– положение камеры в пространстве. Операция dot осуществляет салярное произведение векторов.

И сам код осуществляющий расчёт:

procedure TCamera.SetRender;
var
   A, B, C, D, E, F : single;
   cx, cy, cz       : TVector;
begin
   with Angle do
   begin
     A := cos(X);
     B := sin(X);
     C := sin(Y);
     D := cos(Y);
     E := cos(Z);
     F := sin(Z);
   end;
   cx := Vector(C*E+B*D*F, A*F, B*C*F-E*D);
   cy := Vector(B*D*E-C*F, A*E, D*F+B*C*E);
   cz := Vector(A*D,        -B,       A*C);
   // заполнение матрицы
   m[0] := cx.X;  m[4] := cx.Y;  m[8]  := cx.Z;  m[12] := -V_Dot(cx, Pos);
   m[1] := cy.X;  m[5] := cy.Y;  m[9]  := cy.Z;  m[13] := -V_Dot(cy, Pos);
   m[2] := cz.X;  m[6] := cz.Y;  m[10] := cz.Z;  m[14] := -V_Dot(cz, Pos);
   m[3] := 0;     m[7] := 0;     m[11] := 0;     m[15] := 1;
   // установка матрицы проекции
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity;
   gluPerspective(FOV, Width/Height, 0.1, 100);
   glMatrixMode(GL_MODELVIEW);
   // установка матрицы вида
   glLoadMatrixf(@m);
end;

Здесь матрица описывается в виде одномерного массива из 16 элементов типа single.

m : array [0..15] of single;

Углы (Angle) задаются в радианах.

FOV – угол обзора камеры, который рекомендуется ставить равным 90

Width и Height - ширина и высота поля вывода соответственно

Vector – функция создания переменной типа TVector по трём значениям (x, y, z)

V_Dot – скалярное умножение векторов (x1x2 + y1y2 + z1*z2)

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

Ссылки

Статья на MirGames.ru: Основы 3D математики

19.06.05 16:49