Cht∞li byste vytvo°it v∞rnou simulaci krajiny, ale nevφte, jak na to? Bude nßm staΦit obyΦejn² 2D obrßzek ve stupnφch Üedi, pomocφ kterΘho deformujeme rovinu do t°etφho rozm∞ru. Na prvnφ pohled t∞₧ko °eÜitelnΘ problΘmy b²vajφ Φastokrßt velice jednoduchΘ.
Nynφ byste u₧ m∞li b²t opravdov²mi experty na OpenGL, ale mo₧nß nevφte, co to je v²ÜkovΘ mapovßnφ (height mapping). P°edtavte si rovinu, vytlaΦenou podle n∞jakΘ formy do 3D prostoru. TΘto form∞ se °φkß v²Ükovß mapa, kterou m∙₧e b²t defakto jak²koli typ dat. Obrßzky, textovΘ soubory nebo t°eba datov² proud zvuku - zßle₧φ jen na vßs. UrΦφte si, co reprezentuje ·dolφ a vrcholy a vÜechny ostatnφ hodnoty interpolujete. Doufßm, ₧e mßte alespo≥ zßkladnφ p°edstavu, pokud ne, vÜe byste m∞li pochopit z k≤du.
Dfinujeme t°i opravdu d∙le₧itΘ symbolickΘ konstanty. MAP_SIZE p°edstavuje rozm∞r mapy. v naÜem p°φpad∞ p°φpad∞ se jednß o Üφ°ku/v²Üku obrßzku (1024x1024). Konstanta STEP_SIZE urΦuje velikost krok∙ p°i grabovßnφ hodnot z obrßzku. V souΦasnΘ chvφli bereme v ·vahu ka₧d² Üestnßct² pixel. ZmenÜenφm Φφsla p°idßvßme do v²slednΘho povrchu polygony, tak₧e vypadß mΘn∞ hranat∞ (hladΦeji), ale zßrove≥ zvyÜujeme nßroΦnost na rendering. HEIGHT_RATIO slou₧φ jako m∞°φtko v²Üky na ose y. MalΘ Φφslo zredukuje vysokΘ hory s ·dolφmi na plochou rovinu.
#define MAP_SIZE 1024// Velikost .RAW obrßzku v²ÜkovΘ mapy
#define STEP_SIZE 16// èφ°ka a v²Üka ka₧dΘho polygonu
#define HEIGHT_RATIO 1.5f// Zoom v²Üky terΘnu na ose y
Prom∞nnß bRender p°edstavuje p°φpφnaΦ mezi pevn²mi polygony a drßt∞n²m modelem. ScaleValue urΦuje zoom scΘny na vÜech t°ech osßch.
bool bRender = TRUE;// Polygony - true, drßt∞n² model - false
float scaleValue = 0.15f;// M∞°φtko terΘnu (vÜechny osy)
Deklarujeme jednorozm∞rnΘ pole pro ulo₧enφ vÜech dat v²ÜkovΘ mapy. Pou₧φvan² .RAW obrßzek neobsahuje RGB slo₧ky barvy, ale ka₧d² pixel je tvo°en jednφm bytem, kter² specifikuje jeho odstφn. NicmΘn∞ o barvu se starat nebudeme, jde nßm o hodnoty. ╚φslo 255 bude p°edstavovat nejvyÜÜφ mo₧n² bod povrchu a nula nejni₧Üφ.
BYTE g_HeightMap[MAP_SIZE * MAP_SIZE];// Uklßdß data v²ÜkovΘ mapy
Funkce LoadRawFile() nahrßvß RAW soubor s obrßzkem. Nic komplexnφho! V parametrech se jφ p°edßvß °et∞zec diskovΘ cesty, velikost dat obrßzku a ukazatel na pam∞¥, do kterΘ se uklßdß. Otev°eme soubor pro Φtenφ v binßrnφm m≤du a oÜet°φme situaci, kdy neexistuje.
void LoadRawFile(LPSTR strName, int nSize, BYTE* pHeightMap)// Nahraje .RAW soubor
{
FILE *pFile = NULL;// Handle souboru
pFile = fopen(strName, "rb");// Otev°enφ souboru pro Φtenφ v binßrnφm m≤du
if (pFile == NULL)// Otev°enφ v po°ßdku?
{
MessageBox(NULL, "Can't Find The Height Map!", "Error", MB_OK);
return;
}
Pomocφ fread() naΦteme po jednom bytu ze souboru pFile data o velikosti nSize a ulo₧φme je do pam∞ti na lokaci pHeightMap. Vyskytne-li se chyba, vypφÜeme varovnou zprßvu.
fread(pHeightMap, 1, nSize, pFile);// NaΦte soubor do pam∞ti
int result = ferror(pFile);// V²sledek naΦφtßnφ dat
if (result)// Nastala chyba?
{
MessageBox(NULL, "Failed To Get Data!", "Error", MB_OK);
}
Na konci zb²vß u₧ jenom zav°φt soubor.
fclose(pFile);// Zav°enφ souboru
}
K≤d pro inicializaci OpenGL byste m∞li bez problΘm∙ pochopit sami.
int InitGL(GLvoid)// Inicializace OpenGL
{
glShadeModel(GL_SMOOTH);// JemnΘ stφnovßnφ
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);// ╚ernΘ pozadφ
glClearDepth(1.0f);// Nastavenφ hloubkovΘho bufferu
glEnable(GL_DEPTH_TEST);// Zapne testovßnφ hloubky
glDepthFunc(GL_LEQUAL);// Typ testovßnφ hloubky
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Perspektivnφ korekce
P°ed vrßcenφm true jeÜt∞ do g_HeightMap nahrajeme .RAW obrßzek.
LoadRawFile("Data/Terrain.raw", MAP_SIZE * MAP_SIZE, g_HeightMap);// NaΦtenφ dat v²ÜkovΘ mapy
return TRUE;// VÜe v po°ßdku
}
Mßme zde jeden problΘm - ulo₧ili jsme dvourozm∞rn² obrßzek do jednorozm∞rnΘho pole. Co s tφm? Funkce Height() provede v²poΦet pro transformaci x, y sou°adnic na index do tohoto pole a vrßtφ hodnotu, kterß je na n∞m ulo₧enß. P°i prßci s poli bychom se v₧dy m∞li start o mo₧nost p°eteΦenφ pam∞ti. Jednoduch²m trikem zmenÜφme vysokΘ hodnoty tak, aby byly platnΘ. Pokud n∞kterß z hodnot p°esßhne dan² index, zbytek po d∞lenφ ji zmenÜφ do rozmezφ, kterΘ m∙₧eme bez obav pou₧φt. Dßle otestujeme, jestli se v poli opravdu nachßzejφ data.
int Height(BYTE *pHeightMap, int X, int Y)// P°epoΦφtß 2D sou°adnice na 1D a vrßtφ ulo₧enou hodnotu
{
int x = X % MAP_SIZE;// Proti p°eteΦenφ pam∞ti
int y = Y % MAP_SIZE;
if(!pHeightMap)// Obsahuje pam∞¥ data?
{
return 0;
}
Aby se jednorozm∞rnΘ pole chovalo jako dvojrozm∞rnΘ, musφme zapojit trochu matematiky. index do 1D pole na 2D sou°adnicφch zφskßme tak, ₧e vynßsobφme °ßdek (x) jeho Üφ°kou (MAP_SIZE) a p°iΦteme konkrΘtnφ pozici na °ßdku (x). Kdy₧ se Φlov∞k zamyslφ, opravdu nic slo₧itΘho.
return pHeightMap[(y * MAP_SIZE) + x];// Vrßtφ hodnotu z pole
}
Na tomto mφst∞ nastavujeme barvu vertexu podle aktußlnφ v²Üky nad height mapou. Zφskßme hodnotu na indexu pole a d∞lenφm 256.0f ji zmenÜφme do rozmezφ 0.0f a₧ 1.0f. Abychom ji jeÜt∞ trochu ztmavily odeΦteme -0.15f. V²sledek p°edßme funkci glColor3f() jako modrou slo₧ku barvy.
void SetVertexColor(BYTE *pHeightMap, int x, int y)// Zφskß barvu v zßvislosti na v²Üce
{
if(!pHeightMap)// Obsahuje pam∞¥ data?
{
return;
}
// Zφskßnφ hodnoty, p°epoΦet do rozmezφ 0.0f a₧ 1.0f, ztmavenφ
float fColor = (Height(pHeightMap, x, y) / 256.0f) - 0.15f;
glColor3f(0, 0, fColor);// Odstφny modrΘ barvy
}
Dostßvßme se k nejpodstatn∞jÜφ Φßsti celΘho tutorißlu - renderovßnφ terΘnu. Prom∞nnΘ X, Y slou₧φ k prochßzejφ v²ÜkovΘ mapy a x, y, z jsou 3D sou°adnicemi vertexu.
void RenderHeightMap(BYTE pHeightMap[])// Renderuje terΘn
{
int X = 0, Y = 0;// Pro prochßzenφ polem
int x, y, z;// Sou°adnice vertex∙
if(!pHeightMap)// Obsahuje pam∞¥ data?
{
return;
}
Podle logickΘ hodnoty bRender p°ipφnßme mezi vykreslovßnφm obdΘlnφk∙ a linek.
if(bRender)// Co chce u₧ivatel renderovat?
{
glBegin(GL_QUADS);// Polygony
}
else
{
glBegin(GL_LINES);// Drßt∞n² model
}
Zalo₧φme dva vno°enΘ cykly, kterΘ prochßzejφ jednotlivΘ pixely v²ÜkovΘ mapy. Vn∞jÜφ se starß o osu x a vnit°nφ o osu y, z Φeho₧ plyne, ₧e vykreslujeme po sloupcφch a ne po °ßdcφch. VÜimn∞te si, ₧e po ka₧dΘm pr∙chodu nezv∞tÜujeme °φdφcφ prom∞nnou o jeden pixel, ale hned o n∞kolik. Sice v²sledn² terΘn nebude tak hladk² a p°esn², ale dφky menÜφmu poΦtu polygon∙ se rendering urychlφ. Pokud by se se STEP_SIZE rovnalo jednΘ, ka₧dΘmu pixelu by se p°i°adil jeden polygon. Myslφm, ₧e Φφslo Üestnßct bude vyhovujφcφ, ale pokud zapnete sv∞tla, kterΘ zv²raz≥ujφ hranatost povrchu, m∞li byste ho snφ₧it.
P°ekl.: ┌pln∞ nejlepÜφ by bylo, kdyby se velikost kroku urΦovala p°ed vstupem do cykl∙ podle aktußlnφho FPS. Zavedli bychom tak zp∞tnovazebnφ regulaΦnφ smyΦku.
// P°ekl.:
// if(FPS < 30)// Ni₧Üφ hodnoty => trhßnφ pohyb∙ animace
// {
// if(STEP_SIZE > 1)// Dolnφ mez (1 pixel)
// {
// STEP_SIZE--;// Musφ b²t prom∞nnou a ne symbolickou konstantou
// }
// }
// else
// {
// if(STEP_SIZE < MAP_SIZE-1)// Hornφ mez (velikost v²ÜkovΘ mapy)
// {
// STEP_SIZE++;// Musφ b²t prom∞nnou a ne symbolickou konstantou
// }
// }
for (X = 0; X < MAP_SIZE; X += STEP_SIZE)// ╪ßdky v²ÜkovΘ mapy
{
for (Y = 0; Y < MAP_SIZE; Y += STEP_SIZE)// Sloupce v²ÜkovΘ mapy
{
P°epoklßdßm, ₧e to, jak urΦit pozici vertexu, jste u₧ dßvno vytuÜili. Hodnota na ose x odpovφdß x-ovΘ sou°adnici v²ÜkovΘ mapy a na ose z y-ovΘ. Zφskali jsme umφst∞nφ bodu na rovin∞, pot°ebujeme ho jeÜt∞ vyzdnihnout do v²Üky, kterΘ v OpenGL odpovφdß osa y. TAto v²Üka je definovßna hodnotou ulo₧enou na danΘm prvku pole (sv∞tlostφ obrßzku). Opravdu nic slo₧itΘho...
// Sou°adnice levΘho dolnφho vertexu
x = X;
y = Height(pHeightMap, X, Y );
z = Y;
Nastavφme barvu bodu podle v²Üky nad rovinou. ╚φm v²Üe se nachßzφ, tφm bude sv∞tlejÜφ. Potom pomocφ funkce glVertex3i() p°edßme OpenGL sou°adnice vertexu.
SetVertexColor(pHeightMap, x, z);// Barva vertexu
glVertex3i(x, y, z);// Definovßnφ vertexu
Druh² vertex urΦφme p°iΦtenφm STEP_SIZE k ose z. Na tomto mφst∞ se budeme nachßzet p°i p°φÜtφm pr∙chodu cyklem, tak₧e mezi jednotliv²mi polyguny se nebudou vyskytovat mezery. Analogicky zφskßme i dalÜφ dva body obdΘlnφku. Nyφ u₧ mi v∞°φte, kdy₧ jsem na zaΦßtku tutorißlu psal, ₧e slo₧it∞ vypadajφcφ v∞ci b²vajφ Φasto velice jednoduchΘ?
// Sou°adnice levΘho hornφho vertexu
x = X;
y = Height(pHeightMap, X, Y + STEP_SIZE );
z = Y + STEP_SIZE ;
SetVertexColor(pHeightMap, x, z);// Barva vertexu
glVertex3i(x, y, z);// Definovßnφ vertexu
// Sou°adnice pravΘho hornφho vertexu
x = X + STEP_SIZE;
y = Height(pHeightMap, X + STEP_SIZE, Y + STEP_SIZE );
z = Y + STEP_SIZE ;
SetVertexColor(pHeightMap, x, z);// Barva vertexu
glVertex3i(x, y, z);// Definovßnφ vertexu
// Sou°adnice pravΘho dolnφho vertexu
x = X + STEP_SIZE;
y = Height(pHeightMap, X + STEP_SIZE, Y );
z = Y;
SetVertexColor(pHeightMap, x, z);// Barva vertexu
glVertex3i(x, y, z);// Definovßnφ vertexu
}
}
glEnd();// Konec kreslenφ
Po vykreslenφ terΘnu reinicializujeme barvu na bφlou, abychom nem∞li starosti s barvou ostatnφch objekt∙ ve scΘn∞ (net²kß se tohoto dema).
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);// Reset barvy
}
Na zaΦßtku DrawGLScene() zaΦneme klasicky smazßnφm buffer∙ a resetem matice.
int DrawGLScene(GLvoid)// Vykreslenφ OpenGL scΘny
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Vyma₧e buffery
glLoadIdentity();// Reset matice
Pomocφ funkce gluLookAt() umφstφme a natoΦφme kameru tak, aby byl renderovan² terΘn v zßb∞ru. Prvnφ t°i parametry urΦujφ jejφ pozici vzhledem k poΦßtku sou°adnicovΘho systΘmu, dalÜφ t°i body reprezentujφ mφsto, kam mφ°φ a poslednφ t°i jsou vektorem vzh∙ru. V naÜem p°φpad∞ se nachßzφme nad sledovan²m terΘnem a dφvßme se na n∞j trochu dol∙ (55 je menÜφ ne₧ 60) spφÜe doleva (186 je menÜφ ne₧ 212). hodnota 171 p°edstavuje vzdßlenost od kamery na ose z. Proto₧e se hory zvedajφ od zdola nahoru, nastavφme u vektoru vzh∙ru jedniΦku na ose y. Ostatnφ dv∞ hodnoty z∙stanou na nule.
P°i prvnφm pou₧itφ m∙₧e b²t gluLookAt() trochu odstraÜujφcφ, asi jste zmateni. NejlepÜφ radou je pohrßt si se vÜemi hodnotami, abyste vid∞li, jak se pohled na scΘnu postupn∞ m∞nφ. Pokud byste nap°φklad p°epsal pozici z 60 na 120, vid∞li byste terΘn spφÜe zeshora ne₧ z boku, proto₧e se stßle dφvßte na sou°adnice 55.
Praktick² p°φklad: ╪ekn∞me, ₧e jste vysok² kolem 1,8 m, ale oΦi, kterΘ reprezentujφ kameru, jsou trochu nφ₧e - 1,7 m. Stojφte p°ed st∞nou, kterß je vysokß pouze 1 m, tak₧e bez problΘm∙ vidφte jejφ hornφ stranu. Pokud ale zednφci dostavφ st∞nu na t°i metry, budete se muset dφvat VZH┘RU, ale jejφ vrch u₧ NEUVID═TE. V²hled se zm∞nil podle toho, jestli se dφvßte dol∙ nebo vzh∙ru (respektive jestli jste nad nebo pod objektem).
// Umφst∞nφ a natoΦenφ kamery
gluLookAt(212,60,194, 186,55,171, 0,1,0);// Pozice, sm∞r, vektor vzh∙ru
Aby byl v²sledn² terΘn pon∞kud menÜφ, zm∞nφme m∞°φtko sou°adnicov²ch os. Proto₧e navφc nßsobφme y-ovou hodnotu, budou se hory jevit vyÜÜφ. Mohli bychom takΘ pou₧φt translace a rotace, ale to u₧ nechßm na vßs.
glScalef(scaleValue, scaleValue * HEIGHT_RATIO, scaleValue);// Zoom terΘnu
Pomocφ d°φve napsanΘ funkce vyrenderujeme terΘn.
RenderHeightMap(g_HeightMap);// Renderovßnφ terΘnu
return TRUE;// VÜe v po°ßdku
}
Kliknutφm levΘho tlaΦφtka myÜi m∙₧e u₧ivatel p°epnout mezi renderovßnφm polygon∙ a linek (drßt∞n² model).
// Funkce WndProc()
case WM_LBUTTONDOWN:// LevΘ tlaΦφtko myÜi
{
bRender = !bRender;// P°epne mezi polygony a drßt∞n²m modelem
return 0;// Konec funkce
}
èipkami nahoru a dol∙ zv∞tÜujeme/zmenÜujeme m∞°φtko scΘny a tφm i velikost terΘnu.
// Funkce WinMain()
if (keys[VK_UP])// èipka nahoru
{
scaleValue += 0.001f;// Vyv²Üφ hory
}
if (keys[VK_DOWN])// èipka dol∙
{
scaleValue -= 0.001f;// Snφ₧φ hory
}
Tak to je vÜechno, v²Ükov²m mapovßnφm textur jsme naprogramovali nßdherou kraji, kterß je ale zabarvenß do modra. zkuste nakreslit texturu (leteck² pohled), kterß reprezentuje zasn∞₧enΘ vrcholy hor, louky, jezera a podobn∞ a namapujte ji na terΘn. Texturovacφ koordinßty zφskßte vyd∞lenφm pozice na rovin∞ rozm∞rem obrßzku (zmenÜenφ hodnot do rozsahu 0.0f a₧ 1.0f). Plazmov²mi efekty a rolovßnφm se m∙₧e krajina dynamicky m∞nit. DΘÜ¥ a snφh zajistφ ΦßsticovΘ systΘmy, kterΘ u₧ takΘ znßte. Vlo₧φte-li krajinu do skyboxu, nikdo nepoznß, ₧e se jednß o poΦφtaΦov² model a ne o video animaci.
Nebo m∙₧ete vytvo°it mo°skou hladinu s vlnami, na kter²ch se pohupuje uplavan² mφΦ (v²Üku nad mo°sk²m dnem p°ece znßte - hodnota na indexu v poli). Nechte u₧ivatele, a¥ ho m∙₧e ovlßdat. Mo₧nosti jsou bez hranic...
napsal: Ben Humphrey - DigiBen
p°elo₧il: Michal Turek - Woq