Організація камери в 3D іграх, Різне, Програмування, статті

У даній статті я постараюся показати принципи організації камери в іграх.Пріведенний метод не є чимось новим і досі невідомим, це лише мої напрацювання в цій галузі, які допоможуть швидко і зручно реалізувати камеру в грі.

Отже, що таке камера в 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).

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

Схожі статті:


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

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*