Организация камеры в 3D играх

Источник: delphisources
XProger

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

   Итак, что такое камера в 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 - преобразует текущую (видовую, проекции, текстуры) матрицу в единичную
 

 1  0  0  0
 0  1  0  0
 0  0  1  0
 0  0  0  1


  glRotatef
- домножает текущую матрицу вида на матрицу поворота относительно одной из осей координат.
   Имея 3 оси координат, соответственно можно вычислить всего 3 матрицы поворота:

Относительно оси OX:
 

 1  0  0  0
 0  c  s  0
 0 -s  c  0
 0  0  0  1

Относительно оси OY:
 

 c  0 -s  0
 0  1  0  0
 s  0  c  0
 0  0  0  1

Относительно оси OZ:
 

 c  s  0  0
-s  c  0  0
 0  0  1  0
 0  0  0  1

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

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

 1  0  0  0
 0  1  0  0
 0  0  1  0
 x  y  z  1

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

   Итак, с сутью операций разобрались, теперь можно приступить к оптимизации, которая будет заключаться в ручном вычислении матрицы вида!
   Для этого нам понадобятся 3 угла и позиция камеры.
   Нам необходимо перемножить 3 матрицы поворота, и порядок их перемножения которых имеет большое значение.
   В итоге, перемножение матриц [Z]*[X]*[Y] будет выглядеть так:
 

 [ E  F  0 ]   [ 1  0  0 ]   [ C  0 -D ]   [ CE+BDF AF BCF-ED ]
 [-F  E  0 ] X [ 0  A  B ] X [ 0  1  0 ] = [ BDE-CF AE DF+BCE ]
 [ 0  0  1 ]   [ 0 -B  A ]   [ D  0  C ]   [     AD -B     AC ]

 где
 

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.

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

 x.x    x.y    x.z   -dot(x, Pos)
 y.x    y.y    y.z   -dot(y, Pos)
 z.x    z.y    z.z   -dot(z, Pos)
   0      0      0             1

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

 x = (CE+BDF, AF, BCF-ED)
 y = (BDE-CF, AE, DF+BCE)
 z = (    AD, -B,     AC)

   Pos - положение камеры в пространстве.
   Операция 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 - скалярное умножение векторов (x1*x2 + y1*y2 + z1*z2).

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

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

   Итак, что такое камера в 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 - преобразует текущую (видовую, проекции, текстуры) матрицу в единичную
 

 1  0  0  0
 0  1  0  0
 0  0  1  0
 0  0  0  1


  glRotatef
- домножает текущую матрицу вида на матрицу поворота относительно одной из осей координат.
   Имея 3 оси координат, соответственно можно вычислить всего 3 матрицы поворота:

Относительно оси OX:
 

 1  0  0  0
 0  c  s  0
 0 -s  c  0
 0  0  0  1

Относительно оси OY:
 

 c  0 -s  0
 0  1  0  0
 s  0  c  0
 0  0  0  1

Относительно оси OZ:
 

 c  s  0  0
-s  c  0  0
 0  0  1  0
 0  0  0  1

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

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

 1  0  0  0
 0  1  0  0
 0  0  1  0
 x  y  z  1

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

   Итак, с сутью операций разобрались, теперь можно приступить к оптимизации, которая будет заключаться в ручном вычислении матрицы вида!
   Для этого нам понадобятся 3 угла и позиция камеры.
   Нам необходимо перемножить 3 матрицы поворота, и порядок их перемножения которых имеет большое значение.
   В итоге, перемножение матриц [Z]*[X]*[Y] будет выглядеть так:
 

 [ E  F  0 ]   [ 1  0  0 ]   [ C  0 -D ]   [ CE+BDF AF BCF-ED ]
 [-F  E  0 ] X [ 0  A  B ] X [ 0  1  0 ] = [ BDE-CF AE DF+BCE ]
 [ 0  0  1 ]   [ 0 -B  A ]   [ D  0  C ]   [     AD -B     AC ]

 где
 

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.

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

 x.x    x.y    x.z   -dot(x, Pos)
 y.x    y.y    y.z   -dot(y, Pos)
 z.x    z.y    z.z   -dot(z, Pos)
   0      0      0             1

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

 x = (CE+BDF, AF, BCF-ED)
 y = (BDE-CF, AE, DF+BCE)
 z = (    AD, -B,     AC)

   Pos - положение камеры в пространстве.
   Операция 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 - скалярное умножение векторов (x1*x2 + y1*y2 + z1*z2).

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


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=20551