Программирование 3D графики на Visual Basic 6 и DirectX 8. Часть 4
2.5. Использование прозрачности Чтобы приступить к изучению четвертой компоненты цвета A (альфа-компоненты), приготовим, как водится, опытный проект. Предыдущий проект преобразован для использования одной пары текстурных координат и цвета в вертексе. Нет смысла еще раз пояснять эти преобразования, просто возьмите проект на компакт-диске в папке Pr15. Обратите внимание на процедуру Setting, она вызывается из Sub Main непосредственно перед главным циклом. В этой процедуре мы будем тестировать различные варианты использования альфа-компоненты, пока она пустая. Альфа-компонента используется для имитации эффекта полупрозрачности предметов, так же, как и компоненты R, G и B, альфа – это байт в представлении цвета RGBA, либо значение от 0 до 1 в представлении DirectX. Наиболее распространенная формула для имитации эффекта полупрозрачности такая:
R = Arg1 * Alpha + Arg2 * (1 – Alpha)
Здесь R – это результирующий цвет, Arg1 и Arg2 – смешиваемые цвета. Альфа-компонента может применяться как «внутри» стадии текстурирования для работы с ее аргументами, так и к общему результату работы текстурирования для смешивания его с ранее нарисованным изображением. Рассмотрим сначала более простой первый вариант. Текстура к этому проекту сохранена в формате TGA и имеет в составе альфа-компоненту, также альфа-компоненту содержит и цвет вертексов, обратите внимание – все нижние вертексы в цилиндре имеют цвет &HFF0000FF, то есть:
Alpha = &HFF R = &H0 G = &H0 B = &HFF
А верхние вертексы в цилиндре имеют цвет &HFF, то есть Alpha в них равна 0. Так же, как и другие компоненты цвета вертекса, альфа-компонента линейно интерполируется между вертексами. Впишите в процедуру Setting такие строки:
D3DTOP_BLENDDIFFUSEALPHA для параметра D3DTSS_COLOROP обозначает, что используется альфа-компонента из DIFFUSE. Запускаем программу и видим, что цилиндр снизу покрыт текстурой, которая при подъеме постепенно переходит в DIFFUSE. Чтобы поменять местами TEXTURE и DIFFUSE, достаточно поменять местами COLORARG1 и COLORARG2:
Не правда ли, названия параметров говорят сами за себя? Так же, как и для цвета, для альфа-компоненты тоже предусмотрены оператор и аргументы, они применяются, если нужно каким-либо образом совместить альфа-компоненты нескольких аргументов. Рассмотрим такой вариант:
Мы применили две стадии текстурирования. В нулевой стадии накладывается цвет из текстуры, а альфа-компоненты текстуры и Diffuse перемножаются. В следующей стадии смешиваются цвет из Diffuse с текущим цветом (то есть ранее наложенным, из текстуры), оператор смешения использует текущее (то есть ранее вычисленное в нулевой стадии) значение альфа-компоненты. Мы рассмотрели несколько примеров использования прозрачности, но цилиндр пока прозрачным не был. Чтобы сделать его прозрачным, нужно чтобы в альфа-смешении участвовали не только аргументы текстурирования, но и ранее отрисованное изображение, то есть то, что находится в BackBuffer. При этом в качестве аргументов альфа-смешения будут выступать значение цвета, взятое из BackBuffer, и значение, полученное в результате текстурирования. Эти аргументы нужно указать:
Это более гибкий подход, позволяющий большего достичь, но и более ресурсоемкий, так как производится чтение из BackBuffer. В этих строках указывается, что в качестве множителя для первого аргумента – D3DRS_SRCBLEND, выступает альфа-компонента первого аргумента – D3DBLEND_SRCALPHA, для второго аргумента в качестве множителя мы выбрали D3DBLEND_INVSRCALPHA, то есть 1 – D3DBLEND_SRCALPHA. Далее мы выбрали оператор, применяемый для смешения полученных произведений – D3DRS_BLENDOP, этот оператор – D3DBLENDOP_ADD, то есть сложение. Использование такого типа альфа-смешения необходимо разрешить:
Code
d3dDevice.SetRenderState D3DRS_ALPHABLENDENABLE, 1[code] Закрасим цилиндр текстурой, с использования альфа-компоненты из DIFFUSE:
Цилиндр действительно стал полупрозрачным, но явно видны недостатки – временами изображение правильное, но через ближнюю сторону цилиндра, вторая видна не всегда. Это результат работы ZBuffer, если ближняя сторона цилиндра рисуется раньше, то дальняя в этом месте уже не рисуется, это отлично работало и избавляло нас от упорядочивания треугольников, пока мы не использовали прозрачность. Отключим ZBuffer:
Эта строка находится в процедуре Main. Что ж, изображение улучшилось, но небольшие погрешности в изображении остались, результирующий цвет получается разным, в зависимости от того, какая сторона цилиндра рисуется раньше. Кроме того отключение ZBuffer приведет к тому, что прозрачные предметы будут рисоваться даже тогда, когда они закрыты непрозрачными. Для того, чтобы правильно отображать предметы, использующие такой тип прозрачности, во время их рисования отключают не ZBuffer, а запись в него:
Code
d3dDevice.SetRenderState D3DRS_ZWRITEENABLE, 0
И прозрачные треугольники нужно упорядочивать так, чтобы они отображались от дальних к ближним. Упорядочивание – процесс весьма ресурсоемкий, ведь число треугольников может достигать сотен тысяч! Но иногда удается этого избежать. Например, в нашем цилиндре внутренняя сторона (а это обратная сторона треугольников) всегда находится сзади. Таким образом, достаточно отобразить цилиндр дважды – сначала внутреннюю, а затем и внешнюю стороны, и необходимость в упорядочивании отпадает. Для этого изменим процедуру Render:
Code
Private Sub Render() d3dDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, &H346666, 1, 0 d3dDevice.BeginScene
d3dDevice.EndScene d3dDevice.Present ByVal 0, ByVal 0, 0, ByVal 0 End Sub
Посмотрите на цилиндр теперь – он отображается корректно! Но такие проблемы с альфа-смешением возникают не всегда, если множитель при D3DRS_DESTBLEND равен единице – упорядочивание не требуется (запись в ZBuffer отключаем все равно!). Такие настройки могут применяться, например, для изображения огня. Впишите в процедуру Setting такие настройки:
Такой цилиндр отображается корректно без упорядочивания. При таком альфа-смешении задний фон всегда осветляется. Есть второе значение для D3DRS_BLENDOP, при котором не требуется упорядочивание треугольников, а изображение затемняется:
При таком значении D3DRS_BLENDOP, произведение цвета, полученного при текстурировании, на альфа-аргумент вычитается из цвета заднего плана. Напомню, что проект находится на компакт-диске в папке Pr15, причем процедура Setting в нем пуста.
2.6. Использование моделей. Mesh До сих пор для того, чтобы создать какой-либо геометрический объект, мы выполняли некоторые стандартные действия, занимающие достаточно объемный код. В первую очередь мы создавали флаговое описание вертекса, новый тип, соответствующий формату вертекса и сам вертексный буфер. Для более сложных моделей так же создавался индексный буфер. Далее для отображения нашего геометрического объекта мы сообщали устройству рендера (d3dDevice), какие вертексный и индексный буферы используется, какой размер у вертекса и как его обрабатывать (флаговое описание). Дальше мы вызывали процедуру рендера с указанием с какого вертекса и индекса следует начинать вывод и сколько треугольников отображать. Ну и, естественно, необходимо было не забыть уничтожить все ранее созданные объекты до завершения программы. А, между прочим, создавать, отображать и уничтожать геометрические объекты можно одной командой. В Direct3D существует класс D3DXMesh, предназначенный для хранения таких объектов. Объект (экземпляр класса) можно загружать из файла, так же некоторые простые формы, такие как куб, сфера и т. п. можно генерировать. Создадим новый проект, как обычно на основе предыдущего. Модуль modMain перепишем полностью. В разделе Declarations оставим только две общих переменных Running и FPS и добавим один объект класса D3DXMesh:
Code
Public Running As Boolean Public FPS As Long Dim Mesh As D3DXMesh
Добавим процедуры для инициализации света и материала:
Code
Private Sub InitLight() Dim Light As D3DLIGHT8 Light.Type = D3DLIGHT_DIRECTIONAL Light.Direction = vec3(-1, -1, 1) Light.diffuse.r = 1 Light.diffuse.g = 1 Light.diffuse.b = 1 d3dDevice.SetLight 0, Light d3dDevice.LightEnable 0, 1 End Sub
Private Sub InitMaterial() Dim Mat As D3DMATERIAL8 Mat.diffuse.r = 1 Mat.diffuse.g = 1 Mat.diffuse.b = 1 Mat.Ambient = Mat.diffuse d3dDevice.SetMaterial Mat End Sub
Уже привычная инициализация трансформаций:
Code
Private Sub InitMatrix() Dim Mtrx As D3DMATRIX D3DXMatrixIdentity Mtrx d3dDevice.SetTransform D3DTS_WORLD, Mtrx
D3DXMatrixLookAtLH Mtrx, vec3(0, 1, -3), vec3(0, 0, 0), vec3(0, 1, 0) d3dDevice.SetTransform D3DTS_VIEW, Mtrx End Sub
Процедура ClearAll будет содержать только уничтожение Mesh и D3DTerminate:
Code
Private Sub ClearAll() Set Mesh = Nothing D3DTerminate End Sub
Создадим новую процедуру InitMesh для инициализации модели, она выполняет те же функции, которые раньше выполняла процедура InitGeometry:
Code
Private Sub InitMesh() Set Mesh = d3dx.CreateBox(d3dDevice, 1, 1, 1, Nothing) End Sub
В этой процедуре из одной строки генерируется Box (прямоугольный параллелепипед) с размерами «1, 1, 1», то есть куб. В процедуре Render между BeginScene и EndScene так же будет всего одна строка:
Code
Private Sub Render() d3dDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, &H346666, 1, 0 d3dDevice.BeginScene
Mesh.DrawSubset 0
d3dDevice.EndScene d3dDevice.Present ByVal 0, ByVal 0, 0, ByVal 0 End Sub
И, наконец, Sub Main:
Code
Public Sub Main() Dim Mtrx As D3DMATRIX frmD3D.Show QFreqIni D3DInit frmD3D.hWnd InitMatrix InitMesh InitLight InitMaterial d3dDevice.SetRenderState D3DRS_CULLMODE, D3DCULL_CCW d3dDevice.SetRenderState D3DRS_ZENABLE, D3DZB_TRUE d3dDevice.SetRenderState D3DRS_LIGHTING, 1 Running = True Do While Running DoEvents D3DXMatrixRotationY Mtrx, QTime * 0.5 d3dDevice.SetTransform D3DTS_WORLD, Mtrx Render FPS = FPS + 1 Loop Unload frmD3D ClearAll End Sub
Здесь все привычно, добавился только вызов InitMesh – инициализации модели, в нашем случае куба. Запускаем программу и видим вращающийся куб. Модель, генерируемая D3DX, содержит в формате вертекса координаты и нормаль, поэтому мы можем наблюдать эффект освещения. Обратите внимание, команда для непосредственного отображения модели Mesh.DrawSubset имеет параметр. Дело в том, что сложные модели могут состоять из нескольких частей, называемых Subset, которые можно отображать независимо друг от друга. Это сделано для того, чтобы между отображением частей можно было поменять параметры рендеринга, например, сменить материал или текстуру. Или, представьте, что наша модель – автомобиль. Если ее колеса будут отдельными частями, их можно будет вращать, меняя трансформацию мира непосредственно перед их отображением. Познакомимся с другими примитивными формами, которые можно генерировать средствами D3DX. Заменим единственную строку в InitMesh на такую:
Code
Set Mesh = d3dx.CreateSphere(d3dDevice, 0.6, 32, 32, Nothing)
Теперь у нас не куб, а сфера. Параметры «0.6, 32, 32» – это радиус, число «меридианов» и число «параллелей». Так же можно сгенерировать цилиндр (а мы мучались!):
Code
Set Mesh = d3dx.CreateCylinder(d3dDevice, 0.7, 0.7, 1, 64, 1, Nothing)
Тор:
Code
Set Mesh = d3dx.CreateTorus(d3dDevice, 0.2, 0.7, 32, 32, Nothing)
И, ставшую уже классической, модель чайника:
Code
Set Mesh = d3dx.CreateTeapot(d3dDevice, Nothing)
Несколько другой синтаксис у процедуры генерации объемного текста:
Для того, чтобы это сработало, необходимо присвоить свойству Font формы frmD3D какой-нибудь шрифт, обязательно TrueType! Этим требованиям удовлетворяет, например, шрифт Courier New, но не Courier. Код проекта находится на компакт-диске в папке Pr16.
2.7. Имитация отражения. Spherical Environment Mapping До сих пор мы использовали для наложения текстур координаты, непосредственно заданные в вертексах. Существуют и другие способы, при которых текстурные координаты рассчитываются непосредственно при растеризации. Один из наиболее распространенных подходов – расчет текстурных координат исходя из позиции вертекса и камеры и нормали вертекса, используемый для имитации отражений. Все расчеты производит DirectX, освобождая нас от изучения оптики. Возьмем наш предыдущий проект, оставив в InitMesh генерацию чайника:
Code
Private Sub InitMesh() Set Mesh = d3dx.CreateTeapot(d3dDevice, Nothing) End Sub
Так же уберем все, связанное с использованием света и материала. Добавим в модуль modMain текстуру Tex и процедуру Setting, где будем испытывать нововведения, не забываем вписать вызов Setting перед главным циклом. Текстуру загрузим из файла sky.jpg, находящегося в папке Pr17. Содержимое Setting сначала будет таким:
Code
Private Sub Setting() d3dDevice.SetRenderState D3DRS_CULLMODE, D3DCULL_CCW d3dDevice.SetRenderState D3DRS_ZENABLE, D3DZB_TRUE
Set Tex = d3dx.CreateTextureFromFile(d3dDevice, "sky.jpg") TexFilter 0, TexF_TriLinear
d3dDevice.SetTextureStageState 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 d3dDevice.SetTextureStageState 0, D3DTSS_COLORARG1, D3DTA_TEXTURE d3dDevice.SetTexture 0, Tex End Sub
Пока все по старому, загрузили текстуру, настроили нулевую стадию текстурирования. Однако текстуры не видно, ведь мы не указали текстурные координаты, вся модель закрашена цветом, взятым из текстуры с координат 0, 0. Добавим в Setting такую строку:
Теперь чайник затекстурирован, но текстура не очень похожа на отражение. Описать работу этой команды, не вдаваясь в подробности, можно так – вычисляется направление нормали вертекса относительно направления на камеру. Если нормаль направлена на камеру – результирующие значения u и v текстурных координат равны нулю. Если нормаль направлена левее – значение u уменьшается, правее – увеличивается. Если нормаль направлена выше – значение v уменьшается, ниже – увеличивается. Изменения u и v лежат в диапазоне от -1 до 1, причем u и v не могут принять крайние значения диапазона одновременно, они лежат в пределах окружности с центром в начале координат и радиусом 1 на координатной плоскости. Но на нашей текстуре изображение отражения лежит на вчетверо меньшей (по площади) окружности с центром в точке 0.5, 0.5 и радиусом 0.5. Познакомимся с четвертой трансформацией, помогающей исправить эту ситуацию. Это трансформация D3DTS_TEXTURE0, позволяющая с помощью матриц задавать изменения текстурных координат нулевой стадии текстурирования, аналогичные трансформации существуют для всех восьми стадий. Добавим в процедуру Setting такие строки:
Необходимо там же объявить переменную Mtrx типа D3DMATRIX. Запускаем программу, но картинка не поменялась, все потому, что использование этой трансформации по умолчанию запрещено. Такой строкой мы разрешаем ее использовать для изменения двух текстурных координат:
Также если вы считаете, что данный материал мог быть интересен и полезен кому-то из ваших друзей, то вы бы могли посоветовать его, отправив сообщение на e-mail друга:
Игровые объявления и предложения:
Если вас заинтересовал материал «Программирование 3D графики на Visual Basic 6 и DirectX 8. Часть 4», и вы бы хотели прочесть что-то на эту же тему, то вы можете воспользоваться списком схожих материалов ниже. Данный список сформирован автоматически по тематическим меткам раздела.
Предлагаются такие схожие материалы:
Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями.
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи. [ Регистрация | Вход ]