DirectX 8.1 в MASM32: Урок 4

Дата публикации 19 май 2003

DirectX 8.1 в MASM32: Урок 4 — Архив WASM.RU

Урок 4. Первое полноценное трехмерное приложение.

Основы трехмерной графики. Как это все работает. Я расскажу вам как устроен механизм визуализации в DirectX, и вы научитесь использовать в своей работе простые полигоны.

  1. Система координат
  2. Формат вершин
  3. Полигоны
  4. Что такое сцена. Как Direct3D работает с трехмерным миром.
  5. Камера
  6. Z буфер
  7. Все знания применим на деле !
    1. Об используемых библиотеках
    2. Новое в диалоге
    3. Новое в приложении
    4. Функция Init_Direct3D
    5. Функция Init_Scene
    6. Функция Set_Render_Parameters
    7. Функция Render_Scene
    8. Функция Animate_Scene
    9. Функция Destroy_Direct3D
  8. Послесловие

Предисловие

Как вы знаете, вся трехмерная графика основана на математике. "Линейная алгебра", "Векторная алгебра и начала анализа", "Начертательная геометрия" и еще много чего другого необходимо усвоить прежде чем приступать к успешной работе с 3D. Конечно, это не так интересно, но все равно придется этим заниматься. С вашей стороны необходимо всего лишь желание, а я в свою очередь постараюсь рассказать все как можно проще и доступней на примерах и картинках. Кому не терпится перейти сразу к делу начинайте с пункта 7.

1. Система координат

Начнем с самых основ. Загадочная буква "D" в словах 2D и 3D расшифровывается как Dimension - измерение. Соответственно 2D - это 2 измерения, а 3D - три. 2D представляет собой обычную систему координат на плоскости, которая знакома всем еще со школы. А что представляет собой 3D ? Это та же система координат, только к ней добавляется еще одно измерение обозначаемое как Z. На рисунке изображены обе системы.

Из рисунка видно, что новая координата Z отвечает за то, насколько далеко находится от нас какой - либо обьект. Обе системы координат ( 2D и 3D ) существуют в двух разных вариантах - левосторонняя и правосторонняя. Разница между ними в направлении отсчета. В левосторонней X и Y увеличиваются слева направо, а в правосторонней справа налево. Соответственно координата Z тоже меняет свое направление. На рисунке изображены как раз левосторонние системы. Direct3D позволяет использовать ту, которая вам нравится. Я, как все нормальные люди, буду использовать стандартную левостороннюю систему.

2. Формат вершин

Точка, точка, запятая - вышла рожица кривая. Самое простое, что существует в геометрии - это точка. В трехмерном пространстве точка трактуется как вершина - Vertex. В Direct3D у вершины помимо координат могут быть дополнительно какие - либо свойства. Координаты совместно со свойствами хранятся в одном месте и называются форматом вершины - Vertex Format. Direct3D позволяет задавать различный формат вершины, так называемый FVF - Flexible Vertex Format - гибкий формат вершин. Выбор формата зависит от тех задач, которые вы перед собой ставите. Он может состоять из нескольких флагов. Каждый флаг в отдельности описывает каким свойством будет обладать наша вершина. Допускается объединение сколько угодно таких флагов, если они не противоречат друг другу. Соответственно не забывайте, что чем больше флагов вы используете в формате вершины, тем больший размер она будет занимать в памяти.

Например, часто используемый формат содержит:

Код (Text):
  1.  
  2.   D3DFVF_XYZ       - координаты вершины
  3.   D3DFVF_NORMAL    - вектор для расчета освещения
  4.   D3DFVF_DIFFUSE   - цвет для закраски
  5.   D3DFVF_TEX2      - координаты текстуры
  6.  
  7.   Все это занимает: (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.  
  2. Справка
  3.  
  4.   Все возможные флаги для формата вершины:
  5.   ----------------------------------------
  6.   1. D3DFVF_DIFFUSE  - вершина содержит компоненту цвета.
  7.   2. D3DFVF_NORMAL   - вершина содержит нормаль.
  8.                        Нельзя применять с D3DFVF_XYZRHW
  9.   3. D3DFVF_PSIZE    - вершина специфичного формата.
  10.   4. D3DFVF_SPECULAR - вершина содержит Specular компоненту.
  11.   5. D3DFVF_XYZ      - нетрансформированная вершина содержит позицию.
  12.                        Нельзя использовать вместе с D3DFVF_XYZRHW
  13.   6. D3DFVF_XYZRHW   - трансформированная вершина содержит позицию
  14.                        Нельзя использовать вместе с D3DFVF_XYZ и D3DFVF_NORMAL
  15.   7. D3DFVF_XYZB1    - содержит позицию и значение Beta.
  16.               ...      Beta используется при мультиматричных блендинг
  17.      D3DFVF_XYZB5      ( смешивание ) операциях.                              
  18.                        На данный момент поддерживается три значения Beta
  19.                        и четыре блендинг матрицы.
  20.  
  21.   Все возможные флаги, относящиеся к текстуре:
  22.   -------------------------------------------
  23.   1. D3DFVF_TEX0           - вершина содержит "n" число текстурных координат
  24.      ...
  25.      D3DFVF_TEX8
  26.  
  27.   2. D3DFVF_TEXTUREFORMAT1 - указывает какой размерности текстурная координата
  28.      ...                     Например: D3DFVF_TEXTUREFORMAT1 - одномерная,
  29.      D3DFVF_TEXTUREFORMAT4   D3DFVF_TEXTUREFORMAT2 - двумерная и т.д.
  30.                              На данный момент эта константа редко используется
  31.   Значения масок:
  32.   ---------------
  33.   1. D3DFVF_POSITION_MASK  - битовая маска позиций
  34.   2. D3DFVF_RESERVED0      - маска зарезервированных битов в формате вершины.
  35.      и                       На данный момент не используется
  36.      D3DFVF_RESERVED2
  37.   3. D3DFVF_TEXCOUNT_MASK  - битовая маска для текстурных флагов.
  38.  
  39.   Прочее:
  40.   -------
  41.   1. D3DFVF_LASTBETA_UBYTE4 - когда используется индексное вершинное смешение
  42.                               ( vertex blending ) и фиксирована функция FVF
  43.                               вертексных шейдеров, обязательно необходимо
  44.                               указать этот флаг для вертексного шейдера
  45.   2. D3DFVF_TEXCOUNT_SHIFT  - число бит, на которое сдвинуто значение
  46.                               идентифицирующее число текстурных координат для
  47.                               вершины.
  48.   Несколько примеров:
  49.   -------------------
  50.   1. Легкая нетрансформированная и нетекстурированная вершина.
  51.      Закраска по Гуро.
  52.  
  53.      D3DFVF_CUSTOMVERTEX  equ  D3DFVF_DIFFUSE or D3DFVF_XYZ
  54.  
  55.   2. Легкая нетрансформированная и нетекстурированная вершина.
  56.      Закраска по Гуро. Используется освещение.
  57.  
  58.      D3DFVF_CUSTOMVERTEX  equ  D3DFVF_DIFFUSE or D3DFVF_XYZ or D3DFVF_NORMAL
  59.  
  60.   3. Нетрансформированная вершина использующая текстуру.
  61.      Освещение отсутствует.
  62.  
  63.      D3DFVF_CUSTOMVERTEX  equ D3DFVF_XYZ or D3DFVF_TEX2
  64.  
  65.   4. Трансформируемая вершина с RHW частью, использующая текстуру
  66.  
  67.      D3DFVF_CUSTOMVERTEX  equ D3DFVF_XYZRHW or D3DFVF_TEX2
  68.      
  69.   5. Тяжелая вершина с компонентой цвета, координатами текстуры, использующая
  70.      освещение.
  71.      
  72.     D3DFVF_CUSTOMVERTEX equ D3DFVF_XYZ or D3DFVF_NORMAL or D3DFVF_DIFFUSE or /  
  73.                             D3DFVF_SPECULAR or D3DFVF_TEX2

3. Полигоны

Формат вершины уже не является для вас тайной. Ну а как же составить из разрозненных вершин какой либо обьект ? Все объекты состоят из треугольников, скажете вы и будете правы. Конечно в Direct3D можно использовать и другие фигуры, число углов у которых больше чем три. Это квадраты и многоугольники. Но, так как аппаратная часть ускорителя оптимизирована на работу с треугольниками, то перед отрисовкой ваших квадратов они все равно будут разбиты на треугольники, так что сэкономив на простоте описания обьекта, можно проиграть в скорости. Как и что лучше использовать, решать вам.

А почему выбрали именно треугольник ? Потому что он является самой простой фигурой и всегда выпуклый. А раз он выпуклый, то упрощаются различные расчеты при работе с ним ( пересечение прямой, лежит ли точка внутри него и т.д. ) Вы это поймете сами, когда еще более углубитесь в работу с 3D.

Все фигуры в трехмерной графике называют "полигонами" - если перевести буквально, то это означает многоугольник. Но так как используют практически одни треугольники, то и название полигон стало синонимом слова треугольник. И когда вы видите в какой - либо статье про новую игрушку, что в модели монстра используется 5000 полигонов, то это значит, что он состоит из 5000 треугольников.

Сейчас давайте рассмотрим, как описываются полигоны. Для быстроты просчета сцены ( это определение я раскрою чуть дальше ) в Direct3D ( да и в любом другом 3D API ) применяется схема, когда невидимые полигоны отбрасываются и просчитываются только те, которые мы с вами видим. Как же Direct3D может узнать какие полигоны рисовать, а какие нет? Все дело в том как они заданы. И огромную роль в этом играет порядок следования вершин в описании полигона. Догадались? Правильно. Они должны быть заданы либо по часовой стрелке, либо против. Стандартом является то, когда вершины, составляющие полигон, заданы по часовой стрелке. В Direct3D этот режим включен по умолчанию. Т.е. он будет отрисовывать только эти полигоны, а все другие, где вершины описаны против часовой стрелки, отбрасывать. Нижеследующий рисунок поможет вам прояснить все моменты.

Чтобы было еще более понятно, разберем пример. У нас есть куб. Он повернут к нам одной гранью. Начинаем писать вершины этой грани по часовой стрелке. Начинать можно с любой. Когда мы написали эти четыре вершины друг за дружкой, то поворачиваем к себе куб новой гранью и опять пишем вершины по часовой стрелке и т.д. пока все грани не будут описаны. Если теперь внимательно подумать и посмотреть в каком порядке будут находиться вершины той грани, которая повернута от нас, то окажется, что они будут расположены против часовой стрелки ( если смотреть на эту грань сквозь куб ). Вот и весь фокус. Соответственно раз они против часовой стрелки и грань куба нам действительно не видно, то и рисовать ее не надо, что и делает с успехом Direct3D ( с вашей помощью, конечно ). Если вы собираетесь вручную описывать обьекты для дальнейшего использования в программе, то от этой процедуры вам не избавиться как бы этого ни хотелось. Ну а если использовать 3D редактор, то он все сделает за вас автоматически и опишет каждый полигон в нужном порядке. Также довожу до вашего сведения, что в Direct3D в любой момент можно поменять условие, по которому будет определяться какие полигоны отбрасывать. Те которые по часовой стрелке или против. В некоторых случаях это действительно необходимо, но об этом как - нибудь в другой раз.

Теперь когда вы знаете о том, что такое полигоны и как их нужно описывать, пора переходить к следующей части.

4. Что такое сцена. Как Direct3D работает с трехмерным миром.

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

Допустим, мы имеем обьект из полигонов на сцене. Как мы можем им манипулировать, перемещать, вращать вокруг оси и т.д? Не забудьте при этом, что все присутствующее на сцене распологается в трехмерной системе координат. Для перемещения вдоль какой - либо оси достаточно изменить координаты каждой из вершин в ту или иную сторону. Для поворота все уже намного сложнее. Тут нужно применять формулы, основаные на синусе и косинусе. Все они уже давно написаны математиками и с блеском работают. Главное их неудобство заключается в громоздкости и в том, что для поворота вокруг нескольких осей сразу, нужно применять эти формулы по очереди для каждой оси в отдельности. Результат может оказаться не таким, какой мы ожидали ( думаю, то, что они применяются для каждой вершины отдельно, обьяснять вам не нужно ). Для избавления от этого недостатка была придумана, так называемая, однородная система координат, где все действия над обьектами было очень удобно производить с помощью МАТРИЦ. Если хотели произвести сразу много действий, то просто перемножали матрицы нужных операций и результирующую матрицу, содержащую в себе все преобразования, уже применяли к вершинам всего обьекта. Удобно и быстро. И теперь все системы просчета 3D графики работают на матрицах.

Что представляет собой матрица? Это обычная числовая таблица из строк и столбцов. Вот как она выглядит:

Код (Text):
  1.  
  2.  |а11 a12 a13 a14|- матрица 4х4. Всего 16 элементов. Каждый элемент имеет
  3.  |a21 a22 a23 a24|  обозначение. Буквой обозначается элемент, а цифрами место
  4.  |a31 a32 a33 a34|  расположения в матрице. Например "a23" - это элемент,
  5.  |a41 a42 a43 a44|  стоящий на второй строке в третьем столбце.

Именно такие матрицы 4x4 и использует в своей работе Direct3D. Описываются они обычным массивом из 16 элементов, размер каждого элемента 4 байта ( Real4 ). Соответственно, размер в памяти, который занимает одна матрица равен 64 байтам.

Существуют три основные матрицы, которые мы должны знать:

  • World Matrix (мировая матрица)
  • View Matrix (матрица вида)
  • Matrix Projection (матрица проекции).

"Мировая матрица" отвечает за преобразование всей сцены (вращение вокруг осей, перемещение и т.д.). Именно всей, а не какого - либо отдельного обьекта на ней. "Матрица вида" отвечает за то, какая часть сцены будет видна на экране. "Матрица проекции" отвечает за то, как трехмерные координаты будут преобразованы в двумерные для отображения на экран.

Эти три матрицы и являются тем чудесным средством, с помощью которого Direct3D превращает трехмерный мир в плоскую картинку на вашем мониторе.

5. Камера

Хоть трехмерный мир почти и бесконечен, но вот возможности ускорителя и процессора далеко не безразмерные. Если просчитать пару тысяч полигонов не проблема, то десятки и тем более сотни тысяч за раз подчас является трудоемкой задачей, даже для самых последних hi-end-овых ускорителей ( естественно всякие там Cray не в счет :smile3: ). Поэтому опять приходится как-то оптимизировать все это дело. Решение простое. Чтобы все быстро просчитывалось и отрисовывалось с достаточно приемлемым числом кадров, наша сцена при просчете ограничивается в размерах. Осуществляется это намеренно. Сколько бы мы ни помещали на сцену обьектов, время просчета будет увеличиваться не так сильно, как при отсутствии такого ограничения. Вспомните про полигоны. Отбрасываются те, которые нам не нужны. Здесь отбрасываются те части сцены, которые не попадают внутрь этого ограничения.

В 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, мы получим готовые матрицы для использования. Камера готова !

6. Z буфер

Ну вот, какой-то там Z буфер еще надо знать. На самом деле это полезнейшая вещь. Если вы все еще не догадываетесь, то Z буфер позволяет Direct3D правильно отображать обьекты на экране. Дело в том, что устройство визуализации рисует обьекты на экране в том порядке, в котором их передавать. Т.е. второй обьект нарисуется поверх первого, третий поверх второго и первого. Ну и что скажете вы? Если у нас статичная сцена, и мы передаем все обьекты в нужном порядке, то никакой Z буфер нам и впомине не нужен. Все правильно. А если сцена вращается, и еще обьекты меняют свое местоположение, что тогда? Тогда Z буфер незаменим.

Работает он так:

  1. В начале отрисовки каждого кадра Z буфер заполняется нейтральным значением.
  2. Когда просчитывается полигон, то его надо рисовать на экране. Он состоит из множества пикселей. У каждого из них есть координаты. Direct3D берет Z координату пикселя и сравнивает с тем значением, которое лежит в Z буфере. Если значение в Z буфере больше, то на его место заносится Z координата пикселя, а сам пиксель рисуется, если нет, то ничего делать не надо.
  3. Берется следующий полигон. Выполняется пункт b). Так до тех пор пока все не будет отрисовано.

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

Z буфер имеет такие же размеры что и окно на экране. Если окно 320х240, то и Z буфер будет иметь размер 320х240. Размер значений, которые может содержать Z буфер, варьируются от 16 до 32 бит в зависимости от того, какой формат вы зададите. Соответственно, чем больше формат, тем больше времени будет уходить на его заполнение нейтральным значением. Такое заполнение называется очисткой Z буфера. При использовании буфера придется осуществлять такую очистку перед тем, как рисовать очередной кадр.

Код (Text):
  1.  
  2. Справка
  3.  
  4.   Все возможные форматы Z буфера:
  5.   -------------------------------
  6.   1. D3DFMT_D16          - 16 битный.
  7.   2. D3DFMT_D16_LOCKABLE - 16 битный. Блокируемый приложением.
  8.   3. D3DFMT_D24X8        - 32 битный использующий только 24 бита.
  9.   4. D3DFMT_D32          - 32 битный.

7. Все знания применим на деле !

Уфф... Ну как? Сложно? Думаю вы уже готовы все бросить и пойти отдохнуть. Действительно нужно время, чтобы переварить полученную информацию. Математика сложный предмет и с первого раза доходит не до всех. Я сам когда-то долго не мог въехать в некоторые термины :smile3:. Но когда приходится что-то изучать для любимого дела, то все понимается и усваивается гораздо быстрей.

Итак. Вы знаете что такое трехмерная система координат, вершина, полигон, матрица, вектор, сцена, камера и 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):
  1.  
  2.   При использовании D3DX8D.LIB
  3.   ----------------------------
  4.   1. Нужна дополнительно D3DX8D.DLL ( размер около 700 кило )
  5.   2. В поставку DirectX 8.1 она не входила. И тот пакет, который у вас установлен
  6.      на компе, чтобы играть в игрушки, ее не содержит.
  7.   3. Используя ее, вам придется распостранять свои творения вместе с этой DLL
  8.   4. Размер получаемого exe'шника такой, который вы сделали сами.
  9.  
  10.   При использовании D3DX8.LIB
  11.   ----------------------------
  12.   1. Никакой дополнительной DLL не требуется.
  13.   2. На стадии компиляции приложения необходимо подключать MSVCRT.lib
  14.      и ADVAPI32.lib
  15.   3. Размер exe'ника вырастает пропорционально количеству используемых
  16.      в нем функций, начиная от 150 килобайт и выше
  17.      ( начальный размер зависит от версии Msvcrt.lib )

Да... Как ни крути, а все равно плохо. Выбирайте то, что вам по душе, а мне больше нравится вариант с DLL. Именно его я и буду использовать в дальнейшем. Чтобы не пришлось искать эту DLL по инету, я включил ее в архив с исходником. Поэтому при скачивании оного с Wasm.ru, не удивляйтесь большому размеру архива ( сам exe примера занимает 7 кило ).

7.2. Новое в диалоге

Для работы нам понадобится каркас от предыдущего урока. Как вы помните, он разбит на две части: диалог и само приложение. На форму диалога были добавлены 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):
  1.  
  2. Справка
  3.  
  4.   CheckDepthStencilMatch передается 5 параметров.
  5.   -----------------------------------------------
  6.   1. Номер адаптера. D3DADAPTER_DEFAULT - адаптер по умолчанию.
  7.   2. Тип адаптера. D3DDEVTYPE_HAL - аппаратный. D3DDEVTYPE_SW - программный.
  8.   3. Формат поверхности адаптера ( какой на экране ).
  9.   4. Формат поверхности BackBuffer.
  10.   5. Формат Z буфера.
  11.   -------------------------------------------------------
  12.   Если все прошло успешно, метод возвращает D3D_OK.
  13.   Если нет, то D3DERR_INVALIDCALL или D3DERR_NOTAVAILABLE.

Чтобы сообщить Direct3D что мы будем использовать Z буфер, нам еще придется заполнить дополнительно 2 параметра в D3DPRESENT_PARAMETERS. Это поле EnableAutoDepthStencil ( заносим туда TRUE ) и AutoDepthStencilFormat ( заносим туда значение формата Z буфера, который пользователь выбрал в ComboBox ). Все. Теперь наше приложение будет использовать Z буфер.

Все подробности смотрите в файле Dialog.asm

7.3. Новое в приложении

Чтобы мы могли использовать новые функции, нам необходимо подключить один Inc файл и одну библиотеку. Это d3dx8math.inc и d3dx8d.lib. Данные файлы поддерживают работу с матрицами и векторами, а также еще с несколькими математическими функциями на данный момент нам не нужными.

Изменились старые функции: Init_Direct3D, Destroy_Direct3D и Render_Scene.

Добавились новые: Set_Render_Parameters, Init_Scene и Animate_Scene.

7.4. Функция Init_Direct3D

Как обычно, после того как пользователь запустил приложение, мы получаем от диалога полностью заполненную структуру D3DPRESENT_PARAMETERS. На ее основе создаем устройство Direct3DDevice. Далее нам нужно создать камеру ( о ней см. пункт 6 ).

Код (Text):
  1.  
  2.   Создаем камеру
  3.   --------------
  4.  
  5.   .DATA                
  6.  
  7.    EyeVector    D3DVECTOR  <0.0f, 0.0f, -2.6f>  ; Координаты камеры
  8.    LookAtVector D3DVECTOR  <3.0f, -1.0f, 3.0f>  ; Куда смотрит камера
  9.    UpVector     D3DVECTOR  <0.0f, 2.0f, -0.8f>  ; Указывает верх камеры
  10.  
  11.    
  12.    FieldOfView    dd  0.7853981635f             ; Поле обзора камеры
  13.    AspectRatio    dd  NULL                      ; AspectRatio
  14.    NearViewPlanZ  dd  1.0f                      ; Передняя отсекающая плоскость
  15.    FarViewPlanZ   dd  10.0f                     ; Задняя отсекающая плоскость
  16.    
  17.   .DATA?
  18.  
  19.    WorldMatrix       D3DMATRIX  <?>             ; Мировая матрица
  20.    ViewMatrix        D3DMATRIX  <?>             ; Матрица вида
  21.    ProjectionMatrix  D3DMATRIX  <?>             ; Матрица проекции
  22.  
  23.   .CODE
  24.  
  25.    fild    d3dpp.BackBufferWidth                ; Делим ширину на высоту
  26.    fild    d3dpp.BackBufferHeight
  27.    fdiv    
  28.    fstp    AspectRatio
  29.      
  30.    invoke  D3DXMatrixRotationY, ADDR WorldMatrix, NULL  
  31.  
  32.    invoke  D3DXMatrixLookAtLH, ADDR ViewMatrix, ADDR EyeVector, \
  33.            ADDR LookAtVector, ADDR UpVector
  34.  
  35.    invoke  D3DXMatrixPerspectiveFovLH, ADDR ProjectionMatrix, \
  36.            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):
  1.  
  2. Справка
  3.  
  4.   D3DXMAtrixRotationY передается 2 параметра.
  5.   -------------------------------------------
  6.   1. Адрес матрицы в памяти.
  7.   2. Угол в радианах.
  8.  
  9.   D3DXMAtrixLookAtLH передается 4 параметра.
  10.   ------------------------------------------
  11.   1. Адрес матрицы в памяти.
  12.   2. Адрес EyeVector
  13.   3. Адрес LookAtVector
  14.   4. Адрес UpVector
  15.  
  16.   D3DXMatrixPerspectiveFovLH передается 5 параметров.
  17.   --------------------------------------------------
  18.   1. Адрес матрицы в памяти.
  19.   2. Поле обзора
  20.   3. AspectRatio
  21.   4. Значение переднего плана
  22.   5. Значение заднего плана

7.5. Функция Init_Scene

В этой функции я решил поместить подготовку сцены. На скриншоте вы видели некое подобие флага. Как его создать? Текстур мы не используем, все что у нас имеется - это треугольники, которые можно закрасить каким - либо цветом. Что делать? Давайте создадим флаг из квадратов и там, где нужно, закрасим квадраты красным цветом, чтобы получилась надпись.

Во - первых, описываем формат нашей вершины. Он будет содержать координаты и цвет.

Код (Text):
  1.  
  2.  .CONST
  3.   D3DFVF_CUSTOMVERTEX equ D3DFVF_XYZ OR D3DFVF_DIFFUSE

Затем нам понадобится шаблон для клонирования:

Код (Text):
  1.  
  2.  .DATA
  3.   Vertices  dd 0.0f , 0.0f , 0.0f , 0d1d0e7h   ; 4*4 байт на вершину
  4.             dd 0.0f , 0.1f , 0.0f , 0d1d0e7h   ; 12*4 байт на полигон
  5.             dd 0.1f , 0.1f , 0.0f , 0d1d0e7h   ; 24*4 байт на квадрат
  6.  
  7.             dd 0.0f , 0.0f , 0.0f , 0d1d0e7h
  8.             dd 0.1f , 0.1f , 0.0f , 0d1d0e7h
  9.             dd 0.1f , 0.0f , 0.0f , 0d1d0e7h

Здесь определено 2 треугольника, а все вместе - квадрат. Нужно размножить этот шаблон в количестве 36 по горизонтали и 10 по вертикали. Всего 360 квадратов или 720 треугольников.

Не забудем о надписи. Для ее нанесения используем обычную битовую маску для определения того, где закрашивать нужный квадрат. Надпись WASM.RU укладывается в шесть 32 битных значений. Вот они:

Код (Text):
  1.  
  2.  .DATA
  3.   Text  dd 10001001100011001000100011001001b   ; Надпись WASM.RU
  4.         dd 10001010010100001101100010101001b
  5.         dd 10101010010011001010100010101001b
  6.         dd 10101010010000101000100011001001b
  7.         dd 10101011110100101000100010101001b
  8.         dd 01010010010011001000101010100110b

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

Код (Text):
  1.  
  2.  .DATA
  3.   Fon  dd -1.0f, -6.0f, 1.5f, 0f1f0e7h    ; Фон - 2 полигона с цветом
  4.        dd -1.0f,  2.0f, 1.5f, 0b1b0a7h    ; для градиентной заливки
  5.        dd  9.0f,  2.0f, 1.5f, 0f1f0e7h
  6.  
  7.        dd -1.0f, -6.0f, 1.5f, 0f1f0e7h
  8.        dd  9.0f,  2.0f, 1.5f, 0f1f0e7h
  9.        dd  9.0f, -6.0f, 1.5f, 0FFFFFFh

С данными разобрались, и наступило время узнать как же Direct3D с ними работает. Система хранения данных в Direct3D основана на буферах. Существуют разные типы буферов. Например, VertexBuffer - это буфер, содержащий данные вершин. Создав специальным методом нужный буфер, мы должны занести туда данные, и только затем указывать, что из него рисовать. После создания к буферу можно обращаться и менять какие - либо данные в нем. Переопределять размер в большую или меньшую сторону, как я знаю, нельзя ( тема рассмотрена мной мало, возможно это как-то все-таки осуществляется ). Вообще, ОЧЕНЬ не рекомендуется производить громоздкие манипуляции с буфером во время отрисовки сцены. Записывать данные еще можно, а вот считывать из него ... крайне не приветствуется. Создается VertexBuffer методом CreateVertexBuffer. Для нашего примера создание буфера выглядит так:

Код (Text):
  1.  
  2.   Создание Vertex буфера:
  3.   ----------------------------------------
  4.  .CODE
  5.  
  6.   d3dev8  CreateVertexBuffer, pd3dDev, (24*4)*360+24*4, D3DUSAGE_WRITEONLY, \
  7.           D3DFVF_CUSTOMVERTEX , D3DPOOL_MANAGED, ADDR pd3dVertexBuffer
  8.            
  9.   d3dvb8  Lock1, pd3dVertexBuffer, NULL, CountData, ADDR pLockBuffer, NULL

После создания буфера, доступ к нему осуществляется посредством блокирования. Оно необходимо, так как буфер может находится где угодно. В памяти на самом акселераторе, в AGP памяти, либо где-то еще. Методом Lock блокируем, а разблокирование осуществляется методом Unlock. Когда буфер блокирован, ускоритель не может с ним работать, учтите это хотя возможны и исключения.

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

Код (Text):
  1.  
  2.   В первую очередь занесем данные фона:
  3.   -------------------------------------
  4.   lea     esi, Fon            
  5.   mov     edi, pLockBuffer          
  6.   mov     ecx, 24
  7.   rep     movsd  
  8.  
  9.   Следом клонируем данные флага посредством циклов:
  10.   -------------------------------------------------
  11.  .DATA                                                                                          
  12.    xplus   dd 0.105f                            
  13.    yplus   dd 0.0f
  14.    shiftx  dd 0.105f
  15.    shifty  dd -0.105f
  16.  
  17.  .CODE
  18.    push    edi
  19.    mov     esi, edi
  20.    push    10                                
  21.    pop     ecx                          
  22.    nextVertices3:                
  23.    xor     ebx, ebx    
  24.    push    ecx
  25.    push    36
  26.    pop     ecx
  27.    nextVertices2:
  28.    push    ecx
  29.    lea     edi, Vertices
  30.    push    6
  31.    pop     ecx
  32.    nextVertex:
  33.    fld     DWORD PTR [edi]
  34.    fadd    xplus
  35.    fstp    DWORD PTR [esi]  
  36.    fld     DWORD PTR [edi+4]
  37.    fadd    yplus
  38.    fstp    DWORD PTR [esi+4]
  39.    push    DWORD PTR [edi+8]
  40.    pop     DWORD PTR [esi+8]
  41.    mov     eax, DWORD PTR [edi+12]  
  42.    sub     eax, ebx
  43.    mov     DWORD PTR [esi+12], eax    
  44.    add     esi, 16
  45.    add     edi, 16
  46.    loop    nextVertex
  47.    add     ebx, 00030303h
  48.    fld     xplus
  49.    fadd    shiftx
  50.    fstp    xplus
  51.    pop     ecx
  52.    loop    nextVertices2
  53.    push    shiftx
  54.    pop     xplus
  55.    fld     yplus
  56.    fadd    shifty
  57.    fstp    yplus  
  58.    pop     ecx
  59.    dec     ecx
  60.    jnz     nextVertices3

Так как трехмерная графика основана на цифрах с плавающей запятой, то без сопроцессора не обойтись. При клонировании мы просто добавляем нужное число по какой - либо координате. У меня прибавление по X идет, начиная от 0 в плюсовую сторону. По Y от 0 в минусовую сторону.

В итоге флаг займет нижнюю правую четверть экрана. Почему так? Потому что начало трехмерной системы координат располагается по центру экрана. Если вы хотите иметь какое - то другое расположение системы координат, то вам придется или сдвигать камеру в нужную сторону, или заполнять "Мировую Матрицу" нужными данными. Я просто сдвинул камеру и все.

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

Код (Text):
  1.  
  2.   Фон занесен, флаг тоже, осталась надпись:
  3.   -----------------------------------------
  4.  
  5.    pop     esi
  6.    add     esi, 96*74
  7.    lea     edi, Text
  8.    push    6
  9.    pop     ecx
  10.    nextVert2:
  11.    mov     ebx, 00FF0000h
  12.    push    ecx
  13.    mov     eax, [edi]
  14.    push    32
  15.    pop     ecx
  16.    nextVert:
  17.    shl     eax, 1
  18.    jnc     noMovColor
  19.    mov     DWORD PTR [esi+12], ebx
  20.    mov     DWORD PTR [esi+28], ebx
  21.    mov     DWORD PTR [esi+44], ebx
  22.    mov     DWORD PTR [esi+60], ebx
  23.    mov     DWORD PTR [esi+76], ebx
  24.    mov     DWORD PTR [esi+92], ebx
  25.    noMovColor:
  26.    add     esi, 96
  27.    sub     ebx, 00040000h
  28.    loop    nextVert
  29.    add     esi, 96*4
  30.    add     edi, 4
  31.    pop     ecx
  32.    loop    nextVert2
  33.  
  34.    d3dvb8  Unlock, pd3dVertexBuffer       ; Разблокируем буфер

Посредством сдвига битовой маски определяем какой квадрат закрасить, одновременно увеличивая оттенок квадрата по координате X. Так как каждый квадрат состоит из двух треугольников, то всего надо заполнить цветом шесть вершин. Вообще Direct3D по умолчанию использует градиентную заливку, если у треугольника для каждой из вершин указать разный цвет, то он будет нарисован с плавными переходами из одного цвета в другой. Нам же нужен квадрат одного цвета, поэтому и приходится заносить одинаковый цвет во все шесть вершин.

Посмотрите на то, в какой последовательности я заношу данные в VertexBuffer. Сначала данные фона, а затем данные флага. Помните, в шестом пункте про Z буфер я упоминал, что все данные отрисоваются в порядке их следования? Фон находится дальше чем флаг, правильно? Значит нам по идее Z буфер вообще не нужен. Попробуйте его отключить, и посмотрите что произойдет. Все будет нарисовано как ни в чем не бывало. Используя такие вот особенности, можно увеличить скорость прорисовки, а именно FPS. Старайтесь отключать все, что только можно, и использовать лишь то, что действительно необходимо.

Все. Сцену можно рисовать в функции Render_Scene.

Хотелось бы еще остановится на одном важном моменте. Всегда записывайте на бумажку размер вашей вершины, чтобы не забыть. Исходя из него, нужно подсчитывать, сколько будет занимать один полигон, затем сколько будет занимать группа полигонов. Расчеты должны быть точными, если вы ошибетесь и, при создании буфера, его заполнении или при отрисовке, неправильно укажете данные, то рискуете ничего не увидеть на экране. Будьте очень внимательны.

Код (Text):
  1.  
  2. Справка
  3.  
  4.   CreateVertexBuffer передается 5 параметров.
  5.   -------------------------------------------
  6.   Создает Vertex буфер необходимого размера
  7.  
  8.   1. Размер.
  9.   2. Как будет использоваться. Значение является комбинацией флагов.
  10.      D3DUSAGE_SOFTWAREPROCESSING - используется программная обработка вершин
  11.      D3DUSAGE_DONOTCLIP          - содержимое буфера никогда не будет
  12.                                    клиппироваться. Если установлен этот флаг
  13.                                    то D3DRS_CLIPPING должен быть установлен
  14.                                    в FALSE.
  15.      D3DUSAGE_DYNAMIC            - буфер будет динамически использоваться.
  16.                                    Если не указывать этот флаг, то буфер
  17.                                    будет расположен в видео памяти.
  18.                                    Если указать будет расположен в AGP памяти
  19.                                    Флаг НЕ может использоваться совместно с
  20.                                    D3DPOOL_MANAGED.
  21.      D3DUSAGE_RTPATCHES          - буфер использует для отрисовки
  22.                                    высокоорганизованные примитивы.
  23.      D3DUSAGE_NPATCHES           - буфер использует для отрисовки N патчи
  24.      D3DUSAGE_POINTS             - буфер использует для отрисовки точечные
  25.                                    спрайты или список точек.
  26.      D3DUSAGE_WRITEONLY          - только для записи. Direct3D расположит
  27.                                    его по наиболее эффективному адресу.
  28.   3. Формат вершины.
  29.   4. Параметр определяющий как будет осуществляться менеджмент буфера.
  30.      D3DPOOL_DEFAULT   - По умолчанию. После сброса устройства буфер
  31.                          может быть потерян.
  32.      D3DPOOL_MANAGED   - Direct3D будет управлять буфером.
  33.                          После сброса устройства восстановление буфера не
  34.                          требуется.
  35.      D3DPOOL_SYSTEMMEM - расположить буфер в системной памяти. После сброса
  36.                          устройства восстановление буфера не требуется.
  37.      D3DPOOL_SCRATCH   - буфер расположен в системной памяти. После сброса
  38.                          устройства восстановление буфера не требуется, т.к
  39.                          драйвер о нем не знает. Можно производить с буфером
  40.                          различные действия - копировать, блокировать и .т.д
  41.                          ( не понимаю нафига этот флаг нужен? )
  42.   5. Адрес ячейки куда будет помещен указатель на созданный буфер
  43.   ---------------------------------------------------------
  44.   Если все прошло успешно, метод возвращает D3D_OK.
  45.   Если нет, то D3DERR_INVALIDCALL, D3DERR_OUTOFVIDEOMEMORY
  46.   или D3DERR_OUTOFMEMORY.
  47.  
  48.   Lock передается 4 параметра.
  49.   ----------------------------
  50.   1. Смещение в байтах относительно начала. Обычно используется 0.
  51.   2. Размер данных в байтах которые нужно блокировать. Если 0 то будет
  52.      блокирован весь буфер.
  53.   3. Указатель на заполненный буфер.
  54.   4. Комбинация флагов. Как будет использоваться память при блокировании.
  55.      D3DLOCK_DISCARD     - приложение отбрасывает этот буфер. Direct3D
  56.                            возвращает указатель на буфер в новом месте и
  57.                            производит рендеринг из старого места.
  58.                            ( еще написано, что, вроде, скорость при этом не
  59.                            падает, а наоборот может возрасти )
  60.                            Этот флаг работает только, если буфер создан с
  61.                            флагом D3DUSAGE_DYNAMIC.
  62.      Насчет двух следующих флагов я не совсем понял, возможны ошибки.
  63.      D3DLOCK_NOOVERWRITE - позволяет изменять содержимое буфера если он
  64.                            уже был ранее блокирован но не был разблокирован
  65.                            до нашего блокирования. Или вроде наоборот.                          
  66.                            Можно включать для оптимизации когда приложение
  67.                            только прибавляет данные в буфер.
  68.                            Этот флаг работает только, если буфер создан с
  69.                            флагом D3DUSAGE_DYNAMIC.
  70.      D3DLOCK_NOSYSLOCK   - Позволяет системе исполнять свои обязанности
  71.                            ( например движение курсора ) во время долгого
  72.                            блокирования буфера. И т.д и т.п.
  73.      D3DLOCK_READONLY    - Приложение не будет писать в буфер. Служит для
  74.                            оптимизации. Не употребляется с D3DLOCK_DISCARD.
  75.                            Нельзя использовать, если буфер создан с флагом
  76.                            D3DUSAGE_WRITEONLY.
  77.   ------------------------------------------------
  78.   Если все прошло успешно, метод возвращает D3D_OK.
  79.   Если нет, то D3DERR_INVALIDCALL.
  80.  
  81.   UnLock передается указатель на блокируемый буфер.
  82.   -----------------------------------------------
  83.   Если все прошло успешно, метод возвращает D3D_OK.
  84.   Если нет, то D3DERR_INVALIDCALL.

7.6. Функция Set_Render_Parameters

Все, что содержит данная функция, можно было поместить в Init_Direct3D. Почему этого не было сделано, вы сейчас поймете. В уроке номер 2 я рассказывал о проблеме потерянного устройства. При возвращении в наше приложение по Alt+Tab нам нужно сбрасывать устройство. При этом теряется ВСЯ информация, связанная с ним, и нам необходимо ее восстанавливать. В этой функции содержатся все настройки касательно параметров рендеринга. Мы можем легко их восстановить, поместив вызов этой функции сразу после сбрасывания устройства.

Код (Text):
  1.  
  2.   Установка параметров рендеринга:
  3.   --------------------------------
  4.  
  5.   d3dev8  SetTransform, pd3dDevice, D3DTS_WORLD, ADDR WorldMatrix  
  6.   d3dev8  SetTransform, pd3dDevice, D3DTS_VIEW, ADDR ViewMatrix  
  7.   d3dev8  SetTransform, pd3dDevice, D3DTS_PROJECTION, ADDR ProjectionMatrix
  8.    
  9.   d3dev8  SetRenderState, pd3dDevice, D3DRS_CULLMODE, D3DCULL_NONE
  10.   d3dev8  SetRenderState, pd3dDevice, D3DRS_LIGHTING, NULL
  11.   d3dev8  SetRenderState, pd3dDevice, D3DRS_ZENABLE, D3DZB_TRUE
  12.    
  13.   d3dev8  SetVertexShader, pd3dDevice, D3DFVF_CUSTOMVERTEX
  14.   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):
  1.  
  2. Справка
  3.  
  4.   SetTransform передается 2 параметра.
  5.   ------------------------------------
  6.   1. Название обьекта, который будет модифицирован.
  7.   2. Адрес расположения матрицы.
  8.   ------------------------------------------------
  9.   Если все прошло успешно, метод возвращает D3D_OK.
  10.   Если нет, то D3DERR_INVALIDCALL.
  11.  
  12.   SetRenderState передается 2 параметра.
  13.   --------------------------------------
  14.   1. Название параметра, который будет модифицирован.
  15.   2. Что с ним надо делать.
  16.   ------------------------------------------------
  17.   Если все прошло успешно, метод возвращает D3D_OK.
  18.   Если нет, то D3DERR_INVALIDCALL.
  19.  
  20.   SetVertexShader передается указатель на созданный шейдер.
  21.                   Или в простом случае просто указывается формат вершины.
  22.   ------------------------------------------------
  23.   Если все прошло успешно, метод возвращает D3D_OK.
  24.   Если нет, то D3DERR_INVALIDCALL.
  25.  
  26.   SetStreamSource передается 3 параметра.
  27.   ------------------------------------
  28.   1. Номер потока от 0 до максимального числа потоков -1
  29.   2. Указатель на созданный VertexBuffer
  30.   3. Размер элемента в буфере в байтах.
  31.   ------------------------------------------------
  32.   Если все прошло успешно, метод возвращает D3D_OK.
  33.   Если нет, то D3DERR_INVALIDCALL.

7.7. Функция Render_Scene

Самая главная функция. В ней происходит вызов Direct3D, чтобы он отрендерил сцену, а затем показал на экран.

Код (Text):
  1.  
  2.   Полный текст функции:
  3.   ---------------------
  4.  
  5.  .DATA
  6.    Zvalue      dd  1.0f
  7.    clearcolor  dd  0                    
  8.    
  9.   .CODE
  10.    d3dev8  TestCooperativeLevel, pd3dDevice  
  11.    cmp     eax, D3DERR_DEVICELOST                      
  12.    jne     noreset                                        
  13.    ret                                                    
  14.    noreset:                                                
  15.    cmp     eax, D3DERR_DEVICENOTRESET                    
  16.    jne     noreset2                                        
  17.    d3dev8  Reset, pd3dDevice, ADDR d3dpp.BackBufferWidth
  18.    
  19.    invoke  Set_Render_Parameters                           !!!!!!
  20.  
  21.    noreset2:                                              
  22.    
  23.    invoke  Animate_Scene                          ; Анимация сцены
  24.    
  25.    d3dev8  Clear,pd3dDevice,0,NULL,D3DCLEAR_TARGET or D3DCLEAR_ZBUFFER,\
  26.            0,Zvalue, 0        
  27.  
  28.    d3dev8  DrawPrimitive, pd3dDevice, D3DPT_TRIANGLELIST, 0 , CountPrimitive
  29.    d3dev8  Present, pd3dDevice, NULL, NULL, NULL, NULL
  30.  
  31.    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 придется использовать, а пока все и так отлично работает. Если возможно сократить обьем вызываемого, почему не сделать этого, да и размер программы меньше :smile3:.

Код (Text):
  1.  
  2. Справка
  3.  
  4.   DrawPrimitive передается 3 параметра.
  5.   -------------------------------------
  6.   Отрисовывает неиндексированные геометрические примитивы определяемого формата
  7.   из установленного в StreamSource буфера
  8.  
  9.   1. Тип примитива.
  10.      D3DPT_POINTLIST     - изолированные точки
  11.      D3DPT_LINELIST      - изолированные линии. Если в буфере будет меньше чем
  12.                            2 вершины, при вызове возникнет ошибка.
  13.      D3DPT_LINESTRIP     - вершины образуют ломаную линию. Если в буфере будет
  14.                            меньше чем 2 вершины, при вызове возникнет ошибка.
  15.      D3DPT_TRIANGLELIST  - изолированные треугольники.
  16.      D3DPT_TRIANGLESTRIP - сетка из треугольников.
  17.      D3DPT_TRIANGLEFAN   - сетка из треугольников, но все они сходятся в
  18.                            одной вершине.
  19.   2. Номер начального примитива
  20.   3. Количество примитивов
  21.   ------------------------------------------------
  22.   Если все прошло успешно, метод возвращает D3D_OK.
  23.   Если нет, то D3DERR_INVALIDCALL.

7.8. Функция Animate_Scene

Довольно сносный флаг у нас уже имеется. Но где вы видели абсолютно плоский флаг висящий в пространстве. Анимируем наш флаг, чтобы он смотрелся еще лучше. Обычная функция Sin или Cos поможет нам в этом. Конечно супер реалистичного эффекта мы не получим, но кое-что несомненно. Предлагаю вычислять значение Sin на определенной координате X и применять к координате Z. Получится, что квадраты, составляющие флаг, будут циклически плавать по Z.

Вот исходный код:

Код (Text):
  1.  
  2.   Анимация флага:
  3.   ---------------
  4.  
  5.   .DATA    
  6.     x                 dd 0.1f    ; Начальное значение
  7.     x2                dd 0.0f
  8.     z                 dd 0.0f
  9.     zcoeff            dd 0.3f
  10.     zcorrect          dd 10.0f
  11.     sinx              dd 0.0f
  12.    
  13.   .DATA?
  14.     checktime         dd ?   ;
  15.     color             db ?   ;
  16.     colorshift        db ?   ;
  17.     flag              dd ?   ;
  18.        
  19.   .CODE
  20.    invoke  GetTickCount      ; Получаем число тиков
  21.    mov     ebx, checktime
  22.    add     ebx, 50           ; Добавляем к нему коэффициент
  23.    cmp     eax, ebx
  24.    jb      noColor           ; Если не наступило время обновления, то выход
  25.    mov     checktime, eax
  26.    
  27.    cmp     flag, 1           ; Здесь происходит проверка, которая позволяет
  28.    je      goDec             ; узнать какой цвет был и какой нужно
  29.    inc     color             ; использовать далее
  30.    cmp     color, 164        ;
  31.    jb      noxorFlag         ; Нужно для циклического изменения цвета фона
  32.    xor     flag, 1           ; от синего до красного по кругу
  33.    noxorFlag:                ;
  34.    jmp     exitt             ;
  35.    goDec:                    ;
  36.    dec     color             ;
  37.    cmp     color, 0          ;
  38.    ja      exitt             ;
  39.    xor     flag, 1           ;
  40.    add     colorshift, 8     ;
  41.    cmp     colorshift, 24    ;
  42.    jb      noincshift        ;
  43.    mov     colorshift, 0     ;
  44.    noincshift:               ;
  45.    exitt:                    ;
  46.  
  47.    d3dvb8  Lock1, pd3dVertexBuffer, NULL, CountData, ADDR pLockBuffer, NULL
  48.    
  49.    mov     esi, pLockBuffer             ; Обновляем цвет фона
  50.    add     esi, 28                      ;
  51.    movzx   eax, color                   ;
  52.    movzx   ecx, colorshift              ;
  53.    shl     eax, cl                      ;
  54.    mov     [esi], eax                   ;
  55.                                        
  56.    mov     edi, pLockBuffer             ; А здесь вычисляем нужное значение
  57.    add     edi, 104                     ; посредством Sin
  58.    fld     x2                           ;
  59.    fadd    x                            ;
  60.    fst     x2                           ;
  61.    fstp    z                            ;
  62.    push    36                           ;
  63.    pop     ecx                          ;
  64.    column:                              ;
  65.    push    ecx                          ;
  66.    fld     z                            ;
  67.    fsub    zcoeff                       ;
  68.    fst     z                            ;
  69.    fsin                                 ;
  70.    fdiv    zcorrect                     ;
  71.    fstp    sinx                         ;
  72.    push    sinx                         ;
  73.    pop     eax                          ;
  74.    push    edi                          ; Обновляем Z координату в буфере
  75.    mov     ecx, 10                      ;
  76.    row:                                 ;
  77.    mov     DWORD PTR [edi], eax         ;
  78.    mov     DWORD PTR [edi+16], eax      ;
  79.    mov     DWORD PTR [edi+32], eax      ;
  80.    mov     DWORD PTR [edi+48], eax      ;
  81.    mov     DWORD PTR [edi+64], eax      ;
  82.    mov     DWORD PTR [edi+80], eax      ;
  83.    add     edi, 96*36                   ;
  84.    loop    row                          ;
  85.    pop     edi                          ;
  86.    add     edi, 96                      ;
  87.    pop     ecx                          ;
  88.    loop    column                       ;
  89.  
  90.    d3dvb8  Unlock, pd3dVertexBuffer    
  91.    noColor:
  92.    ret

Чтобы была плавная анимация, нам необходимо обновлять значения, ориентируясь на время. Я использовал функцию GetTickCount. Можно использовать функцию timeGetTime из библиотеки winmm, говорят она будет поточнее. Обновление значений у меня происходит только 20 раз в секунду, этого вполне хватает. Как вы помните, при занесении данных в буфер нам нужно его блокировать, так вот здесь он блокируется также 20 раз в секунду. Все остальное время он не трогается. Приложение при этом рисует его с максимальным количеством кадров, которое возможно, т.е я никак не ограничиваю число кадров ( при входе в функцию Animate_Scene просто проверяется наступило время обновления или нет. Если нет тогда сразу выход. ). Также одновременно обновляется значение цвета у фона.

Итого, фон меняет окраску, а флаг развевается :smile3: То, что мы здесь осуществили, является вертексной анимацией. Наверное слышали о такой? Как видите, совсем не сложно.

7.9. Функция Destroy_Direct3D

Уничтожаем все обьекты относящиеся к Direct3D.

Код (Text):
  1.  
  2.   d3dev8  Release, pd3dVertexBuffer  ; Уничтожаем Vertex буфер
  3.   d3dev8  Release, pd3dDevice        ; Уничтожаем Direct3DDevice8  
  4.   d3d8    Release, pd3d              ; Уничтожаем Direct3D8

При уничтожении обьектов рекомендуется соблюдать порядок. Не думаю, что, если вы сразу уничтожите обьект Direct3D, то остальные будут уничтожены автоматически. Хотя возможно всякое. Кто его знает, что было у разработчиков на уме :smile3:.

8. Послесловие

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

Код (Text):
  1.  
  2.   Порядок действий при запуске приложения:
  3.   ----------------------------------------
  4.   В диалоге уже создан обьект Direct3D
  5.   a) Создаем устройство Direct3DDevice
  6.   b) Заполняем три матрицы, вызывая Direct3D и передавая ему нужные параметры
  7.   c) Создаем VertexBuffer
  8.   d) Заносим в него данные, которые будем отрисовывать
  9.   e) Устанавливаем параметры рендеринга
  10.      ( Устанавливаем три матрицы, включаем Z буфер, освещение и т.п. )
  11.  
  12.   Все готово, можно отрисовывать сцену в цикле:
  13.   ---------------------------------------------
  14.   f) Анимируем нужное
  15.   g) Очищаем BackBuffer и Z Буфер
  16.   h) Рисуем требуемое посредством DrawPrimitive
  17.   i) Показываем все на экран методом Present
  18.   j) Переходим на пункт F и т.д. пока не потребуется завершение программы
  19.  
  20.   Завершение программы:
  21.   ---------------------
  22.   k) Уничтожаем VertexBuffer
  23.   l) Уничтожаем обьект Direct3Ddevice
  24.   m) Уничтожаем обьект Direct3D
  25.   n) Завершаем приложение

PS:

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

Как всегда исходник прилагается.

Все вопросы мыльте сюда mybox@aib.ru © keYMax


0 1.929
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532