Программирование 3D графики на Visual Basic 6 и DirectX 8. Часть 5
2.8. Преобразование содержимого моделей Поставим себе новую задачу – покроем чайник, кроме текстуры отражений, обычной текстурой. Но что делать, если наша модель не содержит в вертексах текстурных координат? Очевидно, нужно их туда добавить. Это можно сделать с помощью функции CloneMeshFVF класса D3DXMesh:
Code
Set Mesh = Mesh.CloneMeshFVF(0, D3DFVF_XYZ Or D3DFVF_NORMAL Or D3DFVF_TEX1, d3dDevice)
Эта функция меняет флаговое описание вертекса на новое и соответственно преобразует формат вертекса. Но поменять формат мало, нужно еще записать в новые поля соответствующие данные. Для начала извлечем из модели вертексный буфер, для этого в процедуре InitMesh создадим объектную переменную типа Direct3DVertexBuffer8 и массив для вертексов:
Code
Dim vBuf As Direct3DVertexBuffer8, vCnt As Long, n As Long, Vert() As vFormat
Кроме этого созданы вспомогательные переменные n для использования в цикле и vCnt, которая будет содержать количество вертексов. Для массива Vert() зададим тип:
Code
Private Type vFormat Pos As D3DVECTOR Normal As D3DVECTOR tu0 As Single tv0 As Single End Type
Далее так. Считываем из меша (модели) число вертексов в нем и ассоциируем объектную переменную vBuf с вертексным буфером меша:
Code
Set vBuf = Mesh.GetVertexBuffer vCnt = Mesh.GetNumVertices
Внимательно присмотритесь к первой строке. Функция GetVertexBuffer не создает новый вертексный буфер, а лишь возвращает указатель на имеющийся в меше, а объектная переменная vBuf ассоциируется с ним. Перезадаем размер массива в соответствие с числом вертексов в буфере и считываем данные вертексов в массив:
Прописываем в вертексы текстурные координаты, тут вариантов может быть множество, например можно поставить текстурные координаты tu0 и tv0 равными координатам x и z соответственно:
Code
For n = 0 To vCnt - 1 Vert(n).tu0 = Vert(n).Pos.x Vert(n).tv0 = Vert(n).Pos.z Next n
Возвращаем данные в вертексный буфер и уничтожаем, больше ненужную, объектную переменную vBuf:
Теперь модель чайника преобразована, ее вертексы, кроме позиции и нормалей, содержат также текстурные координаты для одной стадии текстурирования. Загрузим две текстуры, текстуру мрамора для нулевой стадии и текстуру отражений для первой, обе текстуры можно взять в папке Pr18:
Code
Set Tex0 = d3dx.CreateTextureFromFile(d3dDevice, "marb.jpg") d3dDevice.SetTexture 0, Tex0 Set Tex1 = d3dx.CreateTextureFromFile(d3dDevice, "sky.jpg") d3dDevice.SetTexture 1, Tex1
Настроим параметры текстурирования для этих стадий:
Осталось инициализировать свет и материал, и программа готова:
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
Проект можно взять в папке Pr18.
2.9. Приближение к реальности Наш чайник выглядит не очень естественно, блеск слишком сильный, больше похоже не на фарфор, а на полированный алюминий. Для того чтобы отражение немного «приглушить», можно затемнить текстуру. Попробуйте понизить ее яркость с помощью любого графического редактора, и изображение станет более реалистичным, но это не очень удобно, представьте, что у вас множество моделей с различным блеском, для каждой хранить свою текстуру отражений? Можно было бы понижать яркость, задав в материале Diffuse серого цвета и умножив Texture на Diffuse, но Diffuse у нас уже занят, без него мы не сделаем свет. Для таких и подобных случаев в Direct3D предусмотрена специальная переменная TEXTUREFACTOR типа Long, в нее можно записывать любое значение и использовать в стадиях текстурирования наравне с другими аргументами. Перепишем настройку стадий текстурирования по новому, присвоим переменной TEXTUREFACTOR значение &H404040, соответствующее темно серому цвету:
Рассмотрим подробнее эти настройки. В нулевой стадии происходит умножение Texture на Diffuse, что, в сочетании с использованием света, дает эффект освещения, с этим мы уже знакомы. Но в четвертой строке мы видим новый параметр D3DTSS_RESULTARG, которому присваивается так же новое значение D3DTA_TEMP. Это обозначает, что результат умножения Texture на Diffuse попадет не в регистр Current, как обычно, а в специальный регистр Temp, предназначенный для временного хранения данных во время работы стадий текстурирования. В первой стадии текстура отражений умножается на TEXTUREFACTOR, что должно обеспечить ослабление блеска. Результат попадает в регистр Current, поскольку не указано другое. И во второй стадии суммируются результаты первых двух стадий для формирования окончательного результата. Такой подход более ресурсоемкий, чем тот, что был выбран в предыдущей главе. Используется уже не две, а три стадии текстурирования, что может отразиться несовместимостью с некоторыми старыми видеоадаптерами (смотрите Caps!). Но этот подход гораздо гибче, поскольку позволяет регулировать отражающую способность моделей. Проект можно взять в папке Pr18, этот вариант помечен ремарками. А мы продолжим эксперименты с нашим чайником. Поставим себе более сложную задачу – отобразить фарфоровый чайник с цветным рисунком и золотой полоской, при этом постараться обойтись двумя текстурами, включая карту отражений, и одним проходом рендера. В папке Pr19 на компакт-диске возьмем текстуры. Для отображения такой картинки, как в файле flower.tga, более удобно переделать вычисление текстурных координат таким образом:
Code
Private Sub InitMesh() Dim vBuf As Direct3DVertexBuffer8, vCnt As Long, n As Long, Vert() As vFormat Set Mesh = d3dx.CreateTeapot(d3dDevice, Nothing) Set Mesh = Mesh.CloneMeshFVF(0, D3DFVF_XYZ Or D3DFVF_NORMAL Or D3DFVF_TEX1, d3dDevice) Set vBuf = Mesh.GetVertexBuffer vCnt = Mesh.GetNumVertices ReDim Vert(vCnt - 1) D3DVertexBuffer8GetData vBuf, 0, vCnt * Len(Vert(0)), 0, Vert(0) For n = 0 To vCnt - 1 Vert(n).tv0 = 0.41 - Vert(n).Pos.y Vert(n).tu0 = Vert(n).Pos.x + 0.5 If Vert(n).tu0 > 1.5 Then If Vert(n).tv0 < 0.2 Then Vert(n).tv0 = 0.2 Else If Vert(n).tv0 < 0 Then Vert(n).tv0 = 0 End If If Vert(n).tv0 > 1 Then Vert(n).tv0 = 1 If Vert(n).tu0 < 0 Then Vert(n).tu0 = 0 If Vert(n).tu0 > 1 Then Vert(n).tu0 = 1 Next n D3DVertexBuffer8SetData vBuf, 0, vCnt * Len(Vert(0)), 0, Vert(0) Set vBuf = Nothing End Sub
Строго говоря, покрыть сложный трехмерный объект 2D текстурой без искажений невозможно, так или иначе, в некоторых местах текстура окажется сильно растянутой в каком-либо направлении, получить модель без этих недостатков можно, только если примириться с нестыковкой текстуры – со швами. Если в предыдущем проекте мы, как бы, проецировали текстуру сверху, приравнивая текстурные координаты координатам x и z, то для новой задачи более подходит поставить зависимость от x и y, чтобы не боках чайника, где и будет расположен рисунок, искажения были минимальны. Кроме того, в приведенном фрагменте кода значения текстурных координат ограничены, чтобы рисунок не повторялся. Более подробно пояснять этот фрагмент не буду – с точки зрения программирования Direct3D в нем нет ничего нового для нас. Что, с точки зрения оптических свойств материала, представляет собой золотая полоска? Чем она отличается от просто желтой полосы? Если на блестящей поверхности, окрашенной в желтый (или любой другой) цвет, цвет отражений не зависит от цвета поверхности, а цвет поверхности зависит от освещения, что мы и воспроизвели в прошлом примере, то в материалах, обладающий металлическим блеском, все несколько по-другому. Собственный цвет металла достаточно темный, им даже можно пренебречь, зато коэффициент отражения у него значительно выше, а у цветных металлов он еще и зависит от цвета отражаемого изображения. Вынесем все настройки, характеризующие данный пример, в отдельную процедуру Setting. Загрузим текстуры и включим фильтрацию:
Обратите внимание – результат этой операции попадает во временный регистр D3DTA_TEMP. В следующей, первой стадии текстурирования заполняются альфа и цветовая составляющие регистра D3DTA_CURRENT. Он не указан явно в качестве RESULTARG, так как является таковым по умолчанию. Цветовая составляющая получается умножением D3DTA_TEXTURE на D3DTA_DIFFUSE, то есть мы получаем текстуру с эффектом освещения:
Здесь указывается что, не смотря на то, что это первая стадия текстурирования, текстурные координаты берутся из нулевой стадии. Итак, после первой стадии текстурирования мы имеем в регистре D3DTA_TEMP изображение отражений с полной яркостью, в альфа канале D3DTA_CURRENT у нас альфа канал из текстуры Tex1, а в цветовом канале изображение освещенного чайника с текстурой. Следующая стадия:
Здесь мы задаем значение TEXTUREFACTOR и выполняем один из операторов, работающих с тремя аргументами – D3DTOP_MULTIPLYADD. Он умножает COLORARG1 на COLORARG2 и к полученному произведению прибавляет COLORARG0. В вычислении COLORARG2 мы впервые использовали модификатор D3DTA_ALPHAREPLICATE. Смысл его в том, что в используемом в комбинации с ним цветовом аргументе все три цветовых компоненты (R, G, B ) заменяются на альфа компоненту. Первый аргумент D3DTA_TEMP, содержащий, как мы помним, отражение, мы умножаем не на TEXTUREFACTOR, как в предыдущем примере, а на его альфа компоненту. К полученному ослабленному изображению отражений мы прибавляем D3DTA_CURRENT, где в данный момент находится изображение модели с текстурой Tex1 без блеска. В результате в регистре D3DTA_CURRENT получаем реалистичный фарфор. Теперь воспроизведем золото. В D3DTA_TEMP по-прежнему находится изображение отражений с полной яркостью, для того, чтобы сымитировать цветной (в данном случае желтый) металл, его необходимо умножить на соответствующий цвет. Снова применяем TEXTUREFACTOR, но теперь уже не альфа компоненту, а цвет:
Результат отправляем в D3DTA_TEMP, чтобы не потерять изображение фарфора, находящееся в D3DTA_CURRENT. Если убрать последнюю строку и запустить программу, можно увидеть золотой чайник. Итак, что мы имеем после четырех стадий текстурирования? В D3DTA_CURRENT находится изображение фарфора, в D3DTA_TEMP – золота, а в альфа канале D3DTA_CURRENT альфа канал текстуры Tex1, определяющий в каких местах должен быть виден фарфор, а в каких золото. Остается последняя стадия:
Задача выполнена. Полный код проекта можно взять в папке Pr19, но я настоятельно рекомендую разобрать и понять логику работы всех стадий текстурирования в этом, достаточно сложном, примере.
Также если вы считаете, что данный материал мог быть интересен и полезен кому-то из ваших друзей, то вы бы могли посоветовать его, отправив сообщение на e-mail друга:
Игровые объявления и предложения:
Если вас заинтересовал материал «Программирование 3D графики на Visual Basic 6 и DirectX 8. Часть 5», и вы бы хотели прочесть что-то на эту же тему, то вы можете воспользоваться списком схожих материалов ниже. Данный список сформирован автоматически по тематическим меткам раздела.
Предлагаются такие схожие материалы:
Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями.