allasm.ru |
|
Урок 4. Первое полноценное трехмерное приложение. Основы трехмерной графики. Как это все работает. Я расскажу вам как устроен механизм визуализации в DirectX, и вы научитесь использовать в своей работе простые полигоны.
Предисловие Как вы знаете, вся трехмерная графика основана на математике. "Линейная алгебра", "Векторная алгебра и начала анализа", "Начертательная геометрия" и еще много чего другого необходимо усвоить прежде чем приступать к успешной работе с 3D. Конечно, это не так интересно, но все равно придется этим заниматься. С вашей стороны необходимо всего лишь желание, а я в свою очередь постараюсь рассказать все как можно проще и доступней на примерах и картинках. Кому не терпится перейти сразу к делу начинайте с пункта 7. Начнем с самых основ. Загадочная буква "D" в словах 2D и 3D расшифровывается как Dimension - измерение. Соответственно 2D - это 2 измерения, а 3D - три. 2D представляет собой обычную систему координат на плоскости, которая знакома всем еще со школы. А что представляет собой 3D ? Это та же система координат, только к ней добавляется еще одно измерение обозначаемое как Z. На рисунке изображены обе системы.
Из рисунка видно, что новая координата Z отвечает за то, насколько далеко находится от нас какой - либо обьект. Обе системы координат ( 2D и 3D ) существуют в двух разных вариантах - левосторонняя и правосторонняя. Разница между ними в направлении отсчета. В левосторонней X и Y увеличиваются слева направо, а в правосторонней справа налево. Соответственно координата Z тоже меняет свое направление. На рисунке изображены как раз левосторонние системы. Direct3D позволяет использовать ту, которая вам нравится. Я, как все нормальные люди, буду использовать стандартную левостороннюю систему. Точка, точка, запятая - вышла рожица кривая. Самое простое, что существует в геометрии - это точка. В трехмерном пространстве точка трактуется как вершина - Vertex. В Direct3D у вершины помимо координат могут быть дополнительно какие - либо свойства. Координаты совместно со свойствами хранятся в одном месте и называются форматом вершины - Vertex Format. Direct3D позволяет задавать различный формат вершины, так называемый FVF - Flexible Vertex Format - гибкий формат вершин. Выбор формата зависит от тех задач, которые вы перед собой ставите. Он может состоять из нескольких флагов. Каждый флаг в отдельности описывает каким свойством будет обладать наша вершина. Допускается объединение сколько угодно таких флагов, если они не противоречат друг другу. Соответственно не забывайте, что чем больше флагов вы используете в формате вершины, тем больший размер она будет занимать в памяти. Например, часто используемый формат содержит:
Теперь вы видите, что всего лишь на одну вершину при таком формате будет затрачено не так и мало памяти. Хорошо. Вы выбрали формат, который подходит для ваших целей. Как его описать? Делается это так: D3DFVF_CUSTOMVERTEX equ D3DFVF_DIFFUSE or D3DFVF_XYZ or D3DFVF_NORMAL и т.д D3DFVF_CUSTOMVERTEX - это простая константа, определенная вами, которая позволяет объединять все нужные свойства для вершины. Далее D3DFVF_CUSTOMVERTEX можно уже указывать в качестве параметра в нужной нам функции. Можно D3DFVF_CUSTOMVERTEX и не использовать, но тогда в каждой функции, где необходимо указывать формат вершины, нужно будет перечислять все флаги, что не очень удобно. Пока остановимся на этом. Далее в тексте статьи мы еще поговорим о вершинах.
Формат вершины уже не является для вас тайной. Ну а как же составить из разрозненных вершин какой либо обьект ? Все объекты состоят из треугольников, скажете вы и будете правы. Конечно в Direct3D можно использовать и другие фигуры, число углов у которых больше чем три. Это квадраты и многоугольники. Но, так как аппаратная часть ускорителя оптимизирована на работу с треугольниками, то перед отрисовкой ваших квадратов они все равно будут разбиты на треугольники, так что сэкономив на простоте описания обьекта, можно проиграть в скорости. Как и что лучше использовать, решать вам. А почему выбрали именно треугольник ? Потому что он является самой простой фигурой и всегда выпуклый. А раз он выпуклый, то упрощаются различные расчеты при работе с ним ( пересечение прямой, лежит ли точка внутри него и т.д. ) Вы это поймете сами, когда еще более углубитесь в работу с 3D. Все фигуры в трехмерной графике называют "полигонами" - если перевести буквально, то это означает многоугольник. Но так как используют практически одни треугольники, то и название полигон стало синонимом слова треугольник. И когда вы видите в какой - либо статье про новую игрушку, что в модели монстра используется 5000 полигонов, то это значит, что он состоит из 5000 треугольников. Сейчас давайте рассмотрим, как описываются полигоны. Для быстроты просчета сцены ( это определение я раскрою чуть дальше ) в Direct3D ( да и в любом другом 3D API ) применяется схема, когда невидимые полигоны отбрасываются и просчитываются только те, которые мы с вами видим. Как же Direct3D может узнать какие полигоны рисовать, а какие нет? Все дело в том как они заданы. И огромную роль в этом играет порядок следования вершин в описании полигона. Догадались? Правильно. Они должны быть заданы либо по часовой стрелке, либо против. Стандартом является то, когда вершины, составляющие полигон, заданы по часовой стрелке. В Direct3D этот режим включен по умолчанию. Т.е. он будет отрисовывать только эти полигоны, а все другие, где вершины описаны против часовой стрелки, отбрасывать. Нижеследующий рисунок поможет вам прояснить все моменты.
Чтобы было еще более понятно, разберем пример. У нас есть куб. Он повернут к нам одной гранью. Начинаем писать вершины этой грани по часовой стрелке. Начинать можно с любой. Когда мы написали эти четыре вершины друг за дружкой, то поворачиваем к себе куб новой гранью и опять пишем вершины по часовой стрелке и т.д. пока все грани не будут описаны. Если теперь внимательно подумать и посмотреть в каком порядке будут находиться вершины той грани, которая повернута от нас, то окажется, что они будут расположены против часовой стрелки ( если смотреть на эту грань сквозь куб ). Вот и весь фокус. Соответственно раз они против часовой стрелки и грань куба нам действительно не видно, то и рисовать ее не надо, что и делает с успехом Direct3D ( с вашей помощью, конечно ). Если вы собираетесь вручную описывать обьекты для дальнейшего использования в программе, то от этой процедуры вам не избавиться как бы этого ни хотелось. Ну а если использовать 3D редактор, то он все сделает за вас автоматически и опишет каждый полигон в нужном порядке. Также довожу до вашего сведения, что в Direct3D в любой момент можно поменять условие, по которому будет определяться какие полигоны отбрасывать. Те которые по часовой стрелке или против. В некоторых случаях это действительно необходимо, но об этом как - нибудь в другой раз. Теперь когда вы знаете о том, что такое полигоны и как их нужно описывать, пора переходить к следующей части. 4. Что такое сцена. Как Direct3D работает с трехмерным миром. В трехмерной графике есть такое понятие как сцена. Вы были в театре и видели там сцену, на которой выступают актеры. Так вот здесь это тоже самое. На ней также происходят различные действия. Все обьекты, с которыми вы работаете в данный момент, манипулируете и пытаетесь отрисовать, и представляют собой в совокупности сцену. Простым языком, сцена - это то, что мы в данный момент видим на экране. Допустим, мы имеем обьект из полигонов на сцене. Как мы можем им манипулировать, перемещать, вращать вокруг оси и т.д? Не забудьте при этом, что все присутствующее на сцене распологается в трехмерной системе координат. Для перемещения вдоль какой - либо оси достаточно изменить координаты каждой из вершин в ту или иную сторону. Для поворота все уже намного сложнее. Тут нужно применять формулы, основаные на синусе и косинусе. Все они уже давно написаны математиками и с блеском работают. Главное их неудобство заключается в громоздкости и в том, что для поворота вокруг нескольких осей сразу, нужно применять эти формулы по очереди для каждой оси в отдельности. Результат может оказаться не таким, какой мы ожидали ( думаю, то, что они применяются для каждой вершины отдельно, обьяснять вам не нужно ). Для избавления от этого недостатка была придумана, так называемая, однородная система координат, где все действия над обьектами было очень удобно производить с помощью МАТРИЦ. Если хотели произвести сразу много действий, то просто перемножали матрицы нужных операций и результирующую матрицу, содержащую в себе все преобразования, уже применяли к вершинам всего обьекта. Удобно и быстро. И теперь все системы просчета 3D графики работают на матрицах. Что представляет собой матрица? Это обычная числовая таблица из строк и столбцов. Вот как она выглядит:
Именно такие матрицы 4x4 и использует в своей работе Direct3D. Описываются они обычным массивом из 16 элементов, размер каждого элемента 4 байта ( Real4 ). Соответственно, размер в памяти, который занимает одна матрица равен 64 байтам. Существуют три основные матрицы, которые мы должны знать:
"Мировая матрица" отвечает за преобразование всей сцены (вращение вокруг осей, перемещение и т.д.). Именно всей, а не какого - либо отдельного обьекта на ней. "Матрица вида" отвечает за то, какая часть сцены будет видна на экране. "Матрица проекции" отвечает за то, как трехмерные координаты будут преобразованы в двумерные для отображения на экран. Эти три матрицы и являются тем чудесным средством, с помощью которого Direct3D превращает трехмерный мир в плоскую картинку на вашем мониторе. Хоть трехмерный мир почти и бесконечен, но вот возможности ускорителя и процессора далеко не безразмерные. Если просчитать пару тысяч полигонов не проблема, то десятки и тем более сотни тысяч за раз подчас является трудоемкой задачей, даже для самых последних hi-end-овых ускорителей ( естественно всякие там Cray не в счет :) ). Поэтому опять приходится как-то оптимизировать все это дело. Решение простое. Чтобы все быстро просчитывалось и отрисовывалось с достаточно приемлемым числом кадров, наша сцена при просчете ограничивается в размерах. Осуществляется это намеренно. Сколько бы мы ни помещали на сцену обьектов, время просчета будет увеличиваться не так сильно, как при отсутствии такого ограничения. Вспомните про полигоны. Отбрасываются те, которые нам не нужны. Здесь отбрасываются те части сцены, которые не попадают внутрь этого ограничения. В Direct3D роль такого ограничения выполняет камера. То что видно в камеру, то и отрисовывается.
На рисунке видно, что ограничение представляет собой усеченную пирамиду, расположенную к нам вершиной. Помните про три чудесные матрицы? Так вот две из них и определяют камеру. Это "Матрица вида" и "Матрица проекции". В "Матрице вида" мы задаем положение камеры в пространстве и ее направление. А заполнив нужными значениями "Матрицу проекции", мы тем самым определим какого размера будет эта ограничивающая пирамида. Давайте остановимся на "Матрице вида" и разберем ее подробнее. Чтобы правильно заполнить эту матрицу нам необходимо задать значения трех векторов:
Если вы не знаете что такое вектор, то мы сейчас это исправим. Вектор - это направленный отрезок. Т.е. он указывает направление. Вот рисунок:
Направление, которое описывает вектор, получается при взгляде от исходной в конечную точку. В нашем случае каждый вектор имеет только конечную точку. За начальную берется местоположение камеры в пространстве. Задав эти три вектора, мы смело вызываем Direct3D и передаем ему все заботы, связанные с правильным заполнением "Матрицы вида" нужными значениями на их основе. Как всегда есть и альтернативный путь - заполнить матрицу самим, используя всякие преобразования над векторами ( в документации по Direct3D имеется кое - какая информация ), но пока не будем забивать голову и пойдем дальше. "Матрицу вида" разобрали, осталась только "Матрица проекции". За нас эту матрицу тоже будет заполнять Direct3D. Вот какие значения мы должны ему передать:
Поле обзора камеры - это угол в радианах, который способен охватить обьектив камеры ( связано все это с фокусными расстояниями линз и пр. ) Разный угол и по - разному будет выглядеть сцена. Как его правильно выбрать я расскажу далее. Соотношение сторон - это то, в каком отношении находятся горизонталь и вертикаль. В обычном телевизоре соотношение сторон 4:3, т.е. если поделить 4 на 3 то получим 1.333333..... Вот это 1.33333 и нужно передавать Direct3D. Т.е. на вас лежит ответственность за его вычисление. Попробуйте вместо такого указать единицу и вы получите на экране с соотношением сторон 4:3 из квадрата прямоугольник, т.к. Direct3D будет пытаться соблюсти пропорции. Передняя отсекающая плоскость - ближайшая к нам плоскость ( вершина пирамиды ). Задается значением по координате Z. Т.е. полигоны, находящиеся к нам ближе чем эта плоскость, не будут отрисованы. Задняя отсекающая плоскость - тоже самое что и передняя, только наоборот ( основание пирамиды ). В итоге задав три вектора и четыре параметра и вызвав Direct3D, мы получим готовые матрицы для использования. Камера готова ! Ну вот, какой-то там Z буфер еще надо знать. На самом деле это полезнейшая вещь. Если вы все еще не догадываетесь, то Z буфер позволяет Direct3D правильно отображать обьекты на экране. Дело в том, что устройство визуализации рисует обьекты на экране в том порядке, в котором их передавать. Т.е. второй обьект нарисуется поверх первого, третий поверх второго и первого. Ну и что скажете вы? Если у нас статичная сцена, и мы передаем все обьекты в нужном порядке, то никакой Z буфер нам и впомине не нужен. Все правильно. А если сцена вращается, и еще обьекты меняют свое местоположение, что тогда? Тогда Z буфер незаменим. Работает он так:
Говоря простым языком, проверяется на какой глубине в данном месте экрана нарисован пиксель, и если рисуемый пиксель находится к нам ближе чем тот, который уже нарисован, то старый затирается новым. Z буфер имеет такие же размеры что и окно на экране. Если окно 320х240, то и Z буфер будет иметь размер 320х240. Размер значений, которые может содержать Z буфер, варьируются от 16 до 32 бит в зависимости от того, какой формат вы зададите. Соответственно, чем больше формат, тем больше времени будет уходить на его заполнение нейтральным значением. Такое заполнение называется очисткой Z буфера. При использовании буфера придется осуществлять такую очистку перед тем, как рисовать очередной кадр.
7. Все знания применим на деле ! Уфф... Ну как? Сложно? Думаю вы уже готовы все бросить и пойти отдохнуть. Действительно нужно время, чтобы переварить полученную информацию. Математика сложный предмет и с первого раза доходит не до всех. Я сам когда-то долго не мог въехать в некоторые термины :). Но когда приходится что-то изучать для любимого дела, то все понимается и усваивается гораздо быстрей. Итак. Вы знаете что такое трехмерная система координат, вершина, полигон, матрица, вектор, сцена, камера и Z буфер. Теперь с этой информацией можно приступать к непосредственной работе с 3D. В качестве примера я приготовил довольно занятную вещь. Обычные туториалы, которые встречались мне в инете, показывая работу простейшего трехмерного приложения, выводили на экран либо пару треугольников, либо вращающийся квадрат. Стопроцентно вы бы не хотели смотреть на этот примитивизм, да и я тоже. Из простых полигонов можно создать и нечто поинтереснее, не прилагая при этом слишком больших усилий. Вот наглядный пример:
То что вы видите, мы с вами и сделаем. Думаете сложно? Ничего подобного! Немного смекалки и умения. Если руки у вас растут из нужного места, то через пару уроков вы меня переплюнете и для вас не составит труда создать нечто похожее. 7.1 Об используемых библиотеках При использовании в нашем приложении новых функций и методов, нужно подключать новые Inc файлы и библиотеки, тут мы останавливаемся перед выбором: либо подключать одну библиотеку, либо другую. Мне очень нравится, когда существует несколько путей для достижения желаемого. Microsoft'у видно тоже нравится, и специально для нас он приготовил две версии одной и той же библиотеки: d3dx8.lib и d3dx8d.lib. Разница у них не только в наличии буквы d, но и, как я понял, в реализации ( может буква d символизирует о debug версии ? ). Размер D3DX8D.LIB - 53 414 байт, и она содержит только ссылки на D3DX8D.DLL. А вот у D3DX8.LIB размер уже 2 с лишним метра, и она несет исходный текст функций d3dx внутри себя. Поэтому, когда мы пытаемся ее использовать для постройки приложения, текст всех функций, которые мы решили вызывать, включаются в наш exe'шник. К тому же эта библиотека еще ссылается на фунции, которые есть только в MSVCRT.DLL и в ADVAPI32.DLL. И чем больше мы используем разных функций, тем больше наш exe'шник. Исходя из всего выше изложенного выделим основные моменты:
Да... Как ни крути, а все равно плохо. Выбирайте то, что вам по душе, а мне больше нравится вариант с DLL. Именно его я и буду использовать в дальнейшем. Чтобы не пришлось искать эту DLL по инету, я включил ее в архив с исходником. Поэтому при скачивании оного с Wasm.ru, не удивляйтесь большому размеру архива ( сам exe примера занимает 7 кило ). Для работы нам понадобится каркас от предыдущего урока. Как вы помните, он разбит на две части: диалог и само приложение. На форму диалога были добавлены 2 новых ComboBox, 2 простых текстовых метки и рамка. Из нового в диалоге - это возможность выбирать формат Z буфера и количество BackBuffer. BackBuffer определяется просто числом, поэтому из добавленного нами на форму ComboBox берем значение, которое выбрал пользователь, и помещаем в D3DPRESENT_PARAMETERS. А вот на определении формата Z буфера стоит остановится. Всего форматов Z буфера имеется 4 штуки. Нам придется проверять поддержку каждого из них и только затем помещать название этого формата в ComboBox. Сложность заключается в том, что при проверке нужно знать в каком разрешении будет запускатся приложение, а именно число бит на пиксель. Число бит Z буфера при создании устройства Direct3D не может быть больше, чем число бит включаемого разрешения. Например Z буфер имеет 24 бита, а включаемое разрешение 1024х768х16 бит. При таких параметрах нам будет отказано в создании устройства, т.к. 24 больше чем 16. Вот на это и стоит обратить внимание. В самом файле Dialog.asm я объявил новые массивы: это ZbufferFormatTable - содержащий все возможные форматы Z буфера и ZbufferStrTable - содержащий указатели на текстовые строки с названиями этих самых форматов ( да еще сами текстовые строки ). Как и в предыдущем уроке проверяем, поддерживает данный формат ускоритель или нет, и если нужно заносим его название в ComboBox. Проверка осуществляется методом CheckDepthStencilMatch.
Чтобы сообщить Direct3D что мы будем использовать Z буфер, нам еще придется заполнить дополнительно 2 параметра в D3DPRESENT_PARAMETERS. Это поле EnableAutoDepthStencil ( заносим туда TRUE ) и AutoDepthStencilFormat ( заносим туда значение формата Z буфера, который пользователь выбрал в ComboBox ). Все. Теперь наше приложение будет использовать Z буфер. Все подробности смотрите в файле Dialog.asm Чтобы мы могли использовать новые функции, нам необходимо подключить один Inc файл и одну библиотеку. Это d3dx8math.inc и d3dx8d.lib. Данные файлы поддерживают работу с матрицами и векторами, а также еще с несколькими математическими функциями на данный момент нам не нужными. Изменились старые функции: Init_Direct3D, Destroy_Direct3D и Render_Scene. Добавились новые: Set_Render_Parameters, Init_Scene и Animate_Scene. Как обычно, после того как пользователь запустил приложение, мы получаем от диалога полностью заполненную структуру D3DPRESENT_PARAMETERS. На ее основе создаем устройство Direct3DDevice. Далее нам нужно создать камеру ( о ней см. пункт 6 ).
Стандартно UpVector берется равным <0.0f, 1.0f, 0.0f> - т.е он направлен строго вверх. Наклоняя его в ту или иную сторону вы наклоняете камеру. Поле обзора камеры стандартно берется равным D3DX_PI/4. Т.е обычное число "Пи" делится на четыре. AspectRatio вычисляется делением ширины на высоту, и полученное число сохраняется. Для заполнения WorldMatrix стандартно применяется функция D3DXMatrixRotationY с числом радиан равным 0. Получается, что мы хотим повернуть всю сцену на угол 0 по координате Y. В примерах Microsoft используется именно такой подход. Ради интереса я решил посмотреть, какими значениями заполняет эта функция пустую матрицу. Оказывается матрица из пустой просто превращается в единичную ( единичная - это матрица из нулей у которой по главной диагонали стоят единицы ), плюс один из элементов содержит какое-то число. Я попробовал сам заполнить матрицу как единичную, опуская то непонятное число. И что вы думаете? Все работает. По крайней мере для данного урока. Значит матрицы можно смело заполнять самим, не используя функции из библиотеки. Правда никто не гарантирует, что это будет легко ( документация содержит информацию о том как формируются некоторые матрицы ). Если вам интересно, пишите, и я рассмотрю эту тему. ViewMatrix заполняем с помощью функции D3DXMatrixLookAtLH, передавая ей три вектора. А ProjectionMatrix заполняем функцией D3DXMatrixPerspectiveFovLH, передавая ей четыре параметра. Буквы LH в названии обеих функций говорят о том, что матрицы заполняются, исходя из левосторонней системы координат ( см. пункт 1 ). Вызвав все эти функции, мы подготовили камеру к работе. Осталось только заполнить сцену и установить параметры рендеринга. Сделаем это, вызвав написанные нами функции Init_Scene и Set_Render_Parameters. На этом Init_Direct3D заканчивается.
В этой функции я решил поместить подготовку сцены. На скриншоте вы видели некое подобие флага. Как его создать? Текстур мы не используем, все что у нас имеется - это треугольники, которые можно закрасить каким - либо цветом. Что делать? Давайте создадим флаг из квадратов и там, где нужно, закрасим квадраты красным цветом, чтобы получилась надпись. Во - первых, описываем формат нашей вершины. Он будет содержать координаты и цвет.
Затем нам понадобится шаблон для клонирования:
Здесь определено 2 треугольника, а все вместе - квадрат. Нужно размножить этот шаблон в количестве 36 по горизонтали и 10 по вертикали. Всего 360 квадратов или 720 треугольников. Не забудем о надписи. Для ее нанесения используем обычную битовую маску для определения того, где закрашивать нужный квадрат. Надпись WASM.RU укладывается в шесть 32 битных значений. Вот они:
Вопрос с флагом решен. Теперь подумаем, какой фон мы можем сделать. Обычный одноцветный можно получить, используя простую очистку экрана. Фон с градиентной заливкой выглядит привлекательнее. Возьмем еще два полигона, расположим их за флагом и раскрасим как нам нужно. Вот описание фона:
С данными разобрались, и наступило время узнать как же Direct3D с ними работает. Система хранения данных в Direct3D основана на буферах. Существуют разные типы буферов. Например, VertexBuffer - это буфер, содержащий данные вершин. Создав специальным методом нужный буфер, мы должны занести туда данные, и только затем указывать, что из него рисовать. После создания к буферу можно обращаться и менять какие - либо данные в нем. Переопределять размер в большую или меньшую сторону, как я знаю, нельзя ( тема рассмотрена мной мало, возможно это как-то все-таки осуществляется ). Вообще, ОЧЕНЬ не рекомендуется производить громоздкие манипуляции с буфером во время отрисовки сцены. Записывать данные еще можно, а вот считывать из него ... крайне не приветствуется. Создается VertexBuffer методом CreateVertexBuffer. Для нашего примера создание буфера выглядит так:
После создания буфера, доступ к нему осуществляется посредством блокирования. Оно необходимо, так как буфер может находится где угодно. В памяти на самом акселераторе, в AGP памяти, либо где-то еще. Методом Lock блокируем, а разблокирование осуществляется методом Unlock. Когда буфер блокирован, ускоритель не может с ним работать, учтите это хотя возможны и исключения. Для нашего примера мы создаем буфер такого размера, чтобы в него как раз уместилось все необходимое. Затем блокируем его и начинаем заносить данные. Внимательно следите за размером заносимого, если он превысит размер буфера, то приложение вылетит с ошибкой о записи в недопустимое место памяти.
Так как трехмерная графика основана на цифрах с плавающей запятой, то без сопроцессора не обойтись. При клонировании мы просто добавляем нужное число по какой - либо координате. У меня прибавление по X идет, начиная от 0 в плюсовую сторону. По Y от 0 в минусовую сторону. В итоге флаг займет нижнюю правую четверть экрана. Почему так? Потому что начало трехмерной системы координат располагается по центру экрана. Если вы хотите иметь какое - то другое расположение системы координат, то вам придется или сдвигать камеру в нужную сторону, или заполнять "Мировую Матрицу" нужными данными. Я просто сдвинул камеру и все. Мы не используем освещение, но можно его немного симмитировать. В цикле клонирования очередного квадрата по координате X заносится цвет более темного оттенка, чем у его предыдущего собрата.
Посредством сдвига битовой маски определяем какой квадрат закрасить, одновременно увеличивая оттенок квадрата по координате X. Так как каждый квадрат состоит из двух треугольников, то всего надо заполнить цветом шесть вершин. Вообще Direct3D по умолчанию использует градиентную заливку, если у треугольника для каждой из вершин указать разный цвет, то он будет нарисован с плавными переходами из одного цвета в другой. Нам же нужен квадрат одного цвета, поэтому и приходится заносить одинаковый цвет во все шесть вершин. Посмотрите на то, в какой последовательности я заношу данные в VertexBuffer. Сначала данные фона, а затем данные флага. Помните, в шестом пункте про Z буфер я упоминал, что все данные отрисоваются в порядке их следования? Фон находится дальше чем флаг, правильно? Значит нам по идее Z буфер вообще не нужен. Попробуйте его отключить, и посмотрите что произойдет. Все будет нарисовано как ни в чем не бывало. Используя такие вот особенности, можно увеличить скорость прорисовки, а именно FPS. Старайтесь отключать все, что только можно, и использовать лишь то, что действительно необходимо. Все. Сцену можно рисовать в функции Render_Scene. Хотелось бы еще остановится на одном важном моменте. Всегда записывайте на бумажку размер вашей вершины, чтобы не забыть. Исходя из него, нужно подсчитывать, сколько будет занимать один полигон, затем сколько будет занимать группа полигонов. Расчеты должны быть точными, если вы ошибетесь и, при создании буфера, его заполнении или при отрисовке, неправильно укажете данные, то рискуете ничего не увидеть на экране. Будьте очень внимательны.
7.6. Функция Set_Render_Parameters Все, что содержит данная функция, можно было поместить в Init_Direct3D. Почему этого не было сделано, вы сейчас поймете. В уроке номер 2 я рассказывал о проблеме потерянного устройства. При возвращении в наше приложение по Alt+Tab нам нужно сбрасывать устройство. При этом теряется ВСЯ информация, связанная с ним, и нам необходимо ее восстанавливать. В этой функции содержатся все настройки касательно параметров рендеринга. Мы можем легко их восстановить, поместив вызов этой функции сразу после сбрасывания устройства.
Метод SetTransform позволяет устанавливать в механизме визуализации, все связанное с трансформацией. Вызвав его, мы окончательно даем знать Direct3D, что для просчета сцены будем использовать те три матрицы, которые заполнили ранее. Метод SetRenderState нужен для включения и выключения тех или иных настроек рендеринга. Он является практически незаменимым, так как с его помощью устанавливается большинство параметров. Использовать этот метод очень просто. При вызове указывается нужный нам обьект или свойство механизма визуализации и что с ним нужно сделать. Так как в нашем примере нет обьемных тел, то мы можем отключить проверку следования вершин в полигоне: D3DRS_CULLMODE, D3DCULL_NONE. Также мы не используем освещение: D3DRS_LIGHTING, NULL. Но мы используем Z буфер: D3DRS_ZENABLE, D3DZB_TRUE. Методом SetVertexShader мы указываем, что будет использоваться тот формат вершин, который мы определили сами. Методом SetStreamSource устанавливаем, что исходные данные для рендеринга нужно брать из буфера вершин, а число 16 означает размер нашей вершины в байтах.
Самая главная функция. В ней происходит вызов Direct3D, чтобы он отрендерил сцену, а затем показал на экран.
Самое главное, обратите внимание, где происходит вызов Set_Render_Parameters. Если его не произвести при возвращении по ALT+TAB, то приложение вылетит. Еще раз повторюсь, вы не должны забывать, что при сбросе устройства теряются практически ВСЕ установки, за редким исключением. Старайтесь определить все настройки в какое - либо одно место, чтобы при сбросе вам было легко их восстанавливать. В предыдущих уроках мы использовали только очистку экрана цветом. Теперь с использованием Z буфера, нам необходимо очищать также и его. Соответственно в вызываемом нами методе Clear добавляем флаг D3DCLEAR_ZBUFFER и само значение для заполнения Zvalue. Стандартно Z буфер очищается значением 1.0f Так как вся наша сцена состоит из однотипных треугольников, то ее можно отрисовать всего лишь одним методом DrawPrimitive. Этот метод служит для отрисовки определенного числа примитивов. В качестве параметров ( для нашей сцены ) ему нужно передать следующее: D3DPT_TRIANGLELIST - у нас одиночные треугольники, каждый из них задан тремя вершинами, начинать нужно с нулевого по счету, всего рисовать n-треугольников. Метод позволяет рисовать и выборочно какую-либо группу треугольников, достаточно указать с какого начинать + количество. Ну и как обычно, все показывается на экран методом Present. Удивительно что на подготовку всей сцены и установку параметров уходит довольно много строк кода, а на отрисовку нужен всего лишь один метод. Для маленьких сцен это обычное дело, но для чего-то навороченного так легко будет не отделаться. Да, чуть не забыл, перед тем как начинать отрисовывать сцену, всегда должен вызываться метод BeginScene, а в конце, когда уже все передали для просчета, метод EndScene. Т.е все команды для рисования должны находиться между этими двумя методами. Почему этого нет у меня? В документации написано, что если эти методы не будут вызваны, то Direct3D сам принудительно вызовет их. Не указано только, для каждого рисующего метода это будет сделано или нет. Так как у нас используется всего лишь один рисующий метод, то я решил выкинуть BeginScene и EndScene, ведь все равно Direct3D вызовет их за меня. В дальнейшем, когда я буду использовать много рисующих методов подряд, тогда и всякие BeginScene придется использовать, а пока все и так отлично работает. Если возможно сократить обьем вызываемого, почему не сделать этого, да и размер программы меньше :).
Довольно сносный флаг у нас уже имеется. Но где вы видели абсолютно плоский флаг висящий в пространстве. Анимируем наш флаг, чтобы он смотрелся еще лучше. Обычная функция Sin или Cos поможет нам в этом. Конечно супер реалистичного эффекта мы не получим, но кое-что несомненно. Предлагаю вычислять значение Sin на определенной координате X и применять к координате Z. Получится, что квадраты, составляющие флаг, будут циклически плавать по Z. Вот исходный код:
Чтобы была плавная анимация, нам необходимо обновлять значения, ориентируясь на время. Я использовал функцию GetTickCount. Можно использовать функцию timeGetTime из библиотеки winmm, говорят она будет поточнее. Обновление значений у меня происходит только 20 раз в секунду, этого вполне хватает. Как вы помните, при занесении данных в буфер нам нужно его блокировать, так вот здесь он блокируется также 20 раз в секунду. Все остальное время он не трогается. Приложение при этом рисует его с максимальным количеством кадров, которое возможно, т.е я никак не ограничиваю число кадров ( при входе в функцию Animate_Scene просто проверяется наступило время обновления или нет. Если нет тогда сразу выход. ). Также одновременно обновляется значение цвета у фона. Итого, фон меняет окраску, а флаг развевается :) То, что мы здесь осуществили, является вертексной анимацией. Наверное слышали о такой? Как видите, совсем не сложно. Уничтожаем все обьекты относящиеся к Direct3D.
При уничтожении обьектов рекомендуется соблюдать порядок. Не думаю, что, если вы сразу уничтожите обьект Direct3D, то остальные будут уничтожены автоматически. Хотя возможно всякое. Кто его знает, что было у разработчиков на уме :). Урок получился длинный. И хотя я старался расписать все как можно подробней, возможно некоторое сумбурное изложение с моей стороны. Для окончания позволю себе закрепить весь материал вкратце.
PS: В следующем уроке будет рассмотрена возможность использования обычного шрифта для вывода различной информации, ну и, может, в качестве примера подсчет FPS. Как всегда исходник прилагается. Все вопросы мыльте сюда mybox@aib.ru |