Данный материал взят с сайта old.shatalov.su и является его зеркалом

Создаём компьютерную игру. Создание игр на C++/DirectX

Есть вопросы?
Ошибка на сайте?
рус eng esp
Внимание! Данный сайт не обновляется. Новая версия: shatalov.su

Преобразования матриц в DirectX

Дата создания: 2009-11-23 01:46:15
Последний раз редактировалось: 2012-02-08 08:24:13

    Предварительные уроки:
  1. Перспективная проекция. Перейти.
  2. Статические библиотеки. Перейти.
  3. Функции. Перейти.

Сегодня будет совсем маленький урок. Мы не будем изучать что-то новое, вместо этого закрепим материал по преобразованиям. Зато, вам придётся довольно много поработать самостоятельно.

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

Сегодняшнюю программу я обозвал просто: преобразования. Полный код в разделе Листинги.

В программе мы будем использовать класс vector4 (версия vector4_v0.1.h), который вы можете найти всё в том же разделе листинги. В коде vector4 я оставил только определения переменных и конструкторы. Не забудьте подключить библиотеку math_v0.1.lib (найти можно там же).

Графический конвейер (graphics pipeline) DirectX

Работа с графикой, начиная с создания примитивов и заканчивая выводом на экран, в DirectX разделена на несколько стадий. Все вместе эти стадии образуют графический конвейер - graphics pipeline. Как я уже писал выше, в сегодняшнем уроке мы возьмём на себя реализацию одной из стадий: преобразование матриц.

Нам понадобится новая структура matrix (хотя, можно было бы вполне ограничиться и D3DMATRIX, которую мы использовали раньше):

struct matrix
{
public:
  float _11, _12, _13, _14;
  float _21, _22, _23, _24;
  float _31, _32, _33, _34;
  float _41, _42, _43, _44;
};

Структура простая - 16 полей типа float. Имена полей совпадают с соответствующими полями структуры D3DMATRIX.

Далее идёт инициализация всех точек:

vertex vertices[] =
{
  { 0, 0, 0, 0xffffffff},
  { 0, 0, 0, 0xff000000},
  { 0, 0, 0, 0xff000000},
  { 0, 0, 0, 0xff000000},
};

vector4 vectors[4] = 
{
  vector4(-1,-1,0,1),
  vector4(-1, 1,0,1),
  vector4( 1,-1,0,1),
  vector4( 1, 1,0,1),
};

Координаты из массива vertices мы проинициализировали нулями. Эти поля мы заполним после всех преобразований. Вспомогательный массив vectors хранит координаты точек. 4-ая компонента у всех точек - единица. После всех преобразований мы скопируем информацию из массива vectors в массив vertices.

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

matrix matWorld;
matrix matCam;
matrix matProj;

Инициализацию матриц мы трогать не будем (так как имена полей matrix и D3DMATRIX совпадают), за исключением matProj:

matProj._11=1; matProj._12=0; matProj._13=0; matProj._14=0;
matProj._21=0; matProj._22=1; matProj._23=0; matProj._24=0;
matProj._31=0; matProj._32=0; matProj._33=100/99; matProj._34=1;
matProj._41=0; matProj._42=0; matProj._43=-100/99; matProj._44=0;

Что конкретно обозначают все поля этой матрицы я объяснять не буду, с подробным описанием данной матрицы вы должны были ознакомиться в одном из предварительных уроков.

Заместо тех матриц, которые мы передавали в метод IDirect3DDevice9::SetTransform, по умолчанию в Direct3D используются единичные, т.е. матрицы, на главной диагонали у которых расположены единицы.

Нам нужно перемножать векторы на матрицы преобразования. Для этого мы используем функцию:

void multiplication (vector4& v, matrix& m)
{
  v.x = v.x * m._11 + v.y * m._21 + v.z * m._31 + v.w * m._41;
  v.y = v.x * m._12 + v.y * m._22 + v.z * m._32 + v.w * m._42;
  v.z = v.x * m._13 + v.y * m._23 + v.z * m._33 + v.w * m._43;
  v.w = v.x * m._14 + v.y * m._24 + v.z * m._34 + v.w * m._44;
}

Функция принимает два параметра по ссылке. В теле функции - формула перемножения двух матриц. Как видите, всё просто (ну это если вы внимательно читаете уроки из раздела математика).

    Итак, нам нужно выполнить следующие шаги:
  1. Преобразовать все точки объекта (в нашем случае - квадрата) в мировое координатное пространство.
  2. Из мирового пространства, в пространство камеры.
  3. Подготовить векторы к проецированию на плоскость.
  4. Спроецировать векторы на двухмерную плоскость, разделив все компоненты вектора на четвёртую.

Теперь непосредственно код, выполняющий все шаги преобразования точек нашей модели:

for (int i = 0; i < 4; i++)
{
  multiplication(vectors[i],matWorld);
  multiplication(vectors[i],matCam);
  multiplication(vectors[i],matProj);
  vectors[i].x /= vectors[i].w;
  vectors[i].y /= vectors[i].w;
  vectors[i].z /= vectors[i].w;
  vectors[i].w /= vectors[i].w;
}

Посмотрите на код. Разве это не прекрасно!? Нам потребовалось изучить векторы, матрицы, различные виды преобразований, а теперь всё это уместилось в семи строчках кода!

Далее необходимо первые три компоненты этих векторов запихать в соответствующие структурные переменные (вершины vertex):

for (int i = 0; i < 4; i++)
{
  vertices[i].x = vectors[i].x;
  vertices[i].y = vectors[i].y;
  vertices[i].z = vectors[i].z;
}

Этот код обязательно должен идти до заполнения вершинного буфера! Остальной код программы с прошлого урока не изменился.

Как видите, в программе используется два типа точек: тип vertex, в котором хранится три координаты и цвет; и вспомогательный тип vector4 с четырьмя координатами, который мы используем для вычислений.

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

При вызове метода SetTransform не происходит переменожения всех точек на матрицу. Мы всего-лишь говорим DirectX'у, какую матрицу использовать для какого преобразования. Перемножение точек моделей на матрицы преобразования происходит на соответствующей стадии графического конвейера.

В данной программе мы произвели все вычисления самостоятельно, используя для хранения точек временные переменные типа vector4. А что будет дальше, после того как мы перенесли значения просчитанных координат в переменные типа vertex, будет ли DirectX перемножать эти точки на матрицы преобразования? Конечно же будет, но так как по умолчанию используются единичные матрицы, то значения переменных, которые мы вычислили, не изменятся.

Заключение

Если вы читали какие-нибудь книжки по DirectX, то возможно заметили, что не в каждой преобразования рассматриваются настолько подробно. Обычно же, для преобразований используются библиотечные функции. В наших уроках мы могли бы тоже пойти по этому пути. Но я считаю, и не без оснований, что гораздо полезней изучить механизм лежащий в основе, а не несколько прототипов функций. Например, в D3DX: D3DXMatrixTranslation , D3DXMatrixRotationAxis или в OpenGL: glTranslatef, glRotatef и множество других.

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

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

На сегодня всё.

Упражнения

1. В программе, которую мы сегодня разбирали есть ошибка, вы должны её исправить. Например, при повороте квадрата на 45 градусов с помощью матрицы MatWorld, результат будет вот таким:

Вам нужно сделать так, чтобы квадрат выглядел квадратом, а не вот этим чудовищем на картинке. Те кто неплохо знает C++, наверное, ошибку уже заметили, ну а новичкам придётся потрудиться, чтобы её отыскать. Подсказка: обратите внимание на уроки, которые я порекомендовал в начале выпуска.

2. Выполнить все упражнения из урока по вершинным буферам, теперь уже с помощью кода из сегодняшнего урока.

3. Просто поизучайте преобразования: попробуйте разные углы поворота, попробуйте одновременно поворачивать объект и переносить его (гарантирую, вы сильно удивитесь как оно работает на самом деле; чтобы разобраться, посмотрите материал об инерционной системе координат), попробуйте задать разные соотношения сторон экрана: 4:3, 16:9. В общем, вам нужно привыкнуть к матрицам преобразования.

Не забывайте, что в C++ функции sin и cos (нужно подключить math.h) принимают значения в радианах. Формула перевода из градусной в радианную меру: r = g*PI/180 (где PI = 3,14, а g - размер угла в градусах).