Matrix has you. Матричное исчисление в создании 3D-engine
"Мы хотим делать 3D игры...". Это не удивительно. Игры являются сегодня самым прекрасным союзом художника, музыканта и программиста. Что еще в мире может быть прекрасней, чем сам мир, даже тогда, когда он на экране монитора. Идеальные контуры у каждого дерева, красивые симметричные здания. Весь мир, как хорошо прибранная квартира. Это рай наяву. В конце концов, злые и голодные, мы хотим уйти в ту реальность, где все еще старые замки, где красивые фасады, где зеленый сад, где странные неземные ландшафты, где нет проблем!
Ужасы Матрицы
Итак, в статье пойдет речь о том листке, на котором скромно написано "Матричное исчисление" применительно к проблеме создания 3D-engine. Для тех, кто забыл, напомню: "Матрица размеров mxn - система mn элементов матрицы, расположенных в прямоугольной таблице из m строк и n столбцов. Когда m=n, матрицу называют квадратной порядка n". А теперь вспомните про единичную, диагональную, скалярную матрицу и матрицу-столбец. Также необходимо вспомнить про то, какие операции над ними проводят. Вспомнили? Нет? Не беда - по ходу разговора кое-что проясниться для Вас. Если же знаете, то общение пойдет еще проще.
Чтобы нам было интересно разбираться с этими злыми матрицами, возьмемся разбирать устройство 3D движка, а эта проблема многих из нас интересует много больше, чем Высшая Математика с ее огромным потенциалом. Ничего сложного в матрицах нет, но и одной статьей проблему создания 3D-engine тоже не раскрыть. Будем стараться...
Устройство Матрицы 4x4
Матрица 4x4 не что иное, как способ описания местоположения, поворота, масштаба 3D объекта (или его точки) в линейном пространстве. Обсудим элементарные возможности матриц:
1. Translation. Смещение
Подобная трансформация показывает, как объект должен переместиться из точки (x,y,z) в точку (x',y',z').
2. Rotation. Вращение
Подобная трансформация показывает на то, куда должна попасть точка (или объект) (x,y,z) после вращения в пространстве вокруг оси X. А попадет она в точку (x',y',z').
Данная трансформация матрицы будет вращать точку (объект) вокруг оси Y.

Данная трансформация матрицы будет вращать точку вокруг оси Z.
3. Scaling. Масштабирование
Такая трансформация матрицы будет отвечать за масштаб объекта.
"Человек-Матрица"
Предположим, что мы смоделировали человека в 3D Studio MAX, у которого две руки, две ноги, голова и туловище. Наша модель будет состоять из 6 частей (meshes). Так вот для каждой части человека мы и должны иметь композитную матрицу, откуда будет видно местоположение, поворот и масштаб каждого из элементов человека. Переключаясь от одной матрицы к другой, мы прорисовываем наш объект по системе "одна матрица — одна деталь объекта". Это позволяет, не изменяя местоположения точек в базе данных модели, указывать размер ноги и ее текущее положение всего лишь одной матрицей (т.е. изменением в ней).
Иерархия на матрицах
Если мы захотим, чтобы человек изгибал коленку и нижняя его часть ноги не "отваливалась", то нам лишь стоит перемножить матрицу верхней части ноги и нижней в порядке "от родителя к ребенку". Именно это и называется иерархией.
Давайте подробно рассмотрим пример такой иерархии:
body_transformation // начинаем от туловища
upper_leg geometry // изображаем верхнюю часть ноги
knee transformation // трансформация на колено
lower_leg geometry // изображаем нижнюю часть ноги
footwrist transformation // опускаемся до "ботинка">
foot geometry // изображаем ботинок
А вот псевдокод для Direct3D:
IDirect3DDevice7::SetTransform(D3DTRANSFORMSTATE_WORLD,
body_transform)
IDirect3DDevice7::DrawPrimitive(upper_leg)
IDirect3DDevice7::MultiplyTransform(D3DTRANSFORMSTATE_WORLD,
knee_transform)
IDirect3DDevice7::DrawPrimitive(lower_leg)
IDirect3DDevice7::MultiplyTransform(D3DTRANSFORMSTATE_WORLD,
footwrist_transform)
IDirect3DDevice7::DrawPrimitive(foot)
Матрицы Сцены
Теперь обсудим то, с чего же начать работу и как создать сцену.
Технология не сильно сложна.
pd3dDevice->SetTransform(D3DTRANSFORMSTATE_WORLD, &IdentifyMatrix);
Мы установили матрицу мира. Лучше всего использовать для этой цели единичную матрицу, т.к. она будет удобней всего в использовании (хотя бы из-за того, что является частным случаем).
D3DMATRIX matView = IdentifyMatrix;
matView._43 = 10.0f;
pd3dDevice->SetTransform(D3DTRANSFORMSTATE_VIEW, &matView);
Мы указали местоположение (поворот и масштаб) матрицы камеры, которую отодвинули на 10 единиц по оси Z от центра координат. Мы указали, что камеру нужно отодвинуть, чтобы мы смогли наблюдать то, что происходит в центре (а в центре мы еще изобразим объект).
Как видите, 4-я cтрока и 3-ий столбец отвечают за сдвиг по Z (смотрите пункт 1.Translation. Смещение).
Теперь установим матрицу проекции (проекционная).
D3DMATRIX matProj = IdentifyMatrix;
matProj._11 = 2.0f;
matProj._22 = 2.0f;
matProj._34 = 1.0f;
matProj._43 = -1.0f;
matProj._44 = 0.0f;
pd3dDevice->SetTransform(D3DTRANSFORMSTATE_PROJECTION, &matProj);
Что такое проекционная матрица? Для того чтобы объяснить, посмотрим, как же происходит отображение мира на плоскость монитора.
Все, что находится между front clipping plane и back clipping plane — это место, где можно увидеть часть нашей сцены. Понятно, что показывать близкий к камере объект не стоит.
Теперь рассмотрим вид сверху. Величина D показывает тот пробел, который нам не суждено увидеть. Величина fov показывает угол обзора (наблюдения), который чаще всего в играх равен 60 или 90 градусам. Для Quake он равен 90, для Doom и Wolfenstein — 60. В таких играх, как Rollcage, он динамичен и пропорционален скорости движения "двухсторонней" машины. Очень важно знать, что от него зависит восприятие сцены. Для профессионального оператора умение управлять этим углом — есть основная задача в активных сценах (в боевиках).
Теперь вернемся к задаче трансформации проекции. Расстояние D обуславливается расположением (по данным матриц) камеры в пространстве и ее ближним планом.
Когда объект нам кажется большим, то это означает, что расстояние до него не большое. Если же объект кажется маленьким, то это означает, что он удален от нас. Из этого делаем вывод, что расстояние обратно пропорционально проецируемому объекту. Элементарная проекционная матрица выглядит так:
Вспомните, что положение нашей камеры установлено такой матрицей:
Т.е. камера отодвинута по оси Z на (—D) расстояние. Если помните, у нас оно было равно 10.
Теперь перемножим эти матрицы и получим результирующую:
Результирующая матрица есть не что иное, как матрица трансформации проекции на плоский экран. Теперь рассмотрим матрицу, которая будет больше подходить к практической стороне дела, т.к. будет учитывать то, что изображать нам надо сцену не на квадратный монитор. Т.е. нам нужно выдерживать пропорции при отображении сцены. Вот так выглядит конечная матрица перспективной проекции:
Zn указывает на расстояние до ближайшего плана (near clipping plane). w,h, Q — задают пропорции. Вычисляются величины w,h,Q следующим образом:
Здесь и задаем значения обзора fov для горизонтали и вертикали.
Собственно говоря, теперь будет достаточно просто разобраться и в псевдокоде установки проекционной матрицы.
D3DMATRIX ProjectionMatrix(const float near_plane,
// distance to near clipping plane
const float far_plane,
// distance to far clipping plane
const float fov_horiz,
// horizontal field of view angle, in radians
const float fov_vert)
// vertical field of view angle, in radians
{
float h, w, Q;
w = (float)cot(fov_horiz*0.5);
h = (float)cot(fov_vert*0.5);
Q = far_plane/(far_plane — near_plane);
D3DMATRIX ret = ZeroMatrix();
ret(0, 0) = w;
ret(1, 1) = h;
ret(2, 2) = Q;
ret(3, 2) = -Q*near_plane;
ret(2, 3) = 1;
return ret;
}
Как видите, 3 матрицы мы успешно установили.
Игровой цикл с матрицами
Матрицу камеры (View Matrix) необходимо устанавливать в каждом новом кадре только в том случае, если мы изменили положение камеры (этот случай очень частый). Матрица мира будет задаваться матрицей объекта, т.е. его частью. Рассмотрим технологию обработки кадра сцены:
1. Опрашиваем приходящие от пользователя указания об изменениях в матрице камеры (источника).
2. Устанавливаем матрицу источника.
3. Устанавливаем матрицу мира применительно к объекту, который мы собираемся изображать.
4. Изображаем часть сцены, которая использует данные этой матрицы. Зацикливаемся между 3-им и 4-ым, пунктом пока не закончим работу над сценой.
5. После успешного изображения возвращаемся в 1-ый пункт и повторяем все заново.
"Мы такие, какими мы себе кажемся"
Матрицы являются первоклассным инструментом для достижения целей, которые поставлены в начале статьи. Чтобы эта статья действительно оказалась руководством к действию, то Вам нужно взять документацию к MS DirectX 7.0 SDK и познакомиться поближе с тем, как там работают механизмы отображения примитивов и инициализации сцены. Уж очень хотелось рассказать о том, что многие называют ужасом. Матрицы, матрицы, матрицы... Надеюсь, что что-то прояснил в этом вопросе для Вас.
Продолжение следует...
Павел Горбунов

