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

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

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

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

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

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

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

код на языке c++
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.

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

код на языке c++
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.

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

код на языке c++
matrix matWorld;
matrix matCam;
matrix matProj;

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

код на языке c++
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 используются единичные, т.е. матрицы, на главной диагонали у которых расположены единицы.

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

код на языке c++
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;
}

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

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

код на языке c++
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):

код на языке c++
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.