Плюсы и минусы реализации рельефного текстурирования в DirectX 6.x

Каждый, кто когда-либо занимался трехмерным моделированием в каком-либо из существующих программных продуктов для 3D графики, наверняка применял технику рельефного текстурирования (bump mapping) в своем проекте. Фактически, современные трехмерные игровые движки очень интенсивно используют данную технику, поэтому художники и дизайнеры не ограничены в возможностях и с легкостью применяют ее для придания объема клёпке на металлической поверхности, например, или для получения эффектной кирпичной стены. Иными словами, данный прием позволяет без особых усилий придать визуальный объем любой детали в сцене, и, наоборот, любой объемной детали придать соответствующий внешний вид.

Последние достижения в области аппаратных и программных средств для потребителя привели нас к такому моменту, что мы можем начать реализацию bump mapping в играх, использующих DirectX API. Вкупе с procedural texturing techniques (программным текстурированием), с технологией AGP и возросшими вычислительными мощностями современных процессоров становится возможным реализация очень внушительных визуальных эффектов.

Вы можете сказать: "Я никогда не пользовался программами для трехмерного моделирования. Что такое bump mapping?" Хорошо, во-первых я должен пояснить, что способ, каким bump mapping реализован в DirectX 6.0, это немного не то, что "спецы" называют "настоящим" методом рельефного текстурирования (bump mapping). Если вы спросите Джима Блинна (Jimm Blinn) - что такое bump mapping (а скорее всего он знает, что это такое, так как он сам это и изобрел), то он наверняка скажет, что это отличается от того, о чем я вам хочу рассказать. Блинн наверняка рассказал бы вам, что это метод получения визуальных выпуклостей и вогнутостей на объекте, находящемся в трехмерном пространстве без изменения геометрической формы самого объекта. Это получается путем изменения яркости каждого пикселя в соответствии с текстурой, применяемой в качестве карты выпуклостей и вогнутостей. В процессе прорисовки каждого пикселя конкретного полигона, происходит "подсматривание" на текстуру, используемую в качестве карты рельефа (aka bump map). Значения, полученные в результате подсматривания, используются для соответствующего изменения нормалей к визуализируемому пикселю. Полученная таким образом выпукло — вогнутая нормаль используется в дальнейших просчетах уровня цветности и освещенности, и в результате мы получаем поверхность такой, как будто на ней есть выпуклости и впадины.

Неплохо звучит, правда? Ну, а теперь вы наверняка хотите узнать, как же это реализовать в DirectX 6.0. Хорошо, но сначала разрешите вас предупредить, что вы вряд ли сможете написать игровой или иной движок, просто используя то определение, что было дано выше. Маловероятно, что даже Блинн или любой другой "профи" смог бы реализовать это сейчас. И этому есть ряд причин. Первое, это то, что современное 3D "железо" не способно производить по-пиксельные вычисления освещенности. Вместо этого оно производит затенение по Гуро (Gouraud shading), где цвета интерполируются от значений, полученных в процессе просчета вершин (vertex). Во-вторых, даже если бы железо и могло реализовать bump mapping, вы все равно не смогли бы снабдить API необходимой информацией о нормалях и уровне освещенности. Ситуация очень знакома: "Если курица появилась из яйца, то откуда взялось яйцо?". Здесь мы получаем нечто подобное: "Если железо не может производить по-пиксельное вычисление освещенности объекта (даже в случае известного положения источников света), то оно тем более не сможет использовать bump mapping для изменения этой освещенности".

Что ж, раз мы не можем использовать "букварь" для реализации рельефного текстурирования на современном железе в среде DirectX 6.0, то нам надо искать какие-то другие пути. На настоящий момент их два. Первый — это использование многотекстурного или многопроходного рендеринга (процесса визуализации) для реализации эффекта выдавливания (embossed effect) в процессе bump mapping, и второй — это рельефное текстурирование для получения эффектов окружающей среды (environment-map bump mapping). Именно этот метод и реализован в DirectX 6.0 API.

Эффект выдавливания (Embossing)

Использование техники получения эффекта выдавливания или эффекта чеканки — это очень удобный способ придать дополнительный объем объекту, не усложняя его геометрию. При его применении мы, конечно, не сможем добиться тех результатов, которые можем получить при использовании bump mapping в DirectX 6, но все же у него есть одно важное преимущество — этот метод реализуем на аппаратном уровне современных 3D акселераторов. Техника выдавливания (embossing) использует карту высот для получения эффекта чеканки в виде выпуклостей на поверхности объекта путем затенения и осветления определенных участков поверхности таким образом, чтобы создавалось впечатление, что под воздействием света выпуклости отбрасывают тени (Рисунок 1). Существует множество способов реализации данного эффекта, но один из них, который может применятся на большинстве акселераторов (имеются ввиду даже самые простые, с поддержкой наложения только одной текстуры за проход и с минимумом функций alpha blending), реализуется на аппаратном уровне в три прохода.

Вот как он работает. Сначала вы должны подготовить карту высот, это может быть растровый файл изображения подготовленный художником или сгенерированный программным методом в отдельных случаях. Допустим, в случае с кирпичной стеной для получения имитации отверстий для артиллерии нам надо изобразить самые темные участки — они-то и будут отверсвтиями. Эта карта высот должна иметь цветовую интенсивность в дипазоне 0–0,5 (где 1,0 — это наиболее интенсивные (яркие) участки в значениях RGB 255, 255, 255). Далее производим копию данной карты, но только теперь ее интенсивность должна быть эквивалентна 0,5 минус значение интенсивности оригинала, т.е. получаем негатив. Данная операция нужна для совместимости со всеми типами существующих акселераторов (конечно, вы можете подготовить этот негатив заранее, а не заставлять процессор заниматься изготовлением негатива карты, но тогда в 2 раза вырастут требования по количеству необходимой памяти для хранения текстур).

При первом проходе ваш движок должен отрендерить полигон, используя в качестве текстуры вашу карту высот (Рисунок 1а). Сдвинуть UV координаты негатива карты таким образом, чтобы текстура была направлена в сторону света (Рисунок1б), причем в соответствии с величиной угла между вектором света и нормалью к каждой из вершин (vertex). В случае направленного источника света задача упрощается, и можно использовать только одну нормаль ко всему полигону, а вот в случае точечного или ненаправленного источника света мы должны сделать различное смещение для каждой из вершин. Угол, на который должна быть сдвинута текстура, зависит от типа и рисунка текстуры. Если ваша текстура предусматривает прорисовку крутых уклонов, то тогда излишнее смещение может разрушить весь визуальный эффект, в случае пологих наклонов недостаточное смещение будет портить картину.

При втором проходе, необходимо обеспечить смещение UV
координат треугольника в сторону источника света, произвести его рендеринг, используя в качестве текстуры негативную карту высот (Рисунок 1б), и произвести аддитивное альфа смешение (alpha blending) с результатом первого прохода. В этот момент кадровый буфер (framebuffer) акселератора будет содержать значения от 0 до 1, где 0,5 соответствует отсутствию какой-либо визульной деформации поверхности, пиксели со значением менее 0,5 будут соответствовать наклонам с теневой стороны, а со значением более 0,5 — возвышениям в сторону света. Результатом будет текстура с оттенками серого, с тенями и высветленными участками (Рисунок1с).

При третьем, последнем, проходе, мы рендерим треугольник, накладывая базовую текстуру (Рисунок 1д), а затем модулируем результатом второго прохода в режиме Х2. Этот режим, в отличие от обычного Х1, не позволит нам получить треугольник более темным, чем он есть на самом деле, так как по результатам второго прохода все гладкие участки имеют интенсивность 0,5, и режим Х2 присвоит им текущую интенсивность базовой текстуры с коэффициентом 1 (0,5х2=1). Еще одна особенность этого режима — это то, что участки с интенсивностью менее 0,5 будут выводиться с понижением яркости, а на участках интенсивнее 0,5 (т.е. поверхности обращенные к свету) будет увеличена контрасность изображения, что дает имитацию "бликов". (Рисунок 1е).

Рисунок 1. Этапы получения эффекта выдавливания.


Рисунок 1а. Карта высот


Рисунок 1б Негатив карты с нормализованной интесивностью в 0,5


Рисунок 1с Карта высот со смещенным негативом


Рисунок 1д Базовая текстура


Рисунок 1е Карта высот + смещенный негатив, базова текстура промодулированная в режиме Х2.

Величина смещения UV координат во время второго прохода зависит от вашего конкретного случая, и нет жестких правил, определяющих ее. Карты высот с резкими переходами могут вызвать визуальные дефекты, если смещение было излишним, с другой стороны, эффект выдавливания (чеканки) может быть не столь ощутим, если на мягких переходах высот будет применено недостаточное смещение. Правильная величина смещения выбирается исходя из типа карты и ее рисунка, а так же желания иметь более подчеркнутый или, наоборот, утонченный эффект на объекте.

Смещение UV координат может быть однократным, в случае направленного источника света, или многократным (просчитанным для каждой вершины участка поверхности), в случае применения ненаправленных источников света и их близком расположении к объекту. Для более реалистичной картины вы можете выбрать многократное смещение. (В исходных кодах программы, приложения к этой статье, вы сможете выбрать между этими двумя способами).

Чтобы улучшить производительность приложений, использующих данный метод, можно сократить время его полного исполнения до двух проходов, в случае применения акселераторов, поддерживающих мультитекстурирование (3Dfx Voodoo2, ATI Rage 128, 3DlabsPermedia3, Matrox G-400, и Nvidia Riva TNT). Но, к сожалению, их аппаратная реализация этого метода отличается друг от друга, и результаты будут не всегда одинаковыми. И если вы не поленитесь написать код реализации метода для каждого акселератора в отдельности, то заметное ускорение быстродействия будет достойной компенсацией за это.

Несмотря на то, что метод выдавливания предсказуем и легко реализуется, он все же далеко не универсален. Это просто трюк с осветлением и затенением. Он не позволяет управлять такими вещами, как отражение и манипуляции с картой окружающей обстановки сцены (environment map). Также он работает только в случае применения монохромного (чисто белый цвет) освещения. Конечно, можно реализовать воздействие на поверхность цветного источника света, но для этого нам нужно задействовать четвертый проход. Ну, и в конце программисту необходимо очень четко отслеживать величину сдвига негатива карты высот во втором проходе, чтобы не получить в результате полигон, похожий на лист изрезанный ножницами.

Пример исходного кода использует метод выдавливания как один из способов реализации рельефного текструрирования в DirectX 6. Я не стал дополнительно включать в примеры ряд других однопроходных методов реализации метода, так как на целом ряде сайтов производителей 3D акселераторов вы можете найти готовые варианты для конкретного аппаратного обеспечения.

Пример исходного кода

Listing 1.

Данный пример демонстрирует технику выдавливания. С целью получения более короткого кода некоторые из переменных описывающих статусы рендеринга и текстур (в оригинале renderstates и texturestagestates) опущены, часть из которых, особенно в случаях касающихся фильтрации, вы можете дополнительно посмотреть в исходных кодах программы "Emboss"

// Rendering Pass #1

lpD3DDevice->SetTexture( 0, lpBumpTexture );

//ignore FB color, just use the output of the SRC (the triangle we are about to render)

lpD3DDevice->SetRenderState( D3DRENDERSTATE_ALPHABLENDENABLE, FALSE);

lpD3DDevice->SetRenderState( D3DRENDERSTATE_SRCBLEND, D3DBLEND_ONE);

lpD3DDevice->SetRenderState( D3DRENDERSTATE_DESTBLEND, D3DBLEND_ZERO);

//set up the bump texture

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ADDRESS, D3DTADDRESS_WRAP );

lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE );

lpD3DDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

hr = lpD3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, D3DFVF_TLVERTEX, pVertices, 6, D3DDP_DONOTLIGHT);

// Rendering Pass #2

lpD3DDevice->SetTexture( 0, lpInvBumpTextureRaw );

//using the D3DBLEND_ONE for both SRC and DEST blend factors results in an

// additive blend. FB = (Src * 1) + (Dest * 1)

lpD3DDevice->SetRenderState( D3DRENDERSTATE_ALPHABLENDENABLE, TRUE );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_SRCBLEND, D3DBLEND_ONE );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_DESTBLEND, D3DBLEND_ONE );

// inverted bump texture

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

hr = lpD3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, D3DFVF_TLVERTEX, pVertices, 6, D3DDP_DONOTLIGHT);

// Rendering Pass #3

lpD3DDevice->SetTexture( 0, lpBaseTexture );

//The default blending mode with the frame buffer (D3DRENDERSTATE_TEXTUREMAPBLEND) is modulate

// so by picking the src and dst blend factors below, we get:

// Framebuffer color = (SrcColor*DestColor) + (DestColor*SrcColor), or 2*Dst*Src

// this is why the heightmap needs to be from 0 to 0.5 only, and not above 0.5

lpD3DDevice->SetRenderState( D3DRENDERSTATE_ALPHABLENDENABLE, TRUE );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_SRCBLEND, D3DBLEND_DESTCOLOR );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_DESTBLEND, D3DBLEND_SRCCOLOR );

// base texture

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ADDRESS, D3DTADDRESS_WRAP );

 

hr = lpD3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, D3DFVF_TLVERTEX, pVertices, 6, D3DDP_DONOTLIGHT);

DirectX 6 Environment-Map Bump Mapping

Еще один способ реализации рельефного текстурирования в DirectX 6 — это техника текстурирования с использованием текстуры (карты) окружающей обстановки 3D сцены (Environment-Map Bump Mapping). Однако данный метод может быть реализован только при использовании акселераторов, которые поддерживают его аппаратно. В ближайшие месяцы мы вряд ли сможем увидеть достаточное количество акселераторов, которые будут поддерживать данную технику текстурирования. На данный момент известно, что данную технологию будут поддерживать Matrox G400 и 3Dlabs Permedia3. Остальные производители, вероятнее всего, последуют их примеру позже. (Кстати, обзор Matrox G400 с картинками отображающими Environment-Map Bump Mapping, вы можете посмотреть здесьПрим. переводчика). Используя данную технологию, можно получить множество впечатляющих эффектов, но все же давайте сначала разберемся, что и как работает и способы её применения.

В данном случае DirectX bump mapping будет использовать две текстуры (не считая базовой текстуры объекта).

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

Вторая текстура — это карта окружающей среды, которая накладывается на исходный полигон, но уже с измененными UV координатами. Рисунок 2б окончательный вариант того, что сказано, где environment map — это сцена над водой, отрендеренная в текстуру с соответствующим пиксельным форматом.

Если изготовить environment map в виде яркостной карты, расположив источники света градиентно и по кругу (что то похожее на эффект, проявляющийся при использовании инструмента "баллончик с краской" из графических программ), то мы получим результат похожий на выдавливание, но теперь мы не ограничены в количестве применяемых источников света, плюс наше освещение не обязательно должно быть монохромным.

Использование данной технологии производится следующим образом. Сначала назначьте D3Dtexture2surfaces обе текстуры (базовую и environment) и заполните поверхность обычным образом. Назначьте D3Dtexture2 карту рельефа (карту изменений высот; если она не была подготовлена заранее, сгенерите ее на основании существующей карты высот) и установите нужные значения битовых флагов перед производством операции DirectDrawSurface Creation. Создание карты измененй высот - задача не тяжелая и включает в себя операции по сравнению рядом стоящих пикселей. Эти операции подробно описаны в Примере 2. После этого назначьте эту рельефную карту исходной поверхности.

Переменной D3DtextureStageState определяется стадия обработки текстур.

  • Stage 0 — Settexture to base texture (как обычно)
  • Stage 1 — Set texture to bump map
  • Stage 2 — Set texture to environment map


На первой стадии можно дополнительно менять опции рендеринга установкой следующих переменных

  • D3DTSS_BUMPENVMATxx (где xx — 00, 01, 10 или 11). Определяет размерность рельефной матрицы, значение 11 устанавливает 2х2 матрицу.
  • D3DTSS_BUMPENVSCALE и D3DTSS_BUMPENVOFFSET. Эти две переменные меняют эффект от использования яркостной компоненты в рельефной карте, если она в ней используется. Аспект использования яркостной компоненты описан ниже.


Последнее, что остается сделать, это произвести рендеринг треугольников в каждом кадре. Пример 2 — это отрывок кода, демонстрирующий реализацию описанной технологии на практике.

Пример исходного кода с реализацией Environment Map Bump Mapping

Listing 2. Code snippet showing DirectX 6 bump mapping.

//Pseudo code for creating a height differential map from a height map

//you would also have to handle a special case for the last row and column of the bitmap

for (y=0;y
for (x=0;x
HeightDifferentialMap(x,y).dU = HeightMap(x,y).dU — HeightMap(x+1,y).dU

HeightDifferentialMap(x,y).dU = HeightMap(x,y).dU — HeightMap(x,y+1).dU

Next

Next

//DEVCAPS bits check (assumes you got the CAPS already)

if( pd3dDevDescription->dwTextureOpCaps & D3DTEXOPCAPS_BUMPENVMAP )

DoSomething();//this device supports bump mapping

Else

DontDoAnything();//this device does not support bump mapping

 

//Setup of the bump map surface (assumes a direct draw surface descripton, ddsd, structure

// is set up already, and that pddsurface is a pointer to a DirectDrawSurface)

ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;

ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_SYSTEMMEMORY;

ddsd.ddsCaps.dwCaps2 = 0L;

// Pix format for DuDv88 bumpmap, could also do DuDvL556 or 888

ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);

ddsd.ddpfPixelFormat.dwFlags = DDPF_BUMPDUDV;

ddsd.ddpfPixelFormat.dwBumpBitCount = 16;

ddsd.ddpfPixelFormat.dwBumpDuBitMask = 0x000000ff;

ddsd.ddpfPixelFormat.dwBumpDvBitMask = 0x0000ff00;

ddsd.ddpfPixelFormat.dwBumpLuminanceBitMask = 0x00000000;

// Create the bumpmap's surface and texture objects

if( FAILED( pDD->CreateSurface( &ddsd, &(pddsurface), NULL ) ) )

return NULL;

if( FAILED(pddsurface ->QueryInterface( IID_IDirect3DTexture2, (VOID**)&pd3dtexBumpTexture ) ) )

//Set the Base Texture

lpD3DDevice->SetTexure(0, lpBaseTexture);

lpD3DDevice->SetTexureStageState(0, D3DTSS_TEXCOORDINDEX, 0);

lpD3DDevice->SetTexureStageState (0, D3DTSS_COLOROP, D3DTOP_MODULATE); //gouraud

lpD3DDevice->SetTexureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE);

lpD3DDevice->SetTexureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);

lpD3DDevice->SetTexureStageState (0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);

lpD3DDevice->SetTexureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);

 

// Set up the Bump Texture

lpD3DDevice->SetTexure(1, lpBumpTexture);

lpD3DDevice->SetTexureStageState...

lpD3DDevice->SetTexureStageState (1, D3DTSS_TEXCOORDINDEX, 0);

lpD3DDevice->SetTexureStageState (1, D3DTSS_COLOROP, D3DTOP_BUMPENVMAP);

lpD3DDevice->SetTexureStageState (1, D3DTSS_COLORARG1, D3DTA_TEXTURE);

lpD3DDevice->SetTexureStageState (1, D3DTSS_COLORARG2, D3DTA_CURRENT);

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT00, DWORD(1.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT01, DWORD(0.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT10, DWORD(0.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT11, DWORD(1.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVLSCALE, DWORD(1.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVLOFFSET, DWORD(0.0f));

// Set up the Environment map texture

lpD3DDevice->SetTexure(2, lpEnvMapTexture);

lpD3DDevice->SetTexureStageState (2, D3DTSS_ADDRESS, D3DTADDRESS_CLAMP);

lpD3DDevice->SetTexureStageState (2, D3DTSS_TEXCOORDINDEX, 1);

lpD3DDevice->SetTexureStageState (2, D3DTSS_COLOROP, D3DTOP_ADD);

lpD3DDevice->SetTexureStageState (2, D3DTSS_COLORARG1, D3DTA_TEXTURE);

lpD3DDevice->SetTexureStageState (2, D3DTSS_COLORARG2, D3DTA_CURRENT);

Рельефная текстура (bump map texture) может поддерживать несколько новых пиксельных форматов, и все эти форматы являются нововведением в DirectX 6.0. Пиксельные форматы устанавливаются посредством флагов DDPF_BUMPDUDV и DDPF_BUMPLUMINANCE в период подсчета и создания поверхностей. Эти флаги позволяют описать три пиксельных формата: два размерностью 16 bits-per-pixel (DuDv 88 and 556DuDvL) и один 32 bits-per-pixel формат (888DuDvL).

Рельефная матрица 2х2 предназначена для вращения и масштабирования UV координат по следующей формуле:

[U’,V’] = [U,V] + [bump(u),bump(v)] * [ M00 M01 ]

[ M10 M11 ]

откуда вытекает:

U’ = U + (bump(u)*M00 + bump(v)*M10)

V’ = V + (bump(u)*M01 + bump(v)*M11) где

U и V — координаты текущего пикселя подлежащего визуализации, bump(U) и bump(V) — значения полученные с рельефной карты (bump map) в точке с координатами U и V; U' и V' — результирующие координаты используемые для извлечения текселей из environment map.

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

L’ = bumpmap(L) * BumpEnvScale + BumpEnvOffset

Stage output = EnvironmentMap(U’,V’) * L’ * Polygon diffuse color

Применение эффектов рельефного текстурирования

Самое интересное начинается тогда, когда мы видим результаты того, где
было использовано рельефное текстурирование. Некоторые стадии в процессе применения bump mapping, могут быть анимированы, что позоволяет реализовать некоторые наиболее удивительные эффекты. Среди них можно перечислить следующие:

  • Генерация карт для окружающей среды (environment maps) "на лету", в процессе выполнения программы. Этой картой может быть предварительно отвизуализированный кадр или карта с композицией источников света (если их несколько) или карта (текстура), полученная программным методом путем генерации по одному из шумовых алгоритмов (например, кудрявые, вихрящиеся облака). Рисунок 2б демонстрирует применение этой технологии (для получения этого скриншота использовалась программа — приложение к данной статье) — это реальный пример Environment Map Bump Mapping.
  • Программная генерация и модификация рельефной карты (bump map). Для этого может использоваться любая синусоидальная функция или эффект, имитирующий появление отверстий на поверхности от попадающих пуль — и это все комбинируется с рельефной картой в процессе выполнения программы ("на лету"). Приложение демонстрирует применение подобных эффектов. Волны на поверхности воды — это программно генерированная рельефная карта, пользователь может щелкнуть мышкой на поверхности воды и протащить мышку по поверхности, чтобы получить круги на воде, следующие за перемещающимся указателем мышки. Динамически меняющаяся рельефная карта позволяет получить соответствующее искажение поверхности в окончательном изображении.
  • Динамическое изменение значений BUMPENVMATхх позволяет производить вращение и масштабирование текселей, составляющих карту (текстуру) окружающей среды (environment map). Это проявляется в виде появления дополнительной "бугристости" поверхности. Например, персонаж вдруг резко может покрыться "гусиной кожей" в каком-либо из игровых моментов. И этот эффект достигается изменением всего двух значений в матрице (и всегo за два вызова процедур, определяющих RenderState).
  • Динамическое изменение значений BUMPENVOFFSET и BUMPENVSCALE позволяет изменить или анимировать цветовую яркость "выдавленного" текселя. Данным приемом можно воспользоваться для вращения подсвеченной рельефной поверхности вокруг объекта.
  • Рельефные текстуры и текстуры окружающей среды можно подвергнуть дополнительной обработке посредством filtering или MIP-map tecnique. Это можно использовать для повышения детализации объекта c bump mapping в тот момент, когда персонаж подходит к нему достаточно близко, что позволит сделать рельефность более очевидной.

Рисунок2. Иллюстрация DirectX 6 Bump Mapping




Рисунок 2а. Сцена, воспроизведенная на текстуру, которая будет использована в качестве environment map для поверхности воды.



Рисунок 2б.Сцена, воспроизведенная на текстуру. Environment map искажена методом DirectX Bump Mapping с программной генерацией рельефной карты.



Рисунок 2с. Аналогия 2б, только визуализация в режиме wireframe

Теперь, когда вы знаете, как реализовать Bump Mapping в DirectX 6.0, давайте поговорим о том, что еще возможно реализовать ( я сказал "возможно" потому, что я сам еще этого не пробовал), используя подобную технологию.

ТЕХНИКА ВЫДАВЛИВАНИЯ (EMBOSSING). Используя пример реализации техники выдавливания, описанный ранее в этой статье, вы можете создать эффект воздействия точечного источника света на объект, используя карту с внедренным источником. Однако, в противоположность технике embossing с использованием нескольких текстур, вы можете вместо них применить карты с освещением в различных цветовых оттенках. Если вы будете использовать данную технику
совместно с просчетом освещения вершин (vertex), то, скорее всего, вам придется поэкспериментировать с масштабом и затемненными участками световой карты для того, чтобы полученный эффект вписывался в общую картину освещения вашей трехмерной сцены.

ОТРАЖЕНИЕ НА ВОДЕ (WATER REFLECTION). Динамически, программно изменяемая по синусоиде рельефная карта водной поверхности позволяет достичь изумительных натуральных эффектов без дополнительного усложнения общей геометрии. (Рисунок 2).

ИСКАЖЕНИЯ В ВОДНОЙ СРЕДЕ (WATER DISTORTION). Эффект, подобный тому, что реализован в software режиме Quake (где целый кадровый буфер подвергался синусоидальной анимации). Сначала вы должны провести рендеринг трехмерной сцены на поверхность текстуры. Далее, используя ее в качестве карты окружающей среды, просчетом треугольников заполнить кадровый буфер и сразу подвергнуть всю сцену рельефному текстурированию по синусоидальному образцу для получения необходимого искажения. Таким образом, мы подчиним весь видеовыход волновому искажению производимому водной средой.

КРИВЫЕ ЗЕРКАЛА И ЛИНЗЫ (CIRCUS MIRRORS AND LENS EFFECTS). Разместив поверхность текстуры в поле зрения, отвизуализируйте сцену (или только часть ее), используя в качестве точки наблюдения место, где предполагается находится ЗЕРКАЛУ. Затем используйте полученное изображение в качестве текстуры для зеркала и примените на него технику рельефного текстурирования, чтобы изменить отражение, даваемое зеркалом в соответствии с рельефной картой, которую вы примените. Таким образом вы можете создать зеркало, производящее отражение окружающей действительности по типу тех, что вы видели в "Комнатах смеха" и на цирковых площадках.

ТЕЛЕПОРТЫ, ПОДОБНЫЕ ТЕМ, ЧТО ВЫ МОГЛИ ВИДЕТЬ В ИГРЕ "SUPER MARIO 64". Анимированная текстура для рельефной карты, может повторить эффект, реализованный в игре Super Mario 64, когда портал или телепорт дрожал после того, как Марио прыгал в него. В Nintendo реализация этого эффекта была проведена геометрическим образом.

РЕАЛИЗАЦИЯ ПЕРЕХОДОВ МЕЖДУ УРОВНЯМИ ДЕТАЛИЗАЦИИ. Модели
с низким уровнем детализации (LOD) могут использовать рельефное текстурирование для того, что бы скрыть свою грубоватость ввиду малого количества использованных полигонов. Например, если дом имеет дверь с дверной ручкой и окно с подоконником, которые должны выделятся наружу, то в случае нахождения игрока достаточно далеко от дома данные детали с низким LOD будут иметь кубическую форму. Если применить на них рельефное текстурирование, то эти мелкие детали будут иметь освещенные и затененные участки, имитируя натуральную объемность. Когда же игрок подойдет к ним достаточно близко, то можно будет увеличить LOD и отключить bump mapping для этих деталей.

ЭФФЕКТ РАСТВОРЕНИЯ (ИСЧЕЗНОВЕНИЯ) ПУЛЕВЫХ ОТВЕРСТИЙ. В фильме Terminator 2 мы могли видеть, как отверстия от попавших пуль на серебристом теле Терминатора постепенно затягиваются и исчезают, как бы растворяясь. Этот эффект может быть достигнут применением предварительно рассчитанной рельефной текстуры пулевого отверстия на поверхность и дальнейшего постепенного динамического уменьшения значения BUMPENVMATхх до 0.

ИСКАЖЕНИЯ, ВЫЗЫВАЕМЫЕ ТЕПЛЫМ ВОЗДУХОМ, ПОДНИМАЮЩИМСЯ НАД ЗЕМЛЕЙ. Этот эффект достигается путем аналогичным для искажений в водной среде, но только применительно к определенной части сцены. Это может быть как раз тем примером, где вы захотите использовать для текстуры пиксельный формат DuDvL, где яркостная составляющая на рельефной карте будет постепенно уменьшатся кверху, создавая затухание эффекта в верхней части сцены.

ЭФФЕКТЫ РАЗВИВАЮЩИХСЯ НА ВЕТРУ ФЛАГОВ, КОЛЕБАНИЙ ШТОР/ОДЕЖДЫ. Программно вырабатывая анимированную рельефную карту или просто динамически меняя UV координаты рельефной карты, можно заставить одежду персонажа развиваться на ветру.

ДЕТАЛЬНЫЕ ТЕКСТУРЫ ЗЕМЛИ ДЛЯ АВИАСИМУЛЯТОРОВ. Идея проста -- изменение уровня детализации видимой поверхности в зависимости от расстояния до нее. Допустим, если вы летите на самолете на высоте 1000 футов, то земля под вами будет похожа на стеганое одеяло, когда же вы снизитесь до высоты в 10 футов (как раз за мгновение до того, как врежетесь в землю), детализация увеличится до такой степени, что вы в состоянии разглядеть колосья пшеницы. Или еще — вы можете применить bump mapping для имитации 20 футовых волн на воде, летя на высоте в 1000 футов, а когда вы снизитесь до 10 футовой высоты, можно дополнительно добавить эффекты дрожания и капель воды на стекле.

Реализация описанных технологий и методов

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

Также постарайтесь не забыть, для чего вообще разрабатывалась технология bump mapping изначально: высокая детализация без добавления дополнительных полигонов. Технология является особо желательной там, где разработчик планирует использование программного продукта на целом парке разнотипных по мощности машин или где просто не используется возможность выбора уровня детализации для моделей и объектов. (Например, строения на уровнях целого ряда 3D шутеров). Вы можете использовать оба метода (Embossingи Environment Map — Bump Mapping) для придания более реалистичного вида кирпичным стенам, изрезанным мраморным колоннам и заклепкам на старых железных лестницах.

Конечно, решаясь на применение технологии рельефного текстурирования, вы должны обязательно взвесить все плюсы (получение дополнительных визуальных эффектов без усложнения геометрии) и минусы (необходимость прописывать участки кода для различного аппаратного обеспечения и более высокие требования к акселератору по способности заполнения (fillrate) во время рендеринга), и только после этого принять решение. Последние графические акселераторы, выпущенные на массовый рынок, имеют достаточные мощности по части заполнения пикселями треугольников, поэтому один из минусов в ближайшем будущем уже не будет иметь такого значительного веса. Также стоит учитывать тот факт, что способность акселераторов накладывать несколько текстур за один проход дает значительный выигрыш в производительности, и программистам скоро не надо будет изыскивать многопроходные решения для наложения нескольких текстур, которые в основном рассчитаны на старый парк акселераторов, обладающих, к тому же, низкой скоростью заполнения.

Разработчики, которые пишут программы для PC, сталкиваются с необходимостью масштабировать свою программу под компьютеры с различной мощностью. К счастью, оба описанных в данной статье подхода к реализации эффектов рельефности на объектах помогают в масштабировании ваших приложений к различному аппаратному обеспечению без особых трудозатрат. Вам просто достаточно сделать так, чтобы bumpmapping отключалось при обнаружении более медленного процессора на компьютере и, наоборот, включалась на полную мощность в случае машины высокого класса. Так что, Bump Mapping сделает вам графику в вашем следующем проекте, ой, пардон, обещает сделать...

Автор статьи Kim Pallister, инженер по техническому маркетингу и публицист по процессорным технологиям Intel’s Developer Relations Group.




6 августа 1999 Г.

Плюсы и минусы реализации рельефного текстурирования в DirectX 6.x

Плюсы и минусы реализации рельефного текстурирования в DirectX 6.x

Каждый, кто когда-либо занимался трехмерным моделированием в каком-либо из существующих программных продуктов для 3D графики, наверняка применял технику рельефного текстурирования (bump mapping) в своем проекте. Фактически, современные трехмерные игровые движки очень интенсивно используют данную технику, поэтому художники и дизайнеры не ограничены в возможностях и с легкостью применяют ее для придания объема клёпке на металлической поверхности, например, или для получения эффектной кирпичной стены. Иными словами, данный прием позволяет без особых усилий придать визуальный объем любой детали в сцене, и, наоборот, любой объемной детали придать соответствующий внешний вид.

Последние достижения в области аппаратных и программных средств для потребителя привели нас к такому моменту, что мы можем начать реализацию bump mapping в играх, использующих DirectX API. Вкупе с procedural texturing techniques (программным текстурированием), с технологией AGP и возросшими вычислительными мощностями современных процессоров становится возможным реализация очень внушительных визуальных эффектов.

Вы можете сказать: "Я никогда не пользовался программами для трехмерного моделирования. Что такое bump mapping?" Хорошо, во-первых я должен пояснить, что способ, каким bump mapping реализован в DirectX 6.0, это немного не то, что "спецы" называют "настоящим" методом рельефного текстурирования (bump mapping). Если вы спросите Джима Блинна (Jimm Blinn) - что такое bump mapping (а скорее всего он знает, что это такое, так как он сам это и изобрел), то он наверняка скажет, что это отличается от того, о чем я вам хочу рассказать. Блинн наверняка рассказал бы вам, что это метод получения визуальных выпуклостей и вогнутостей на объекте, находящемся в трехмерном пространстве без изменения геометрической формы самого объекта. Это получается путем изменения яркости каждого пикселя в соответствии с текстурой, применяемой в качестве карты выпуклостей и вогнутостей. В процессе прорисовки каждого пикселя конкретного полигона, происходит "подсматривание" на текстуру, используемую в качестве карты рельефа (aka bump map). Значения, полученные в результате подсматривания, используются для соответствующего изменения нормалей к визуализируемому пикселю. Полученная таким образом выпукло — вогнутая нормаль используется в дальнейших просчетах уровня цветности и освещенности, и в результате мы получаем поверхность такой, как будто на ней есть выпуклости и впадины.

Неплохо звучит, правда? Ну, а теперь вы наверняка хотите узнать, как же это реализовать в DirectX 6.0. Хорошо, но сначала разрешите вас предупредить, что вы вряд ли сможете написать игровой или иной движок, просто используя то определение, что было дано выше. Маловероятно, что даже Блинн или любой другой "профи" смог бы реализовать это сейчас. И этому есть ряд причин. Первое, это то, что современное 3D "железо" не способно производить по-пиксельные вычисления освещенности. Вместо этого оно производит затенение по Гуро (Gouraud shading), где цвета интерполируются от значений, полученных в процессе просчета вершин (vertex). Во-вторых, даже если бы железо и могло реализовать bump mapping, вы все равно не смогли бы снабдить API необходимой информацией о нормалях и уровне освещенности. Ситуация очень знакома: "Если курица появилась из яйца, то откуда взялось яйцо?". Здесь мы получаем нечто подобное: "Если железо не может производить по-пиксельное вычисление освещенности объекта (даже в случае известного положения источников света), то оно тем более не сможет использовать bump mapping для изменения этой освещенности".

Что ж, раз мы не можем использовать "букварь" для реализации рельефного текстурирования на современном железе в среде DirectX 6.0, то нам надо искать какие-то другие пути. На настоящий момент их два. Первый — это использование многотекстурного или многопроходного рендеринга (процесса визуализации) для реализации эффекта выдавливания (embossed effect) в процессе bump mapping, и второй — это рельефное текстурирование для получения эффектов окружающей среды (environment-map bump mapping). Именно этот метод и реализован в DirectX 6.0 API.



Эффект выдавливания (Embossing)

Использование техники получения эффекта выдавливания или эффекта чеканки — это очень удобный способ придать дополнительный объем объекту, не усложняя его геометрию. При его применении мы, конечно, не сможем добиться тех результатов, которые можем получить при использовании bump mapping в DirectX 6, но все же у него есть одно важное преимущество — этот метод реализуем на аппаратном уровне современных 3D акселераторов. Техника выдавливания (embossing) использует карту высот для получения эффекта чеканки в виде выпуклостей на поверхности объекта путем затенения и осветления определенных участков поверхности таким образом, чтобы создавалось впечатление, что под воздействием света выпуклости отбрасывают тени (Рисунок 1). Существует множество способов реализации данного эффекта, но один из них, который может применятся на большинстве акселераторов (имеются ввиду даже самые простые, с поддержкой наложения только одной текстуры за проход и с минимумом функций alpha blending), реализуется на аппаратном уровне в три прохода.

Вот как он работает. Сначала вы должны подготовить карту высот, это может быть растровый файл изображения подготовленный художником или сгенерированный программным методом в отдельных случаях. Допустим, в случае с кирпичной стеной для получения имитации отверстий для артиллерии нам надо изобразить самые темные участки — они-то и будут отверсвтиями. Эта карта высот должна иметь цветовую интенсивность в дипазоне 0–0,5 (где 1,0 — это наиболее интенсивные (яркие) участки в значениях RGB 255, 255, 255). Далее производим копию данной карты, но только теперь ее интенсивность должна быть эквивалентна 0,5 минус значение интенсивности оригинала, т.е. получаем негатив. Данная операция нужна для совместимости со всеми типами существующих акселераторов (конечно, вы можете подготовить этот негатив заранее, а не заставлять процессор заниматься изготовлением негатива карты, но тогда в 2 раза вырастут требования по количеству необходимой памяти для хранения текстур).

При первом проходе ваш движок должен отрендерить полигон, используя в качестве текстуры вашу карту высот (Рисунок 1а). Сдвинуть UV координаты негатива карты таким образом, чтобы текстура была направлена в сторону света (Рисунок1б), причем в соответствии с величиной угла между вектором света и нормалью к каждой из вершин (vertex). В случае направленного источника света задача упрощается, и можно использовать только одну нормаль ко всему полигону, а вот в случае точечного или ненаправленного источника света мы должны сделать различное смещение для каждой из вершин. Угол, на который должна быть сдвинута текстура, зависит от типа и рисунка текстуры. Если ваша текстура предусматривает прорисовку крутых уклонов, то тогда излишнее смещение может разрушить весь визуальный эффект, в случае пологих наклонов недостаточное смещение будет портить картину.

При втором проходе, необходимо обеспечить смещение UV
координат треугольника в сторону источника света, произвести его рендеринг, используя в качестве текстуры негативную карту высот (Рисунок 1б), и произвести аддитивное альфа смешение (alpha blending) с результатом первого прохода. В этот момент кадровый буфер (framebuffer) акселератора будет содержать значения от 0 до 1, где 0,5 соответствует отсутствию какой-либо визульной деформации поверхности, пиксели со значением менее 0,5 будут соответствовать наклонам с теневой стороны, а со значением более 0,5 — возвышениям в сторону света. Результатом будет текстура с оттенками серого, с тенями и высветленными участками (Рисунок1с).

При третьем, последнем, проходе, мы рендерим треугольник, накладывая базовую текстуру (Рисунок 1д), а затем модулируем результатом второго прохода в режиме Х2. Этот режим, в отличие от обычного Х1, не позволит нам получить треугольник более темным, чем он есть на самом деле, так как по результатам второго прохода все гладкие участки имеют интенсивность 0,5, и режим Х2 присвоит им текущую интенсивность базовой текстуры с коэффициентом 1 (0,5х2=1). Еще одна особенность этого режима — это то, что участки с интенсивностью менее 0,5 будут выводиться с понижением яркости, а на участках интенсивнее 0,5 (т.е. поверхности обращенные к свету) будет увеличена контрасность изображения, что дает имитацию "бликов". (Рисунок 1е).

Рисунок 1. Этапы получения эффекта выдавливания.




Рисунок 1а. Карта высот


Рисунок 1б Негатив карты с нормализованной интесивностью в 0,5


Рисунок 1с Карта высот со смещенным негативом


Рисунок 1д Базовая текстура


Рисунок 1е Карта высот + смещенный негатив, базова текстура промодулированная в режиме Х2.

Величина смещения UV координат во время второго прохода зависит от вашего конкретного случая, и нет жестких правил, определяющих ее. Карты высот с резкими переходами могут вызвать визуальные дефекты, если смещение было излишним, с другой стороны, эффект выдавливания (чеканки) может быть не столь ощутим, если на мягких переходах высот будет применено недостаточное смещение. Правильная величина смещения выбирается исходя из типа карты и ее рисунка, а так же желания иметь более подчеркнутый или, наоборот, утонченный эффект на объекте.

Смещение UV координат может быть однократным, в случае направленного источника света, или многократным (просчитанным для каждой вершины участка поверхности), в случае применения ненаправленных источников света и их близком расположении к объекту. Для более реалистичной картины вы можете выбрать многократное смещение. (В исходных кодах программы, приложения к этой статье, вы сможете выбрать между этими двумя способами).

Чтобы улучшить производительность приложений, использующих данный метод, можно сократить время его полного исполнения до двух проходов, в случае применения акселераторов, поддерживающих мультитекстурирование (3Dfx Voodoo2, ATI Rage 128, 3DlabsPermedia3, Matrox G-400, и Nvidia Riva TNT). Но, к сожалению, их аппаратная реализация этого метода отличается друг от друга, и результаты будут не всегда одинаковыми. И если вы не поленитесь написать код реализации метода для каждого акселератора в отдельности, то заметное ускорение быстродействия будет достойной компенсацией за это.

Несмотря на то, что метод выдавливания предсказуем и легко реализуется, он все же далеко не универсален. Это просто трюк с осветлением и затенением. Он не позволяет управлять такими вещами, как отражение и манипуляции с картой окружающей обстановки сцены (environment map). Также он работает только в случае применения монохромного (чисто белый цвет) освещения. Конечно, можно реализовать воздействие на поверхность цветного источника света, но для этого нам нужно задействовать четвертый проход. Ну, и в конце программисту необходимо очень четко отслеживать величину сдвига негатива карты высот во втором проходе, чтобы не получить в результате полигон, похожий на лист изрезанный ножницами.

Пример исходного кода использует метод выдавливания как один из способов реализации рельефного текструрирования в DirectX 6. Я не стал дополнительно включать в примеры ряд других однопроходных методов реализации метода, так как на целом ряде сайтов производителей 3D акселераторов вы можете найти готовые варианты для конкретного аппаратного обеспечения.

Пример исходного кода

Listing 1.

Данный пример демонстрирует технику выдавливания. С целью получения более короткого кода некоторые из переменных описывающих статусы рендеринга и текстур (в оригинале renderstates и texturestagestates) опущены, часть из которых, особенно в случаях касающихся фильтрации, вы можете дополнительно посмотреть в исходных кодах программы "Emboss"



// Rendering Pass #1

lpD3DDevice->SetTexture( 0, lpBumpTexture );

//ignore FB color, just use the output of the SRC (the triangle we are about to render)

lpD3DDevice->SetRenderState( D3DRENDERSTATE_ALPHABLENDENABLE, FALSE);

lpD3DDevice->SetRenderState( D3DRENDERSTATE_SRCBLEND, D3DBLEND_ONE);

lpD3DDevice->SetRenderState( D3DRENDERSTATE_DESTBLEND, D3DBLEND_ZERO);

//set up the bump texture

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ADDRESS, D3DTADDRESS_WRAP );

lpD3DDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE );

lpD3DDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

hr = lpD3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, D3DFVF_TLVERTEX, pVertices, 6, D3DDP_DONOTLIGHT);

// Rendering Pass #2

lpD3DDevice->SetTexture( 0, lpInvBumpTextureRaw );

//using the D3DBLEND_ONE for both SRC and DEST blend factors results in an

// additive blend. FB = (Src * 1) + (Dest * 1)

lpD3DDevice->SetRenderState( D3DRENDERSTATE_ALPHABLENDENABLE, TRUE );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_SRCBLEND, D3DBLEND_ONE );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_DESTBLEND, D3DBLEND_ONE );

// inverted bump texture

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

hr = lpD3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, D3DFVF_TLVERTEX, pVertices, 6, D3DDP_DONOTLIGHT);

// Rendering Pass #3

lpD3DDevice->SetTexture( 0, lpBaseTexture );

//The default blending mode with the frame buffer (D3DRENDERSTATE_TEXTUREMAPBLEND) is modulate

// so by picking the src and dst blend factors below, we get:

// Framebuffer color = (SrcColor*DestColor) + (DestColor*SrcColor), or 2*Dst*Src

// this is why the heightmap needs to be from 0 to 0.5 only, and not above 0.5

lpD3DDevice->SetRenderState( D3DRENDERSTATE_ALPHABLENDENABLE, TRUE );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_SRCBLEND, D3DBLEND_DESTCOLOR );

lpD3DDevice->SetRenderState( D3DRENDERSTATE_DESTBLEND, D3DBLEND_SRCCOLOR );

// base texture

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

lpD3DDevice->SetTextureStageState( 0, D3DTSS_ADDRESS, D3DTADDRESS_WRAP );

 

hr = lpD3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, D3DFVF_TLVERTEX, pVertices, 6, D3DDP_DONOTLIGHT);


DirectX 6 Environment-Map Bump Mapping

Еще один способ реализации рельефного текстурирования в DirectX 6 — это техника текстурирования с использованием текстуры (карты) окружающей обстановки 3D сцены (Environment-Map Bump Mapping). Однако данный метод может быть реализован только при использовании акселераторов, которые поддерживают его аппаратно. В ближайшие месяцы мы вряд ли сможем увидеть достаточное количество акселераторов, которые будут поддерживать данную технику текстурирования. На данный момент известно, что данную технологию будут поддерживать Matrox G400 и 3Dlabs Permedia3. Остальные производители, вероятнее всего, последуют их примеру позже. (Кстати, обзор Matrox G400 с картинками отображающими Environment-Map Bump Mapping, вы можете посмотреть здесьПрим. переводчика). Используя данную технологию, можно получить множество впечатляющих эффектов, но все же давайте сначала разберемся, что и как работает и способы её применения.

В данном случае DirectX bump mapping будет использовать две текстуры (не считая базовой текстуры объекта).

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

Вторая текстура — это карта окружающей среды, которая накладывается на исходный полигон, но уже с измененными UV координатами. Рисунок 2б окончательный вариант того, что сказано, где environment map — это сцена над водой, отрендеренная в текстуру с соответствующим пиксельным форматом.

Если изготовить environment map в виде яркостной карты, расположив источники света градиентно и по кругу (что то похожее на эффект, проявляющийся при использовании инструмента "баллончик с краской" из графических программ), то мы получим результат похожий на выдавливание, но теперь мы не ограничены в количестве применяемых источников света, плюс наше освещение не обязательно должно быть монохромным.

Использование данной технологии производится следующим образом. Сначала назначьте D3Dtexture2surfaces обе текстуры (базовую и environment) и заполните поверхность обычным образом. Назначьте D3Dtexture2 карту рельефа (карту изменений высот; если она не была подготовлена заранее, сгенерите ее на основании существующей карты высот) и установите нужные значения битовых флагов перед производством операции DirectDrawSurface Creation. Создание карты измененй высот - задача не тяжелая и включает в себя операции по сравнению рядом стоящих пикселей. Эти операции подробно описаны в Примере 2. После этого назначьте эту рельефную карту исходной поверхности.

Переменной D3DtextureStageState определяется стадия обработки текстур.

  • Stage 0 — Settexture to base texture (как обычно)
  • Stage 1 — Set texture to bump map
  • Stage 2 — Set texture to environment map


На первой стадии можно дополнительно менять опции рендеринга установкой следующих переменных

  • D3DTSS_BUMPENVMATxx (где xx — 00, 01, 10 или 11). Определяет размерность рельефной матрицы, значение 11 устанавливает 2х2 матрицу.
  • D3DTSS_BUMPENVSCALE и D3DTSS_BUMPENVOFFSET. Эти две переменные меняют эффект от использования яркостной компоненты в рельефной карте, если она в ней используется. Аспект использования яркостной компоненты описан ниже.


Последнее, что остается сделать, это произвести рендеринг треугольников в каждом кадре. Пример 2 — это отрывок кода, демонстрирующий реализацию описанной технологии на практике.



Пример исходного кода с реализацией Environment Map Bump Mapping

Listing 2. Code snippet showing DirectX 6 bump mapping.



//Pseudo code for creating a height differential map from a height map

//you would also have to handle a special case for the last row and column of the bitmap

for (y=0;y<height;y++)

for (x=0;x<width;x++)

HeightDifferentialMap(x,y).dU = HeightMap(x,y).dU — HeightMap(x+1,y).dU

HeightDifferentialMap(x,y).dU = HeightMap(x,y).dU — HeightMap(x,y+1).dU

Next

Next

//DEVCAPS bits check (assumes you got the CAPS already)

if( pd3dDevDescription->dwTextureOpCaps & D3DTEXOPCAPS_BUMPENVMAP )

DoSomething();//this device supports bump mapping

Else

DontDoAnything();//this device does not support bump mapping

 

//Setup of the bump map surface (assumes a direct draw surface descripton, ddsd, structure

// is set up already, and that pddsurface is a pointer to a DirectDrawSurface)

ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;

ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_SYSTEMMEMORY;

ddsd.ddsCaps.dwCaps2 = 0L;

// Pix format for DuDv88 bumpmap, could also do DuDvL556 or 888

ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);

ddsd.ddpfPixelFormat.dwFlags = DDPF_BUMPDUDV;

ddsd.ddpfPixelFormat.dwBumpBitCount = 16;

ddsd.ddpfPixelFormat.dwBumpDuBitMask = 0x000000ff;

ddsd.ddpfPixelFormat.dwBumpDvBitMask = 0x0000ff00;

ddsd.ddpfPixelFormat.dwBumpLuminanceBitMask = 0x00000000;

// Create the bumpmap's surface and texture objects

if( FAILED( pDD->CreateSurface( &ddsd, &(pddsurface), NULL ) ) )

return NULL;

if( FAILED(pddsurface ->QueryInterface( IID_IDirect3DTexture2, (VOID**)&pd3dtexBumpTexture ) ) )

//Set the Base Texture

lpD3DDevice->SetTexure(0, lpBaseTexture);

lpD3DDevice->SetTexureStageState(0, D3DTSS_TEXCOORDINDEX, 0);

lpD3DDevice->SetTexureStageState (0, D3DTSS_COLOROP, D3DTOP_MODULATE); //gouraud

lpD3DDevice->SetTexureStageState (0, D3DTSS_COLORARG1, D3DTA_TEXTURE);

lpD3DDevice->SetTexureStageState (0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);

lpD3DDevice->SetTexureStageState (0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);

lpD3DDevice->SetTexureStageState (0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);

 

// Set up the Bump Texture

lpD3DDevice->SetTexure(1, lpBumpTexture);

lpD3DDevice->SetTexureStageState...

lpD3DDevice->SetTexureStageState (1, D3DTSS_TEXCOORDINDEX, 0);

lpD3DDevice->SetTexureStageState (1, D3DTSS_COLOROP, D3DTOP_BUMPENVMAP);

lpD3DDevice->SetTexureStageState (1, D3DTSS_COLORARG1, D3DTA_TEXTURE);

lpD3DDevice->SetTexureStageState (1, D3DTSS_COLORARG2, D3DTA_CURRENT);

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT00, DWORD(1.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT01, DWORD(0.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT10, DWORD(0.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVMAT11, DWORD(1.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVLSCALE, DWORD(1.0f));

lpD3DDevice->SetTexureStageState (1, D3DTSS_BUMPENVLOFFSET, DWORD(0.0f));

// Set up the Environment map texture

lpD3DDevice->SetTexure(2, lpEnvMapTexture);

lpD3DDevice->SetTexureStageState (2, D3DTSS_ADDRESS, D3DTADDRESS_CLAMP);

lpD3DDevice->SetTexureStageState (2, D3DTSS_TEXCOORDINDEX, 1);

lpD3DDevice->SetTexureStageState (2, D3DTSS_COLOROP, D3DTOP_ADD);

lpD3DDevice->SetTexureStageState (2, D3DTSS_COLORARG1, D3DTA_TEXTURE);

lpD3DDevice->SetTexureStageState (2, D3DTSS_COLORARG2, D3DTA_CURRENT);


Рельефная текстура (bump map texture) может поддерживать несколько новых пиксельных форматов, и все эти форматы являются нововведением в DirectX 6.0. Пиксельные форматы устанавливаются посредством флагов DDPF_BUMPDUDV и DDPF_BUMPLUMINANCE в период подсчета и создания поверхностей. Эти флаги позволяют описать три пиксельных формата: два размерностью 16 bits-per-pixel (DuDv 88 and 556DuDvL) и один 32 bits-per-pixel формат (888DuDvL).

Рельефная матрица 2х2 предназначена для вращения и масштабирования UV координат по следующей формуле:

[U’,V’] = [U,V] + [bump(u),bump(v)] * [ M00 M01 ]

[ M10 M11 ]

откуда вытекает:

U’ = U + (bump(u)*M00 + bump(v)*M10)

V’ = V + (bump(u)*M01 + bump(v)*M11) где

U и V — координаты текущего пикселя подлежащего визуализации, bump(U) и bump(V) — значения полученные с рельефной карты (bump map) в точке с координатами U и V; U' и V' — результирующие координаты используемые для извлечения текселей из environment map.

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

L’ = bumpmap(L) * BumpEnvScale + BumpEnvOffset

Stage output = EnvironmentMap(U’,V’) * L’ * Polygon diffuse color

Применение эффектов рельефного текстурирования

Самое интересное начинается тогда, когда мы видим результаты того, где
было использовано рельефное текстурирование. Некоторые стадии в процессе применения bump mapping, могут быть анимированы, что позоволяет реализовать некоторые наиболее удивительные эффекты. Среди них можно перечислить следующие:

  • Генерация карт для окружающей среды (environment maps) "на лету", в процессе выполнения программы. Этой картой может быть предварительно отвизуализированный кадр или карта с композицией источников света (если их несколько) или карта (текстура), полученная программным методом путем генерации по одному из шумовых алгоритмов (например, кудрявые, вихрящиеся облака). Рисунок 2б демонстрирует применение этой технологии (для получения этого скриншота использовалась программа — приложение к данной статье) — это реальный пример Environment Map Bump Mapping.
  • Программная генерация и модификация рельефной карты (bump map). Для этого может использоваться любая синусоидальная функция или эффект, имитирующий появление отверстий на поверхности от попадающих пуль — и это все комбинируется с рельефной картой в процессе выполнения программы ("на лету"). Приложение демонстрирует применение подобных эффектов. Волны на поверхности воды — это программно генерированная рельефная карта, пользователь может щелкнуть мышкой на поверхности воды и протащить мышку по поверхности, чтобы получить круги на воде, следующие за перемещающимся указателем мышки. Динамически меняющаяся рельефная карта позволяет получить соответствующее искажение поверхности в окончательном изображении.
  • Динамическое изменение значений BUMPENVMATхх позволяет производить вращение и масштабирование текселей, составляющих карту (текстуру) окружающей среды (environment map). Это проявляется в виде появления дополнительной "бугристости" поверхности. Например, персонаж вдруг резко может покрыться "гусиной кожей" в каком-либо из игровых моментов. И этот эффект достигается изменением всего двух значений в матрице (и всегo за два вызова процедур, определяющих RenderState).
  • Динамическое изменение значений BUMPENVOFFSET и BUMPENVSCALE позволяет изменить или анимировать цветовую яркость "выдавленного" текселя. Данным приемом можно воспользоваться для вращения подсвеченной рельефной поверхности вокруг объекта.
  • Рельефные текстуры и текстуры окружающей среды можно подвергнуть дополнительной обработке посредством filtering или MIP-map tecnique. Это можно использовать для повышения детализации объекта c bump mapping в тот момент, когда персонаж подходит к нему достаточно близко, что позволит сделать рельефность более очевидной.

Рисунок2. Иллюстрация DirectX 6 Bump Mapping




Рисунок 2а. Сцена, воспроизведенная на текстуру, которая будет использована в качестве environment map для поверхности воды.



Рисунок 2б.Сцена, воспроизведенная на текстуру. Environment map искажена методом DirectX Bump Mapping с программной генерацией рельефной карты.



Рисунок 2с. Аналогия 2б, только визуализация в режиме wireframe

Теперь, когда вы знаете, как реализовать Bump Mapping в DirectX 6.0, давайте поговорим о том, что еще возможно реализовать ( я сказал "возможно" потому, что я сам еще этого не пробовал), используя подобную технологию.

ТЕХНИКА ВЫДАВЛИВАНИЯ (EMBOSSING). Используя пример реализации техники выдавливания, описанный ранее в этой статье, вы можете создать эффект воздействия точечного источника света на объект, используя карту с внедренным источником. Однако, в противоположность технике embossing с использованием нескольких текстур, вы можете вместо них применить карты с освещением в различных цветовых оттенках. Если вы будете использовать данную технику
совместно с просчетом освещения вершин (vertex), то, скорее всего, вам придется поэкспериментировать с масштабом и затемненными участками световой карты для того, чтобы полученный эффект вписывался в общую картину освещения вашей трехмерной сцены.

ОТРАЖЕНИЕ НА ВОДЕ (WATER REFLECTION). Динамически, программно изменяемая по синусоиде рельефная карта водной поверхности позволяет достичь изумительных натуральных эффектов без дополнительного усложнения общей геометрии. (Рисунок 2).

ИСКАЖЕНИЯ В ВОДНОЙ СРЕДЕ (WATER DISTORTION). Эффект, подобный тому, что реализован в software режиме Quake (где целый кадровый буфер подвергался синусоидальной анимации). Сначала вы должны провести рендеринг трехмерной сцены на поверхность текстуры. Далее, используя ее в качестве карты окружающей среды, просчетом треугольников заполнить кадровый буфер и сразу подвергнуть всю сцену рельефному текстурированию по синусоидальному образцу для получения необходимого искажения. Таким образом, мы подчиним весь видеовыход волновому искажению производимому водной средой.

КРИВЫЕ ЗЕРКАЛА И ЛИНЗЫ (CIRCUS MIRRORS AND LENS EFFECTS). Разместив поверхность текстуры в поле зрения, отвизуализируйте сцену (или только часть ее), используя в качестве точки наблюдения место, где предполагается находится ЗЕРКАЛУ. Затем используйте полученное изображение в качестве текстуры для зеркала и примените на него технику рельефного текстурирования, чтобы изменить отражение, даваемое зеркалом в соответствии с рельефной картой, которую вы примените. Таким образом вы можете создать зеркало, производящее отражение окружающей действительности по типу тех, что вы видели в "Комнатах смеха" и на цирковых площадках.

ТЕЛЕПОРТЫ, ПОДОБНЫЕ ТЕМ, ЧТО ВЫ МОГЛИ ВИДЕТЬ В ИГРЕ "SUPER MARIO 64". Анимированная текстура для рельефной карты, может повторить эффект, реализованный в игре Super Mario 64, когда портал или телепорт дрожал после того, как Марио прыгал в него. В Nintendo реализация этого эффекта была проведена геометрическим образом.

РЕАЛИЗАЦИЯ ПЕРЕХОДОВ МЕЖДУ УРОВНЯМИ ДЕТАЛИЗАЦИИ. Модели
с низким уровнем детализации (LOD) могут использовать рельефное текстурирование для того, что бы скрыть свою грубоватость ввиду малого количества использованных полигонов. Например, если дом имеет дверь с дверной ручкой и окно с подоконником, которые должны выделятся наружу, то в случае нахождения игрока достаточно далеко от дома данные детали с низким LOD будут иметь кубическую форму. Если применить на них рельефное текстурирование, то эти мелкие детали будут иметь освещенные и затененные участки, имитируя натуральную объемность. Когда же игрок подойдет к ним достаточно близко, то можно будет увеличить LOD и отключить bump mapping для этих деталей.

ЭФФЕКТ РАСТВОРЕНИЯ (ИСЧЕЗНОВЕНИЯ) ПУЛЕВЫХ ОТВЕРСТИЙ. В фильме Terminator 2 мы могли видеть, как отверстия от попавших пуль на серебристом теле Терминатора постепенно затягиваются и исчезают, как бы растворяясь. Этот эффект может быть достигнут применением предварительно рассчитанной рельефной текстуры пулевого отверстия на поверхность и дальнейшего постепенного динамического уменьшения значения BUMPENVMATхх до 0.

ИСКАЖЕНИЯ, ВЫЗЫВАЕМЫЕ ТЕПЛЫМ ВОЗДУХОМ, ПОДНИМАЮЩИМСЯ НАД ЗЕМЛЕЙ. Этот эффект достигается путем аналогичным для искажений в водной среде, но только применительно к определенной части сцены. Это может быть как раз тем примером, где вы захотите использовать для текстуры пиксельный формат DuDvL, где яркостная составляющая на рельефной карте будет постепенно уменьшатся кверху, создавая затухание эффекта в верхней части сцены.

ЭФФЕКТЫ РАЗВИВАЮЩИХСЯ НА ВЕТРУ ФЛАГОВ, КОЛЕБАНИЙ ШТОР/ОДЕЖДЫ. Программно вырабатывая анимированную рельефную карту или просто динамически меняя UV координаты рельефной карты, можно заставить одежду персонажа развиваться на ветру.

ДЕТАЛЬНЫЕ ТЕКСТУРЫ ЗЕМЛИ ДЛЯ АВИАСИМУЛЯТОРОВ. Идея проста -- изменение уровня детализации видимой поверхности в зависимости от расстояния до нее. Допустим, если вы летите на самолете на высоте 1000 футов, то земля под вами будет похожа на стеганое одеяло, когда же вы снизитесь до высоты в 10 футов (как раз за мгновение до того, как врежетесь в землю), детализация увеличится до такой степени, что вы в состоянии разглядеть колосья пшеницы. Или еще — вы можете применить bump mapping для имитации 20 футовых волн на воде, летя на высоте в 1000 футов, а когда вы снизитесь до 10 футовой высоты, можно дополнительно добавить эффекты дрожания и капель воды на стекле.

Реализация описанных технологий и методов

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

Также постарайтесь не забыть, для чего вообще разрабатывалась технология bump mapping изначально: высокая детализация без добавления дополнительных полигонов. Технология является особо желательной там, где разработчик планирует использование программного продукта на целом парке разнотипных по мощности машин или где просто не используется возможность выбора уровня детализации для моделей и объектов. (Например, строения на уровнях целого ряда 3D шутеров). Вы можете использовать оба метода (Embossingи Environment Map — Bump Mapping) для придания более реалистичного вида кирпичным стенам, изрезанным мраморным колоннам и заклепкам на старых железных лестницах.

Конечно, решаясь на применение технологии рельефного текстурирования, вы должны обязательно взвесить все плюсы (получение дополнительных визуальных эффектов без усложнения геометрии) и минусы (необходимость прописывать участки кода для различного аппаратного обеспечения и более высокие требования к акселератору по способности заполнения (fillrate) во время рендеринга), и только после этого принять решение. Последние графические акселераторы, выпущенные на массовый рынок, имеют достаточные мощности по части заполнения пикселями треугольников, поэтому один из минусов в ближайшем будущем уже не будет иметь такого значительного веса. Также стоит учитывать тот факт, что способность акселераторов накладывать несколько текстур за один проход дает значительный выигрыш в производительности, и программистам скоро не надо будет изыскивать многопроходные решения для наложения нескольких текстур, которые в основном рассчитаны на старый парк акселераторов, обладающих, к тому же, низкой скоростью заполнения.

Разработчики, которые пишут программы для PC, сталкиваются с необходимостью масштабировать свою программу под компьютеры с различной мощностью. К счастью, оба описанных в данной статье подхода к реализации эффектов рельефности на объектах помогают в масштабировании ваших приложений к различному аппаратному обеспечению без особых трудозатрат. Вам просто достаточно сделать так, чтобы bumpmapping отключалось при обнаружении более медленного процессора на компьютере и, наоборот, включалась на полную мощность в случае машины высокого класса. Так что, Bump Mapping сделает вам графику в вашем следующем проекте, ой, пардон, обещает сделать...



Автор статьи Kim Pallister, инженер по техническому маркетингу и публицист по процессорным технологиям Intel’s Developer Relations Group.