В отличие от двухмерного программирования графики, работать с 3D-графикой несколько сложнее. Для создания даже простых трехмерных программ вам понадобится значительно больше математических познаний, чем для работы со спрайтовой графикой. В связи с этим в этой главе я предлагаю рассмотреть основы программирования 3D-графики, без знания которых у вас не получится создать ни одной дельной программы. Прежде всего нам необходимо разобраться с тем, что на самом деле есть трехмерное пространство и каким образом в этом пространстве представляются 3D-объекты. После этого мы обобщим все полученные знания и рассмотрим модель графического конвейера, используемого в XNA, а уже со следующей главы приступим к разработке трехмерной игры. Итак, приступим к изучению 3D-графики и начнем с понятия простой системы трехмерных координат.
15.1. Система трехмерных координат
Система координат, применяемая в 3D-программировании, отличается от двухмерной системы координат, но практически идентична простой декартовой системе, за исключением некоторых нюансов. В этой системе координат начальная точка отсчета находится в левом нижнем углу экрана. Ось X проходит по нижней части телевизора слева направо, а ось Y – снизу вверх. Дополнительно в трехмерном программировании добавляется новая ось Z, которая и позволяет работать с 3D-объектами. Смысловая нагрузка оси Z заключается в том, чтобы на двухмерной плоскости экрана правильно представить трехмерный объект, или, как часто говорят, модель. Ось Z позволяет организовать глубину в двухмерном пространстве. Для начала это дает нам возможность отдалять или приближать объекты по этой оси. В более сложных операциях ось Z принимает участие в трансформации и масштабировании объектов, в различных видовых и мировых преобразованиях, вычислении перспективы и многом другом, о чем мы, несомненно, поговорим в этой главе. Сейчас посмотрите на рис. 15.1, где изображена трехмерная система координат. В DirectX и OpenGL имеются две различные ориентации трехмерных координат. Это так называемая правосторонняя система координат и левосторонняя система координат. В DirectX ранее использовалась только левосторонняя система, а в OpenGL основной была правосторонняя система координат. В XNA Framework для представления трехмерного пространства применяется исключительно правосторонняя система координат. В чем разница между этими двумя ориентациями?
Рис. 15.1. Трехмерная система координат
Разница небольшая, но она существенна и может повлиять на неправильное определение точки вывода объекта в пространстве. В правосторонней системе координат положительная ось Z идет в направлении прямо к вам. То есть если вы сейчас смотрите на страницу этой книги, то положительная часть оси Z выходит прямо со страницы книги и направляется в вашу сторону. В свою очередь, отрицательная часть плоскости оси Z удаляется от вас, и все то, что находится за книгой, – это уже есть отрицательная часть оси Z. На рис. 15.2 представлена правосторонняя система координат. В левосторонней системе координат все наоборот. Положительная часть оси Z удаляется от вас, тогда как отрицательная часть оси Z направлена в вашу сторону. Поэтому если вы перепутаете эти две системы координат между собой и дадите объекту неправильные координаты по оси Z, то можете этот объект просто не увидеть на экране. Например, вы указываете для вывода объекта по оси Z координату со значением минус 100 пикселей. В этом случае в правосторонней системе координат (нашей основной системе координат) объект будет располагаться как бы внутри телевизора и будет удален от поверхности экрана в его глубь на 100 пикселей. Если вы перепутаете значения и укажите положительные 100 пикселей, то объекта на экране вы не увидите, поскольку он будет находиться где-то между поверхностью экрана и вашими глазами, то есть вне зоны видимости. Но при этом существуют дополнительные нюансы, связанные с положением камеры или положением ваших глаз. По умолчанию в правосторонней истеме координат предполагается, что камера или ваши глаза находятся в положительной части оси Z, и соответственно, для того чтобы увидеть объект на экране, его нужно удалять от себя или назначать ему небольшие положительные, нулевые или отрицательные значения по оси Z. Но можно изменять положение камеры и переносить ее в отрицательную плоскость, чтобы смотреть на объекты сзади. В этом случае для вывода объектов может потребоваться использовать уже положительные значения по оси Z. Более подробно о камерах мы поговорим далее в этой главе, а сейчас перейдем к основам представления примитивов в трехмерной плоскости.
Рис. 15.2. Правосторонняя система координат
15.2. Точки и вершины
Очевидно, что для представления любой точки в трехмерной плоскости нужно задать координаты по всем трем осям – X, Y и Z. Для представления на экране телевизора примитива (треугольник, квадрат, прямоугольник, ромб и т. д.) необходимо задать N-количество точек, соединив которые между собой, вы получите заданный примитив. То есть с помощью определенного количества точек в пространстве можно нарисовать примитив любой формы. В компьютерной графике есть понятие полигон. Полигон – это площадь в пространстве определенного размера, которая строится на основе точек. Как правило, в качестве полигона в графике применяется обыкновенный треугольник (примитив). Получается, что полигон – это простой треугольник, построение которого в пространстве происходит на базе трех точек. В этом контексте точка приобретает более осмысленную роль, и ее в компьютерной графике принято называть вершиной. Вершина – эта точка в пространстве, заданная по трем осям координат X, Y и Z. Имея три вершины, вы можете построить в пространстве полигон. Иначе говоря, вершины и полигоны – это простые точки и треугольники, которые мы все изучали в геометрии. На базе вершин и полигонов в компь исходит работа с трехмерными объектами, или моделями.
15.3. Модель
Любую трехмерную модель можно представить в пространстве определенным количеством полигонов. Посмотрите на рис. 15.3, где показана работа утилиты DirectX Viewer, входящей в состав DirectX SDK. Эта утилита позволяет открыть модель в формате X-файла, или мэш, и показать количество полигонов, участвующих в построении этой модели. Работа любой видеокарты по рисованию трехмерной графики на экране состоит в том, чтобы собрать из вершин полигоны и затем сформировать из этих полигонов конечную модель. Большое количество полигонов трехмерного объекта дает возможность создавать более детализированную модель. С другой стороны, большое количество полигонов одной модели может повлиять на скорость прорисовки графики на экране телевизора. На сегодняшний день множество отдельно взятых моделей с несколькими десятками тысяч полигонов будет по силам приставке Xbox 360.
15.4. Матрицы
Программирование трехмерной графики невозможно себе представить без использования матриц. Матрица – это двухмерный массив данных, аполненный определенными значениями. Матрицы позволяют легко и быстро производить любые манипуляции с вершинами объектов в пространстве. С помощью матриц можно связать несколько однородных операций над вершинами, что в результате позволяет выполнить колоссальные по объему математические вычисления за небольшой промежуток времени. Более того, матрицы можно использовать и в описании координатных систем для переноса, масштабирования, вращения и трансформации объектов в пространстве. Размерность матрицы может быть любой, но в компьютерной графике типичной размерностью является матрица 4×4, где имеются четыре строки и четыре столбца.
Рис. 15.3. Полигоны модели
Если вам необходимо определить положение элемента внутри матрицы, то нужно сначала указать, в какой именно строке находится этот элемент, а затем указать, в каком столбце он располагается. В итоге получается, что искомый элемент матрицы с размерностью m×n находится в строке m столбца n.
15.4.1. Сложение и вычитание матриц
Все математические операции над матрицами основаны на знаниях, которые мы изучали в школе. Единственным условием в сложении и вычитании матриц является их одинаковая размерность. Чтобы сложить между собой две матрицы, необходимо просто сложить поэлементно обе матрицы между собой, а результат записать в отдельную матрицу.
Вычитание двух матриц происходит по той же схеме.
15.4.2. Умножение матриц
Умножение матриц бывает двух видов. Это скалярное произведение и матричное произведение. Скалярное произведение матрицы – это умножение матрицы на любое скалярное значение. В этом случае элементы матрицы поочередно перемножаются на это самое скалярное значение, а результат записывается в итоговую матрицу. При такой операции размерность матриц не имеет абсолютно никакого значения.
Матричное произведение отличается от скалярного произведения тем, что в этих операциях используются две и более матрицы, где обязательно должно соблюдаться следующие условие: количество столбцов матрицы А должно быть равно количеству строк матрицы В. Механизм перемножения двух матриц между собой заключается в последовательном произведении каждого элемента из первой строки матрицы А на каждый элемент первого столбца матрицы В. Затем это произведение суммируется между собой, а результат записывается в отдельную матрицу
В этом показательном примере первый элемент матрицы А умножается на первый элемент первого столбца матрицы В. Затем второй элемент первой строки матрицы А умножается на второй элемент первого столбца матрицы В. И так далее, до окончания всех элементов в строке матрицы А и в столбце матрицы В. Потом эти результаты суммируются между собой, а итоговое значение записывается в первую строку матрицы С. Все записи в матрице С происходят слева направо и сверху вниз. Перемножение матриц между собой носит название матричной конкатенации.
Операция умножения матрицы А на матрицу В некоммутативна, то есть А × В = С, но В × А ≠ С.
15.5. Матричные преобразования
В компьютерной графике определены понятия трех различных матриц. Это мировая матрица (World Matrix), матрица вида (View Matrix) и матрица проекции (Projection Matrix). С помощью этих матриц в исходном коде программы производятся матричные преобразования над моделями. Матричные преобразования подразумевают под собой умножение каждой вершины объекта на одну из матриц или последовательное умножение всех вершин объекта на каждую из трех матриц. Такой подход позволяет корректно представить модель в трехмерном пространстве вашего двухмерного телевизора. Техника прохода модели через три перечисленные матрицы представляет суть механизма работы с графическими данными в трехмерном пространстве. Чтобы было более понятно, о чем идет речь, давайте остановимся на каждой из трех матриц подробнее.
15.5.1. Мировая матрица
Мировая матрица – позволяет производить различные матричные реобразования (трансформацию, масштабирование) объекта в мировой системе координат. Мировая система координат – это своя локальная система координат данного объекта, которой наделяется каждый объект, скажем так, прошедший через мировую матрицу, поскольку каждая вершина участвует в произведении этой матрицы. Новая локальная система координат значительно упрощает аффинные преобразования объекта в пространстве. Например, чтобы перенести объект с левого верхнего угла экрана в нижний правый угол экрана, необходимо просто перенести его локальную точку отсчета системы координат на новое место. А представьте, если бы не было мировой матрицы и этот объект пришлось переносить по дной вершине из угла в угол телевизора… Поэтому любой объект, а точнее все вершины этого объекта проходят через мировую матрицу преобразования. Как уже упоминалось, мировое преобразование вершин объекта может состоять из любых комбинаций вращения, трансляции и масштабирования. В математической записи вращение вершины по оси Х выглядит следующим образом:
где Ω — угол вращения в радианах. Вращение вершины вокруг оси Y выглядит так:
А вращение вокруг оси Z происходит по следующей формуле:
Трансляция вершины позволяет переместить вершину с координатами x, y, z в новую точку с новыми координатами x1, y1, z1. В математической записи это выглядит так:
x1 = x + Tx y1 = y + Ty. z1 = z + Tz
Трансляция вершины в матричной записи выглядит следующим образом:
где Tx, Ty и Tz — значения смещения по осям X, Y и Z. Масштабировать вершину в пространстве (удалять или приближать) с координатами x, y, z в новую точку с новыми значениями x1, y1, z1 можно посредством следующей записи: x1 = x × S y1 = y × S. z1 = z × S В матричной записи это выражается следующим образом:
где Sx, Sy, Sz — значения коэффициентов растяжения или сжатия по осям X, Y, Z. Все перечисленные операции можно делать в исходном коде программы вручную, то есть вычислять приведенные записи так, как мы их только что описали. Но, естественно, так никто не делает (почти никто), потому что в XNA Framework имеется огромное количество методов, которые сделают все вышеприведенные операции за «один присест». В дальнейшем, когда мы начнем работать с объектами, вы познакомитесь с этими методами вплотную и на практике.
15.5.2. Матрица вида
Матрица вида – задает местоположение камеры в пространстве, это вторая по счету матрица, на которую умножаются вершины объекта. Эта матрица способствует определению направления просмотра трехмерной сцены. Трехмерная сцена – это все то, что вы видите на экране телевизора. Это как в театре, где вы сидите в портере или на галерке и наблюдаете за действиями на сцене. Так вот, сидя в портере, у вас будет одно местоположение камеры, а сидя на галерке – уже совсем другое. Фактически эта матрица даже позволяет определять жанр игры. Например, игра DOOM от первого лица – это, можно сказать, первые ряды портера в театре, тогда как игра Warcraft – это галерка, причем высоко на балконе. Матрица вида предназначена для определения положения камеры в пространстве, где вы можете смещать позицию камеры влево, вправо, вверх, вниз, удалять, приближать ее и т. д.
15.5.3. Матрица проекции
Матрица проекции – это более сложная матрица, которая создает проекцию трехмерного объекта на плоскость двумерного экрана телевизора. С помощью этой матрицы определяются передняя и задняя области отсечения трехмерного пространства, что позволяет регулировать пространство отсечения невидимых на экране объектов, а заодно и снизить нагрузку процессора видеокарты. На рис. 15.4 изображен механизм проекции объекта в плоскости и отсечение пространства.