DirectX 8.1 в MASM32: Урок 4 — Архив WASM.RU
Урок 4. Первое полноценное трехмерное приложение.
Основы трехмерной графики. Как это все работает. Я расскажу вам как устроен механизм визуализации в DirectX, и вы научитесь использовать в своей работе простые полигоны.
- Система координат
- Формат вершин
- Полигоны
- Что такое сцена. Как Direct3D работает с трехмерным миром.
- Камера
- Z буфер
- Все знания применим на деле !
- Послесловие
Предисловие
Как вы знаете, вся трехмерная графика основана на математике. "Линейная алгебра", "Векторная алгебра и начала анализа", "Начертательная геометрия" и еще много чего другого необходимо усвоить прежде чем приступать к успешной работе с 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 - гибкий формат вершин. Выбор формата зависит от тех задач, которые вы перед собой ставите. Он может состоять из нескольких флагов. Каждый флаг в отдельности описывает каким свойством будет обладать наша вершина. Допускается объединение сколько угодно таких флагов, если они не противоречат друг другу. Соответственно не забывайте, что чем больше флагов вы используете в формате вершины, тем больший размер она будет занимать в памяти.
Например, часто используемый формат содержит:
Код (Text):
D3DFVF_XYZ - координаты вершины D3DFVF_NORMAL - вектор для расчета освещения D3DFVF_DIFFUSE - цвет для закраски D3DFVF_TEX2 - координаты текстуры Все это занимает: (4+4+4)+(4+4+4)+4+(4+4) = 36 байт.Теперь вы видите, что всего лишь на одну вершину при таком формате будет затрачено не так и мало памяти.
Хорошо. Вы выбрали формат, который подходит для ваших целей. Как его описать? Делается это так: D3DFVF_CUSTOMVERTEX equ D3DFVF_DIFFUSE or D3DFVF_XYZ or D3DFVF_NORMAL и т.д
D3DFVF_CUSTOMVERTEX - это простая константа, определенная вами, которая позволяет объединять все нужные свойства для вершины. Далее D3DFVF_CUSTOMVERTEX можно уже указывать в качестве параметра в нужной нам функции. Можно D3DFVF_CUSTOMVERTEX и не использовать, но тогда в каждой функции, где необходимо указывать формат вершины, нужно будет перечислять все флаги, что не очень удобно. Пока остановимся на этом. Далее в тексте статьи мы еще поговорим о вершинах.
Код (Text):
Справка Все возможные флаги для формата вершины: ---------------------------------------- 1. D3DFVF_DIFFUSE - вершина содержит компоненту цвета. 2. D3DFVF_NORMAL - вершина содержит нормаль. Нельзя применять с D3DFVF_XYZRHW 3. D3DFVF_PSIZE - вершина специфичного формата. 4. D3DFVF_SPECULAR - вершина содержит Specular компоненту. 5. D3DFVF_XYZ - нетрансформированная вершина содержит позицию. Нельзя использовать вместе с D3DFVF_XYZRHW 6. D3DFVF_XYZRHW - трансформированная вершина содержит позицию Нельзя использовать вместе с D3DFVF_XYZ и D3DFVF_NORMAL 7. D3DFVF_XYZB1 - содержит позицию и значение Beta. ... Beta используется при мультиматричных блендинг D3DFVF_XYZB5 ( смешивание ) операциях. На данный момент поддерживается три значения Beta и четыре блендинг матрицы. Все возможные флаги, относящиеся к текстуре: ------------------------------------------- 1. D3DFVF_TEX0 - вершина содержит "n" число текстурных координат ... D3DFVF_TEX8 2. D3DFVF_TEXTUREFORMAT1 - указывает какой размерности текстурная координата ... Например: D3DFVF_TEXTUREFORMAT1 - одномерная, D3DFVF_TEXTUREFORMAT4 D3DFVF_TEXTUREFORMAT2 - двумерная и т.д. На данный момент эта константа редко используется Значения масок: --------------- 1. D3DFVF_POSITION_MASK - битовая маска позиций 2. D3DFVF_RESERVED0 - маска зарезервированных битов в формате вершины. и На данный момент не используется D3DFVF_RESERVED2 3. D3DFVF_TEXCOUNT_MASK - битовая маска для текстурных флагов. Прочее: ------- 1. D3DFVF_LASTBETA_UBYTE4 - когда используется индексное вершинное смешение ( vertex blending ) и фиксирована функция FVF вертексных шейдеров, обязательно необходимо указать этот флаг для вертексного шейдера 2. D3DFVF_TEXCOUNT_SHIFT - число бит, на которое сдвинуто значение идентифицирующее число текстурных координат для вершины. Несколько примеров: ------------------- 1. Легкая нетрансформированная и нетекстурированная вершина. Закраска по Гуро. D3DFVF_CUSTOMVERTEX equ D3DFVF_DIFFUSE or D3DFVF_XYZ 2. Легкая нетрансформированная и нетекстурированная вершина. Закраска по Гуро. Используется освещение. D3DFVF_CUSTOMVERTEX equ D3DFVF_DIFFUSE or D3DFVF_XYZ or D3DFVF_NORMAL 3. Нетрансформированная вершина использующая текстуру. Освещение отсутствует. D3DFVF_CUSTOMVERTEX equ D3DFVF_XYZ or D3DFVF_TEX2 4. Трансформируемая вершина с RHW частью, использующая текстуру D3DFVF_CUSTOMVERTEX equ D3DFVF_XYZRHW or D3DFVF_TEX2 5. Тяжелая вершина с компонентой цвета, координатами текстуры, использующая освещение. D3DFVF_CUSTOMVERTEX equ D3DFVF_XYZ or D3DFVF_NORMAL or D3DFVF_DIFFUSE or / D3DFVF_SPECULAR or D3DFVF_TEX2Формат вершины уже не является для вас тайной. Ну а как же составить из разрозненных вершин какой либо обьект ? Все объекты состоят из треугольников, скажете вы и будете правы. Конечно в Direct3D можно использовать и другие фигуры, число углов у которых больше чем три. Это квадраты и многоугольники. Но, так как аппаратная часть ускорителя оптимизирована на работу с треугольниками, то перед отрисовкой ваших квадратов они все равно будут разбиты на треугольники, так что сэкономив на простоте описания обьекта, можно проиграть в скорости. Как и что лучше использовать, решать вам.
А почему выбрали именно треугольник ? Потому что он является самой простой фигурой и всегда выпуклый. А раз он выпуклый, то упрощаются различные расчеты при работе с ним ( пересечение прямой, лежит ли точка внутри него и т.д. ) Вы это поймете сами, когда еще более углубитесь в работу с 3D.
Все фигуры в трехмерной графике называют "полигонами" - если перевести буквально, то это означает многоугольник. Но так как используют практически одни треугольники, то и название полигон стало синонимом слова треугольник. И когда вы видите в какой - либо статье про новую игрушку, что в модели монстра используется 5000 полигонов, то это значит, что он состоит из 5000 треугольников.
Сейчас давайте рассмотрим, как описываются полигоны. Для быстроты просчета сцены ( это определение я раскрою чуть дальше ) в Direct3D ( да и в любом другом 3D API ) применяется схема, когда невидимые полигоны отбрасываются и просчитываются только те, которые мы с вами видим. Как же Direct3D может узнать какие полигоны рисовать, а какие нет? Все дело в том как они заданы. И огромную роль в этом играет порядок следования вершин в описании полигона. Догадались? Правильно. Они должны быть заданы либо по часовой стрелке, либо против. Стандартом является то, когда вершины, составляющие полигон, заданы по часовой стрелке. В Direct3D этот режим включен по умолчанию. Т.е. он будет отрисовывать только эти полигоны, а все другие, где вершины описаны против часовой стрелки, отбрасывать. Нижеследующий рисунок поможет вам прояснить все моменты.
Чтобы было еще более понятно, разберем пример. У нас есть куб. Он повернут к нам одной гранью. Начинаем писать вершины этой грани по часовой стрелке. Начинать можно с любой. Когда мы написали эти четыре вершины друг за дружкой, то поворачиваем к себе куб новой гранью и опять пишем вершины по часовой стрелке и т.д. пока все грани не будут описаны. Если теперь внимательно подумать и посмотреть в каком порядке будут находиться вершины той грани, которая повернута от нас, то окажется, что они будут расположены против часовой стрелки ( если смотреть на эту грань сквозь куб ). Вот и весь фокус. Соответственно раз они против часовой стрелки и грань куба нам действительно не видно, то и рисовать ее не надо, что и делает с успехом Direct3D ( с вашей помощью, конечно ). Если вы собираетесь вручную описывать обьекты для дальнейшего использования в программе, то от этой процедуры вам не избавиться как бы этого ни хотелось. Ну а если использовать 3D редактор, то он все сделает за вас автоматически и опишет каждый полигон в нужном порядке. Также довожу до вашего сведения, что в Direct3D в любой момент можно поменять условие, по которому будет определяться какие полигоны отбрасывать. Те которые по часовой стрелке или против. В некоторых случаях это действительно необходимо, но об этом как - нибудь в другой раз.
Теперь когда вы знаете о том, что такое полигоны и как их нужно описывать, пора переходить к следующей части.
4. Что такое сцена. Как Direct3D работает с трехмерным миром.
В трехмерной графике есть такое понятие как сцена. Вы были в театре и видели там сцену, на которой выступают актеры. Так вот здесь это тоже самое. На ней также происходят различные действия. Все обьекты, с которыми вы работаете в данный момент, манипулируете и пытаетесь отрисовать, и представляют собой в совокупности сцену. Простым языком, сцена - это то, что мы в данный момент видим на экране.
Допустим, мы имеем обьект из полигонов на сцене. Как мы можем им манипулировать, перемещать, вращать вокруг оси и т.д? Не забудьте при этом, что все присутствующее на сцене распологается в трехмерной системе координат. Для перемещения вдоль какой - либо оси достаточно изменить координаты каждой из вершин в ту или иную сторону. Для поворота все уже намного сложнее. Тут нужно применять формулы, основаные на синусе и косинусе. Все они уже давно написаны математиками и с блеском работают. Главное их неудобство заключается в громоздкости и в том, что для поворота вокруг нескольких осей сразу, нужно применять эти формулы по очереди для каждой оси в отдельности. Результат может оказаться не таким, какой мы ожидали ( думаю, то, что они применяются для каждой вершины отдельно, обьяснять вам не нужно ). Для избавления от этого недостатка была придумана, так называемая, однородная система координат, где все действия над обьектами было очень удобно производить с помощью МАТРИЦ. Если хотели произвести сразу много действий, то просто перемножали матрицы нужных операций и результирующую матрицу, содержащую в себе все преобразования, уже применяли к вершинам всего обьекта. Удобно и быстро. И теперь все системы просчета 3D графики работают на матрицах.
Что представляет собой матрица? Это обычная числовая таблица из строк и столбцов. Вот как она выглядит:
Код (Text):
|а11 a12 a13 a14|- матрица 4х4. Всего 16 элементов. Каждый элемент имеет |a21 a22 a23 a24| обозначение. Буквой обозначается элемент, а цифрами место |a31 a32 a33 a34| расположения в матрице. Например "a23" - это элемент, |a41 a42 a43 a44| стоящий на второй строке в третьем столбце.Именно такие матрицы 4x4 и использует в своей работе Direct3D. Описываются они обычным массивом из 16 элементов, размер каждого элемента 4 байта ( Real4 ). Соответственно, размер в памяти, который занимает одна матрица равен 64 байтам.
Существуют три основные матрицы, которые мы должны знать:
- World Matrix (мировая матрица)
- View Matrix (матрица вида)
- Matrix Projection (матрица проекции).
"Мировая матрица" отвечает за преобразование всей сцены (вращение вокруг осей, перемещение и т.д.). Именно всей, а не какого - либо отдельного обьекта на ней. "Матрица вида" отвечает за то, какая часть сцены будет видна на экране. "Матрица проекции" отвечает за то, как трехмерные координаты будут преобразованы в двумерные для отображения на экран.
Эти три матрицы и являются тем чудесным средством, с помощью которого Direct3D превращает трехмерный мир в плоскую картинку на вашем мониторе.
Хоть трехмерный мир почти и бесконечен, но вот возможности ускорителя и процессора далеко не безразмерные. Если просчитать пару тысяч полигонов не проблема, то десятки и тем более сотни тысяч за раз подчас является трудоемкой задачей, даже для самых последних hi-end-овых ускорителей ( естественно всякие там Cray не в счет ). Поэтому опять приходится как-то оптимизировать все это дело. Решение простое. Чтобы все быстро просчитывалось и отрисовывалось с достаточно приемлемым числом кадров, наша сцена при просчете ограничивается в размерах. Осуществляется это намеренно. Сколько бы мы ни помещали на сцену обьектов, время просчета будет увеличиваться не так сильно, как при отсутствии такого ограничения. Вспомните про полигоны. Отбрасываются те, которые нам не нужны. Здесь отбрасываются те части сцены, которые не попадают внутрь этого ограничения.
В Direct3D роль такого ограничения выполняет камера. То что видно в камеру, то и отрисовывается.
На рисунке видно, что ограничение представляет собой усеченную пирамиду, расположенную к нам вершиной. Помните про три чудесные матрицы? Так вот две из них и определяют камеру. Это "Матрица вида" и "Матрица проекции".
В "Матрице вида" мы задаем положение камеры в пространстве и ее направление. А заполнив нужными значениями "Матрицу проекции", мы тем самым определим какого размера будет эта ограничивающая пирамида.
Давайте остановимся на "Матрице вида" и разберем ее подробнее. Чтобы правильно заполнить эту матрицу нам необходимо задать значения трех векторов:
- EyeVector - координаты камеры в пространстве
- LookAtVector - указывает куда направлен обьектив камеры
- UpVector - указывает верх камеры
Если вы не знаете что такое вектор, то мы сейчас это исправим. Вектор - это направленный отрезок. Т.е. он указывает направление. Вот рисунок:
Направление, которое описывает вектор, получается при взгляде от исходной в конечную точку. В нашем случае каждый вектор имеет только конечную точку. За начальную берется местоположение камеры в пространстве.
Задав эти три вектора, мы смело вызываем Direct3D и передаем ему все заботы, связанные с правильным заполнением "Матрицы вида" нужными значениями на их основе. Как всегда есть и альтернативный путь - заполнить матрицу самим, используя всякие преобразования над векторами ( в документации по Direct3D имеется кое - какая информация ), но пока не будем забивать голову и пойдем дальше.
"Матрицу вида" разобрали, осталась только "Матрица проекции". За нас эту матрицу тоже будет заполнять Direct3D. Вот какие значения мы должны ему передать:
- FieldOfView - поле обзора камеры
- AspectRatio - соотношение сторон
- NearViewPlan - передняя отсекающая плоскость
- FarViewPlan - задняя отсекающая плоскость
Поле обзора камеры - это угол в радианах, который способен охватить обьектив камеры ( связано все это с фокусными расстояниями линз и пр. ) Разный угол и по - разному будет выглядеть сцена. Как его правильно выбрать я расскажу далее.
Соотношение сторон - это то, в каком отношении находятся горизонталь и вертикаль. В обычном телевизоре соотношение сторон 4:3, т.е. если поделить 4 на 3 то получим 1.333333..... Вот это 1.33333 и нужно передавать Direct3D. Т.е. на вас лежит ответственность за его вычисление. Попробуйте вместо такого указать единицу и вы получите на экране с соотношением сторон 4:3 из квадрата прямоугольник, т.к. Direct3D будет пытаться соблюсти пропорции.
Передняя отсекающая плоскость - ближайшая к нам плоскость ( вершина пирамиды ). Задается значением по координате Z. Т.е. полигоны, находящиеся к нам ближе чем эта плоскость, не будут отрисованы.
Задняя отсекающая плоскость - тоже самое что и передняя, только наоборот ( основание пирамиды ).
В итоге задав три вектора и четыре параметра и вызвав Direct3D, мы получим готовые матрицы для использования. Камера готова !
Ну вот, какой-то там Z буфер еще надо знать. На самом деле это полезнейшая вещь. Если вы все еще не догадываетесь, то Z буфер позволяет Direct3D правильно отображать обьекты на экране. Дело в том, что устройство визуализации рисует обьекты на экране в том порядке, в котором их передавать. Т.е. второй обьект нарисуется поверх первого, третий поверх второго и первого. Ну и что скажете вы? Если у нас статичная сцена, и мы передаем все обьекты в нужном порядке, то никакой Z буфер нам и впомине не нужен. Все правильно. А если сцена вращается, и еще обьекты меняют свое местоположение, что тогда? Тогда Z буфер незаменим.
Работает он так:
- В начале отрисовки каждого кадра Z буфер заполняется нейтральным значением.
- Когда просчитывается полигон, то его надо рисовать на экране. Он состоит из множества пикселей. У каждого из них есть координаты. Direct3D берет Z координату пикселя и сравнивает с тем значением, которое лежит в Z буфере. Если значение в Z буфере больше, то на его место заносится Z координата пикселя, а сам пиксель рисуется, если нет, то ничего делать не надо.
- Берется следующий полигон. Выполняется пункт b). Так до тех пор пока все не будет отрисовано.
Говоря простым языком, проверяется на какой глубине в данном месте экрана нарисован пиксель, и если рисуемый пиксель находится к нам ближе чем тот, который уже нарисован, то старый затирается новым.
Z буфер имеет такие же размеры что и окно на экране. Если окно 320х240, то и Z буфер будет иметь размер 320х240. Размер значений, которые может содержать Z буфер, варьируются от 16 до 32 бит в зависимости от того, какой формат вы зададите. Соответственно, чем больше формат, тем больше времени будет уходить на его заполнение нейтральным значением. Такое заполнение называется очисткой Z буфера. При использовании буфера придется осуществлять такую очистку перед тем, как рисовать очередной кадр.
Код (Text):
Справка Все возможные форматы Z буфера: ------------------------------- 1. D3DFMT_D16 - 16 битный. 2. D3DFMT_D16_LOCKABLE - 16 битный. Блокируемый приложением. 3. D3DFMT_D24X8 - 32 битный использующий только 24 бита. 4. D3DFMT_D32 - 32 битный.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'шник. Исходя из всего выше изложенного выделим основные моменты:
Код (Text):
При использовании D3DX8D.LIB ---------------------------- 1. Нужна дополнительно D3DX8D.DLL ( размер около 700 кило ) 2. В поставку DirectX 8.1 она не входила. И тот пакет, который у вас установлен на компе, чтобы играть в игрушки, ее не содержит. 3. Используя ее, вам придется распостранять свои творения вместе с этой DLL 4. Размер получаемого exe'шника такой, который вы сделали сами. При использовании D3DX8.LIB ---------------------------- 1. Никакой дополнительной DLL не требуется. 2. На стадии компиляции приложения необходимо подключать MSVCRT.lib и ADVAPI32.lib 3. Размер exe'ника вырастает пропорционально количеству используемых в нем функций, начиная от 150 килобайт и выше ( начальный размер зависит от версии Msvcrt.lib )Да... Как ни крути, а все равно плохо. Выбирайте то, что вам по душе, а мне больше нравится вариант с 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.
Код (Text):
Справка CheckDepthStencilMatch передается 5 параметров. ----------------------------------------------- 1. Номер адаптера. D3DADAPTER_DEFAULT - адаптер по умолчанию. 2. Тип адаптера. D3DDEVTYPE_HAL - аппаратный. D3DDEVTYPE_SW - программный. 3. Формат поверхности адаптера ( какой на экране ). 4. Формат поверхности BackBuffer. 5. Формат Z буфера. ------------------------------------------------------- Если все прошло успешно, метод возвращает D3D_OK. Если нет, то D3DERR_INVALIDCALL или D3DERR_NOTAVAILABLE.Чтобы сообщить 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 ).
Код (Text):
Создаем камеру -------------- .DATA EyeVector D3DVECTOR <0.0f, 0.0f, -2.6f> ; Координаты камеры LookAtVector D3DVECTOR <3.0f, -1.0f, 3.0f> ; Куда смотрит камера UpVector D3DVECTOR <0.0f, 2.0f, -0.8f> ; Указывает верх камеры FieldOfView dd 0.7853981635f ; Поле обзора камеры AspectRatio dd NULL ; AspectRatio NearViewPlanZ dd 1.0f ; Передняя отсекающая плоскость FarViewPlanZ dd 10.0f ; Задняя отсекающая плоскость .DATA? WorldMatrix D3DMATRIX <?> ; Мировая матрица ViewMatrix D3DMATRIX <?> ; Матрица вида ProjectionMatrix D3DMATRIX <?> ; Матрица проекции .CODE fild d3dpp.BackBufferWidth ; Делим ширину на высоту fild d3dpp.BackBufferHeight fdiv fstp AspectRatio invoke D3DXMatrixRotationY, ADDR WorldMatrix, NULL invoke D3DXMatrixLookAtLH, ADDR ViewMatrix, ADDR EyeVector, \ ADDR LookAtVector, ADDR UpVector invoke D3DXMatrixPerspectiveFovLH, ADDR ProjectionMatrix, \ FieldOfView, AspectRatio, NearViewPlanZ, FarViewPlanZСтандартно 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 заканчивается.
Код (Text):
Справка D3DXMAtrixRotationY передается 2 параметра. ------------------------------------------- 1. Адрес матрицы в памяти. 2. Угол в радианах. D3DXMAtrixLookAtLH передается 4 параметра. ------------------------------------------ 1. Адрес матрицы в памяти. 2. Адрес EyeVector 3. Адрес LookAtVector 4. Адрес UpVector D3DXMatrixPerspectiveFovLH передается 5 параметров. -------------------------------------------------- 1. Адрес матрицы в памяти. 2. Поле обзора 3. AspectRatio 4. Значение переднего плана 5. Значение заднего планаВ этой функции я решил поместить подготовку сцены. На скриншоте вы видели некое подобие флага. Как его создать? Текстур мы не используем, все что у нас имеется - это треугольники, которые можно закрасить каким - либо цветом. Что делать? Давайте создадим флаг из квадратов и там, где нужно, закрасим квадраты красным цветом, чтобы получилась надпись.
Во - первых, описываем формат нашей вершины. Он будет содержать координаты и цвет.
Код (Text):
.CONST D3DFVF_CUSTOMVERTEX equ D3DFVF_XYZ OR D3DFVF_DIFFUSEЗатем нам понадобится шаблон для клонирования:
Код (Text):
.DATA Vertices dd 0.0f , 0.0f , 0.0f , 0d1d0e7h ; 4*4 байт на вершину dd 0.0f , 0.1f , 0.0f , 0d1d0e7h ; 12*4 байт на полигон dd 0.1f , 0.1f , 0.0f , 0d1d0e7h ; 24*4 байт на квадрат dd 0.0f , 0.0f , 0.0f , 0d1d0e7h dd 0.1f , 0.1f , 0.0f , 0d1d0e7h dd 0.1f , 0.0f , 0.0f , 0d1d0e7hЗдесь определено 2 треугольника, а все вместе - квадрат. Нужно размножить этот шаблон в количестве 36 по горизонтали и 10 по вертикали. Всего 360 квадратов или 720 треугольников.
Не забудем о надписи. Для ее нанесения используем обычную битовую маску для определения того, где закрашивать нужный квадрат. Надпись WASM.RU укладывается в шесть 32 битных значений. Вот они:
Код (Text):
.DATA Text dd 10001001100011001000100011001001b ; Надпись WASM.RU dd 10001010010100001101100010101001b dd 10101010010011001010100010101001b dd 10101010010000101000100011001001b dd 10101011110100101000100010101001b dd 01010010010011001000101010100110bВопрос с флагом решен. Теперь подумаем, какой фон мы можем сделать. Обычный одноцветный можно получить, используя простую очистку экрана. Фон с градиентной заливкой выглядит привлекательнее. Возьмем еще два полигона, расположим их за флагом и раскрасим как нам нужно. Вот описание фона:
Код (Text):
.DATA Fon dd -1.0f, -6.0f, 1.5f, 0f1f0e7h ; Фон - 2 полигона с цветом dd -1.0f, 2.0f, 1.5f, 0b1b0a7h ; для градиентной заливки dd 9.0f, 2.0f, 1.5f, 0f1f0e7h dd -1.0f, -6.0f, 1.5f, 0f1f0e7h dd 9.0f, 2.0f, 1.5f, 0f1f0e7h dd 9.0f, -6.0f, 1.5f, 0FFFFFFhС данными разобрались, и наступило время узнать как же Direct3D с ними работает. Система хранения данных в Direct3D основана на буферах. Существуют разные типы буферов. Например, VertexBuffer - это буфер, содержащий данные вершин. Создав специальным методом нужный буфер, мы должны занести туда данные, и только затем указывать, что из него рисовать. После создания к буферу можно обращаться и менять какие - либо данные в нем. Переопределять размер в большую или меньшую сторону, как я знаю, нельзя ( тема рассмотрена мной мало, возможно это как-то все-таки осуществляется ). Вообще, ОЧЕНЬ не рекомендуется производить громоздкие манипуляции с буфером во время отрисовки сцены. Записывать данные еще можно, а вот считывать из него ... крайне не приветствуется. Создается VertexBuffer методом CreateVertexBuffer. Для нашего примера создание буфера выглядит так:
Код (Text):
Создание Vertex буфера: ---------------------------------------- .CODE d3dev8 CreateVertexBuffer, pd3dDev, (24*4)*360+24*4, D3DUSAGE_WRITEONLY, \ D3DFVF_CUSTOMVERTEX , D3DPOOL_MANAGED, ADDR pd3dVertexBuffer d3dvb8 Lock1, pd3dVertexBuffer, NULL, CountData, ADDR pLockBuffer, NULLПосле создания буфера, доступ к нему осуществляется посредством блокирования. Оно необходимо, так как буфер может находится где угодно. В памяти на самом акселераторе, в AGP памяти, либо где-то еще. Методом Lock блокируем, а разблокирование осуществляется методом Unlock. Когда буфер блокирован, ускоритель не может с ним работать, учтите это хотя возможны и исключения.
Для нашего примера мы создаем буфер такого размера, чтобы в него как раз уместилось все необходимое. Затем блокируем его и начинаем заносить данные. Внимательно следите за размером заносимого, если он превысит размер буфера, то приложение вылетит с ошибкой о записи в недопустимое место памяти.
Код (Text):
В первую очередь занесем данные фона: ------------------------------------- lea esi, Fon mov edi, pLockBuffer mov ecx, 24 rep movsd Следом клонируем данные флага посредством циклов: ------------------------------------------------- .DATA xplus dd 0.105f yplus dd 0.0f shiftx dd 0.105f shifty dd -0.105f .CODE push edi mov esi, edi push 10 pop ecx nextVertices3: xor ebx, ebx push ecx push 36 pop ecx nextVertices2: push ecx lea edi, Vertices push 6 pop ecx nextVertex: fld DWORD PTR [edi] fadd xplus fstp DWORD PTR [esi] fld DWORD PTR [edi+4] fadd yplus fstp DWORD PTR [esi+4] push DWORD PTR [edi+8] pop DWORD PTR [esi+8] mov eax, DWORD PTR [edi+12] sub eax, ebx mov DWORD PTR [esi+12], eax add esi, 16 add edi, 16 loop nextVertex add ebx, 00030303h fld xplus fadd shiftx fstp xplus pop ecx loop nextVertices2 push shiftx pop xplus fld yplus fadd shifty fstp yplus pop ecx dec ecx jnz nextVertices3Так как трехмерная графика основана на цифрах с плавающей запятой, то без сопроцессора не обойтись. При клонировании мы просто добавляем нужное число по какой - либо координате. У меня прибавление по X идет, начиная от 0 в плюсовую сторону. По Y от 0 в минусовую сторону.
В итоге флаг займет нижнюю правую четверть экрана. Почему так? Потому что начало трехмерной системы координат располагается по центру экрана. Если вы хотите иметь какое - то другое расположение системы координат, то вам придется или сдвигать камеру в нужную сторону, или заполнять "Мировую Матрицу" нужными данными. Я просто сдвинул камеру и все.
Мы не используем освещение, но можно его немного симмитировать. В цикле клонирования очередного квадрата по координате X заносится цвет более темного оттенка, чем у его предыдущего собрата.
Код (Text):
Фон занесен, флаг тоже, осталась надпись: ----------------------------------------- pop esi add esi, 96*74 lea edi, Text push 6 pop ecx nextVert2: mov ebx, 00FF0000h push ecx mov eax, [edi] push 32 pop ecx nextVert: shl eax, 1 jnc noMovColor mov DWORD PTR [esi+12], ebx mov DWORD PTR [esi+28], ebx mov DWORD PTR [esi+44], ebx mov DWORD PTR [esi+60], ebx mov DWORD PTR [esi+76], ebx mov DWORD PTR [esi+92], ebx noMovColor: add esi, 96 sub ebx, 00040000h loop nextVert add esi, 96*4 add edi, 4 pop ecx loop nextVert2 d3dvb8 Unlock, pd3dVertexBuffer ; Разблокируем буферПосредством сдвига битовой маски определяем какой квадрат закрасить, одновременно увеличивая оттенок квадрата по координате X. Так как каждый квадрат состоит из двух треугольников, то всего надо заполнить цветом шесть вершин. Вообще Direct3D по умолчанию использует градиентную заливку, если у треугольника для каждой из вершин указать разный цвет, то он будет нарисован с плавными переходами из одного цвета в другой. Нам же нужен квадрат одного цвета, поэтому и приходится заносить одинаковый цвет во все шесть вершин.
Посмотрите на то, в какой последовательности я заношу данные в VertexBuffer. Сначала данные фона, а затем данные флага. Помните, в шестом пункте про Z буфер я упоминал, что все данные отрисоваются в порядке их следования? Фон находится дальше чем флаг, правильно? Значит нам по идее Z буфер вообще не нужен. Попробуйте его отключить, и посмотрите что произойдет. Все будет нарисовано как ни в чем не бывало. Используя такие вот особенности, можно увеличить скорость прорисовки, а именно FPS. Старайтесь отключать все, что только можно, и использовать лишь то, что действительно необходимо.
Все. Сцену можно рисовать в функции Render_Scene.
Хотелось бы еще остановится на одном важном моменте. Всегда записывайте на бумажку размер вашей вершины, чтобы не забыть. Исходя из него, нужно подсчитывать, сколько будет занимать один полигон, затем сколько будет занимать группа полигонов. Расчеты должны быть точными, если вы ошибетесь и, при создании буфера, его заполнении или при отрисовке, неправильно укажете данные, то рискуете ничего не увидеть на экране. Будьте очень внимательны.
Код (Text):
Справка CreateVertexBuffer передается 5 параметров. ------------------------------------------- Создает Vertex буфер необходимого размера 1. Размер. 2. Как будет использоваться. Значение является комбинацией флагов. D3DUSAGE_SOFTWAREPROCESSING - используется программная обработка вершин D3DUSAGE_DONOTCLIP - содержимое буфера никогда не будет клиппироваться. Если установлен этот флаг то D3DRS_CLIPPING должен быть установлен в FALSE. D3DUSAGE_DYNAMIC - буфер будет динамически использоваться. Если не указывать этот флаг, то буфер будет расположен в видео памяти. Если указать будет расположен в AGP памяти Флаг НЕ может использоваться совместно с D3DPOOL_MANAGED. D3DUSAGE_RTPATCHES - буфер использует для отрисовки высокоорганизованные примитивы. D3DUSAGE_NPATCHES - буфер использует для отрисовки N патчи D3DUSAGE_POINTS - буфер использует для отрисовки точечные спрайты или список точек. D3DUSAGE_WRITEONLY - только для записи. Direct3D расположит его по наиболее эффективному адресу. 3. Формат вершины. 4. Параметр определяющий как будет осуществляться менеджмент буфера. D3DPOOL_DEFAULT - По умолчанию. После сброса устройства буфер может быть потерян. D3DPOOL_MANAGED - Direct3D будет управлять буфером. После сброса устройства восстановление буфера не требуется. D3DPOOL_SYSTEMMEM - расположить буфер в системной памяти. После сброса устройства восстановление буфера не требуется. D3DPOOL_SCRATCH - буфер расположен в системной памяти. После сброса устройства восстановление буфера не требуется, т.к драйвер о нем не знает. Можно производить с буфером различные действия - копировать, блокировать и .т.д ( не понимаю нафига этот флаг нужен? ) 5. Адрес ячейки куда будет помещен указатель на созданный буфер --------------------------------------------------------- Если все прошло успешно, метод возвращает D3D_OK. Если нет, то D3DERR_INVALIDCALL, D3DERR_OUTOFVIDEOMEMORY или D3DERR_OUTOFMEMORY. Lock передается 4 параметра. ---------------------------- 1. Смещение в байтах относительно начала. Обычно используется 0. 2. Размер данных в байтах которые нужно блокировать. Если 0 то будет блокирован весь буфер. 3. Указатель на заполненный буфер. 4. Комбинация флагов. Как будет использоваться память при блокировании. D3DLOCK_DISCARD - приложение отбрасывает этот буфер. Direct3D возвращает указатель на буфер в новом месте и производит рендеринг из старого места. ( еще написано, что, вроде, скорость при этом не падает, а наоборот может возрасти ) Этот флаг работает только, если буфер создан с флагом D3DUSAGE_DYNAMIC. Насчет двух следующих флагов я не совсем понял, возможны ошибки. D3DLOCK_NOOVERWRITE - позволяет изменять содержимое буфера если он уже был ранее блокирован но не был разблокирован до нашего блокирования. Или вроде наоборот. Можно включать для оптимизации когда приложение только прибавляет данные в буфер. Этот флаг работает только, если буфер создан с флагом D3DUSAGE_DYNAMIC. D3DLOCK_NOSYSLOCK - Позволяет системе исполнять свои обязанности ( например движение курсора ) во время долгого блокирования буфера. И т.д и т.п. D3DLOCK_READONLY - Приложение не будет писать в буфер. Служит для оптимизации. Не употребляется с D3DLOCK_DISCARD. Нельзя использовать, если буфер создан с флагом D3DUSAGE_WRITEONLY. ------------------------------------------------ Если все прошло успешно, метод возвращает D3D_OK. Если нет, то D3DERR_INVALIDCALL. UnLock передается указатель на блокируемый буфер. ----------------------------------------------- Если все прошло успешно, метод возвращает D3D_OK. Если нет, то D3DERR_INVALIDCALL.7.6. Функция Set_Render_Parameters
Все, что содержит данная функция, можно было поместить в Init_Direct3D. Почему этого не было сделано, вы сейчас поймете. В уроке номер 2 я рассказывал о проблеме потерянного устройства. При возвращении в наше приложение по Alt+Tab нам нужно сбрасывать устройство. При этом теряется ВСЯ информация, связанная с ним, и нам необходимо ее восстанавливать. В этой функции содержатся все настройки касательно параметров рендеринга. Мы можем легко их восстановить, поместив вызов этой функции сразу после сбрасывания устройства.
Код (Text):
Установка параметров рендеринга: -------------------------------- d3dev8 SetTransform, pd3dDevice, D3DTS_WORLD, ADDR WorldMatrix d3dev8 SetTransform, pd3dDevice, D3DTS_VIEW, ADDR ViewMatrix d3dev8 SetTransform, pd3dDevice, D3DTS_PROJECTION, ADDR ProjectionMatrix d3dev8 SetRenderState, pd3dDevice, D3DRS_CULLMODE, D3DCULL_NONE d3dev8 SetRenderState, pd3dDevice, D3DRS_LIGHTING, NULL d3dev8 SetRenderState, pd3dDevice, D3DRS_ZENABLE, D3DZB_TRUE d3dev8 SetVertexShader, pd3dDevice, D3DFVF_CUSTOMVERTEX d3dev8 SetStreamSource, pd3dDevice, 0 , pd3dVertexBuffer, 16Метод SetTransform позволяет устанавливать в механизме визуализации, все связанное с трансформацией. Вызвав его, мы окончательно даем знать Direct3D, что для просчета сцены будем использовать те три матрицы, которые заполнили ранее.
Метод SetRenderState нужен для включения и выключения тех или иных настроек рендеринга. Он является практически незаменимым, так как с его помощью устанавливается большинство параметров. Использовать этот метод очень просто. При вызове указывается нужный нам обьект или свойство механизма визуализации и что с ним нужно сделать.
Так как в нашем примере нет обьемных тел, то мы можем отключить проверку следования вершин в полигоне: D3DRS_CULLMODE, D3DCULL_NONE. Также мы не используем освещение: D3DRS_LIGHTING, NULL. Но мы используем Z буфер: D3DRS_ZENABLE, D3DZB_TRUE.
Методом SetVertexShader мы указываем, что будет использоваться тот формат вершин, который мы определили сами. Методом SetStreamSource устанавливаем, что исходные данные для рендеринга нужно брать из буфера вершин, а число 16 означает размер нашей вершины в байтах.
Код (Text):
Справка SetTransform передается 2 параметра. ------------------------------------ 1. Название обьекта, который будет модифицирован. 2. Адрес расположения матрицы. ------------------------------------------------ Если все прошло успешно, метод возвращает D3D_OK. Если нет, то D3DERR_INVALIDCALL. SetRenderState передается 2 параметра. -------------------------------------- 1. Название параметра, который будет модифицирован. 2. Что с ним надо делать. ------------------------------------------------ Если все прошло успешно, метод возвращает D3D_OK. Если нет, то D3DERR_INVALIDCALL. SetVertexShader передается указатель на созданный шейдер. Или в простом случае просто указывается формат вершины. ------------------------------------------------ Если все прошло успешно, метод возвращает D3D_OK. Если нет, то D3DERR_INVALIDCALL. SetStreamSource передается 3 параметра. ------------------------------------ 1. Номер потока от 0 до максимального числа потоков -1 2. Указатель на созданный VertexBuffer 3. Размер элемента в буфере в байтах. ------------------------------------------------ Если все прошло успешно, метод возвращает D3D_OK. Если нет, то D3DERR_INVALIDCALL.Самая главная функция. В ней происходит вызов Direct3D, чтобы он отрендерил сцену, а затем показал на экран.
Код (Text):
Полный текст функции: --------------------- .DATA Zvalue dd 1.0f clearcolor dd 0 .CODE d3dev8 TestCooperativeLevel, pd3dDevice cmp eax, D3DERR_DEVICELOST jne noreset ret noreset: cmp eax, D3DERR_DEVICENOTRESET jne noreset2 d3dev8 Reset, pd3dDevice, ADDR d3dpp.BackBufferWidth invoke Set_Render_Parameters !!!!!! noreset2: invoke Animate_Scene ; Анимация сцены d3dev8 Clear,pd3dDevice,0,NULL,D3DCLEAR_TARGET or D3DCLEAR_ZBUFFER,\ 0,Zvalue, 0 d3dev8 DrawPrimitive, pd3dDevice, D3DPT_TRIANGLELIST, 0 , CountPrimitive d3dev8 Present, pd3dDevice, NULL, NULL, NULL, NULL retСамое главное, обратите внимание, где происходит вызов 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 придется использовать, а пока все и так отлично работает. Если возможно сократить обьем вызываемого, почему не сделать этого, да и размер программы меньше .
Код (Text):
Справка DrawPrimitive передается 3 параметра. ------------------------------------- Отрисовывает неиндексированные геометрические примитивы определяемого формата из установленного в StreamSource буфера 1. Тип примитива. D3DPT_POINTLIST - изолированные точки D3DPT_LINELIST - изолированные линии. Если в буфере будет меньше чем 2 вершины, при вызове возникнет ошибка. D3DPT_LINESTRIP - вершины образуют ломаную линию. Если в буфере будет меньше чем 2 вершины, при вызове возникнет ошибка. D3DPT_TRIANGLELIST - изолированные треугольники. D3DPT_TRIANGLESTRIP - сетка из треугольников. D3DPT_TRIANGLEFAN - сетка из треугольников, но все они сходятся в одной вершине. 2. Номер начального примитива 3. Количество примитивов ------------------------------------------------ Если все прошло успешно, метод возвращает D3D_OK. Если нет, то D3DERR_INVALIDCALL.Довольно сносный флаг у нас уже имеется. Но где вы видели абсолютно плоский флаг висящий в пространстве. Анимируем наш флаг, чтобы он смотрелся еще лучше. Обычная функция Sin или Cos поможет нам в этом. Конечно супер реалистичного эффекта мы не получим, но кое-что несомненно. Предлагаю вычислять значение Sin на определенной координате X и применять к координате Z. Получится, что квадраты, составляющие флаг, будут циклически плавать по Z.
Вот исходный код:
Код (Text):
Анимация флага: --------------- .DATA x dd 0.1f ; Начальное значение x2 dd 0.0f z dd 0.0f zcoeff dd 0.3f zcorrect dd 10.0f sinx dd 0.0f .DATA? checktime dd ? ; color db ? ; colorshift db ? ; flag dd ? ; .CODE invoke GetTickCount ; Получаем число тиков mov ebx, checktime add ebx, 50 ; Добавляем к нему коэффициент cmp eax, ebx jb noColor ; Если не наступило время обновления, то выход mov checktime, eax cmp flag, 1 ; Здесь происходит проверка, которая позволяет je goDec ; узнать какой цвет был и какой нужно inc color ; использовать далее cmp color, 164 ; jb noxorFlag ; Нужно для циклического изменения цвета фона xor flag, 1 ; от синего до красного по кругу noxorFlag: ; jmp exitt ; goDec: ; dec color ; cmp color, 0 ; ja exitt ; xor flag, 1 ; add colorshift, 8 ; cmp colorshift, 24 ; jb noincshift ; mov colorshift, 0 ; noincshift: ; exitt: ; d3dvb8 Lock1, pd3dVertexBuffer, NULL, CountData, ADDR pLockBuffer, NULL mov esi, pLockBuffer ; Обновляем цвет фона add esi, 28 ; movzx eax, color ; movzx ecx, colorshift ; shl eax, cl ; mov [esi], eax ; mov edi, pLockBuffer ; А здесь вычисляем нужное значение add edi, 104 ; посредством Sin fld x2 ; fadd x ; fst x2 ; fstp z ; push 36 ; pop ecx ; column: ; push ecx ; fld z ; fsub zcoeff ; fst z ; fsin ; fdiv zcorrect ; fstp sinx ; push sinx ; pop eax ; push edi ; Обновляем Z координату в буфере mov ecx, 10 ; row: ; mov DWORD PTR [edi], eax ; mov DWORD PTR [edi+16], eax ; mov DWORD PTR [edi+32], eax ; mov DWORD PTR [edi+48], eax ; mov DWORD PTR [edi+64], eax ; mov DWORD PTR [edi+80], eax ; add edi, 96*36 ; loop row ; pop edi ; add edi, 96 ; pop ecx ; loop column ; d3dvb8 Unlock, pd3dVertexBuffer noColor: retЧтобы была плавная анимация, нам необходимо обновлять значения, ориентируясь на время. Я использовал функцию GetTickCount. Можно использовать функцию timeGetTime из библиотеки winmm, говорят она будет поточнее. Обновление значений у меня происходит только 20 раз в секунду, этого вполне хватает. Как вы помните, при занесении данных в буфер нам нужно его блокировать, так вот здесь он блокируется также 20 раз в секунду. Все остальное время он не трогается. Приложение при этом рисует его с максимальным количеством кадров, которое возможно, т.е я никак не ограничиваю число кадров ( при входе в функцию Animate_Scene просто проверяется наступило время обновления или нет. Если нет тогда сразу выход. ). Также одновременно обновляется значение цвета у фона.
Итого, фон меняет окраску, а флаг развевается То, что мы здесь осуществили, является вертексной анимацией. Наверное слышали о такой? Как видите, совсем не сложно.
Уничтожаем все обьекты относящиеся к Direct3D.
Код (Text):
d3dev8 Release, pd3dVertexBuffer ; Уничтожаем Vertex буфер d3dev8 Release, pd3dDevice ; Уничтожаем Direct3DDevice8 d3d8 Release, pd3d ; Уничтожаем Direct3D8При уничтожении обьектов рекомендуется соблюдать порядок. Не думаю, что, если вы сразу уничтожите обьект Direct3D, то остальные будут уничтожены автоматически. Хотя возможно всякое. Кто его знает, что было у разработчиков на уме .
Урок получился длинный. И хотя я старался расписать все как можно подробней, возможно некоторое сумбурное изложение с моей стороны. Для окончания позволю себе закрепить весь материал вкратце.
Код (Text):
Порядок действий при запуске приложения: ---------------------------------------- В диалоге уже создан обьект Direct3D a) Создаем устройство Direct3DDevice b) Заполняем три матрицы, вызывая Direct3D и передавая ему нужные параметры c) Создаем VertexBuffer d) Заносим в него данные, которые будем отрисовывать e) Устанавливаем параметры рендеринга ( Устанавливаем три матрицы, включаем Z буфер, освещение и т.п. ) Все готово, можно отрисовывать сцену в цикле: --------------------------------------------- f) Анимируем нужное g) Очищаем BackBuffer и Z Буфер h) Рисуем требуемое посредством DrawPrimitive i) Показываем все на экран методом Present j) Переходим на пункт F и т.д. пока не потребуется завершение программы Завершение программы: --------------------- k) Уничтожаем VertexBuffer l) Уничтожаем обьект Direct3Ddevice m) Уничтожаем обьект Direct3D n) Завершаем приложениеPS:
В следующем уроке будет рассмотрена возможность использования обычного шрифта для вывода различной информации, ну и, может, в качестве примера подсчет FPS.
Как всегда исходник прилагается.
Все вопросы мыльте сюда mybox@aib.ru © keYMax
DirectX 8.1 в MASM32: Урок 4
Дата публикации 19 май 2003