Hlavní navigace

Open Inventor: Terrain Generator (1)

Jan Pečiva

V počítačové grafice existuje několik základních metod pro generování virtuální krajiny. My si ukážeme dvě z nich.

V minulých dílech jsme se pohybovali ve vesmíru. Nyní se dostáváme zpět na zem a budeme zkoušet generovat krajinu. Krajinou zde budeme nazývat to, co se skrývá za anglickým slovem „landscape“, tedy zemský povrch, nikoliv lesy, louky, háje a pasoucí se ovce.

V následujících dvou dílech bychom si měli ukázat dvě nejzákladnější metody pro generování krajiny:

  • Mid-point displacement (zkráceně midpoint) – metoda přesouvání prostředního bodu
  • Fault formation algoritmus – metoda zlomů

V tomto dílu se budeme soustředit na základy z generování krajiny, takže nečekejte nějaké super výsledky. Ty se možná dostaví příště, kdy vše dotáhneme do konce. Dnes nás tedy čekají tyto dva příklady:

Tabulka č. 562
3.1 – Simple Terrain Nejjednodušší krajina vytvořená ze statické výškové mapy
3.2 – Midpoint Terrain Krajina generovaná midpoint algoritmem

Příklad 3.1: Simple Terrain

Zdrojáky si můžete stáhnout zde. Novinka je, že jsou společné pro Linux i pro Windows. Všechen problematicky přenosný kód byl přesunut do souborů Main-Linux.cpp a Main-Windows.cpp. Z těchto souborů se pak volá především funkce createScene(), která je už umístěna v našem SimpleTerrain.cpp spolu s množstvím dalšího kódu tohoto příkladu.

Abychom věděli, k čemu směřujeme, ukažme si screenshot:

Vidíme plát krajiny osvětlený jedním světlem. Jak vytvořit takovýto plát? K tomu se v počítačové grafice často používá výšková mapa – anglicky „height map“. Ve zdrojovém souboru SimpleTerrain.cpp můžeme tuto výškovou mapu najít jakožto pole floatů 16×16 udávající výšku krajiny v jejích jednotlivých bodech. V tomto příkladě je výšková mapa zadána staticky, ale v dalších příkladech si ji budeme generovat sami.

Máme-li výškovou mapu, je druhou otázkou, jak ji zobrazit. Na následujícím obrázku vidíme, že krajina je složená z čtverců, jejichž vrcholy mají různou výšku. A právě tuto výšku nám udává naše výšková mapa.

Nejvhodnější třídou pro rendrování čtverců je SoQuadMesh, ač ji použijeme jen tentokrát a v následujících příkladech už to bude jen tradiční So(Indexed)Tri­angleStripSet.

Pojďme tedy zabrouzdat do zdrojových kódů. Naším nejdůležitějším úkolem je výpočet souřadnic bodů (neboli vertexů) naší krajiny a jejich předání do nodu SoCoordinate3:

// Vytvoř objekt nesoucí souřadnice modelu krajiny
SoCoordinate3 *coords = new SoCoordinate3;

// Nejprve nastavíme počet souřadnic, které budeme generovat.
// (šablona SbSqr počítá druhou mocninu)
coords->point.setNum(SbSqr(TERRAIN_SIZE));
SbVec3f *c = coords->point.startEditing();

// Nyní generujeme souřadnice bodů do paměti
int ci = 0;
int x,y;
for (y=0; y<TERRAIN_SIZE; y++) {
  for (x=0; x<TERRAIN_SIZE; x++) {
    c[ci++] = SbVec3f(float(x), hmap[y][x]/15.f, float(y));
  }
}
coords->point.finishEditing();
root->addChild(coords); 

Nód SoQuadMesh pak provádí už samotné vykreslení. Stačí mu říci, kolik vertexů máme v řádcích a kolik v sloupcích:

// Vytvoříme objekt "čtvercové mřížky" a nastavíme počet
// řádků a sloupců.
SoQuadMesh *mesh = new SoQuadMesh;
mesh->verticesPerColumn.setValue(TERRAIN_SIZE);
mesh->verticesPerRow.setValue(TERRAIN_SIZE);
root->addChild(mesh); 

Za krátkou zmínku stojí i osvětlení scény. Terén je osvětlen směrovým světlem, a jak čteme z jeho směrového vektoru (0.35f, –1.0f, 0.25f), svítí podle y-ové osy směrem dolů, podle x-ové trochu doprava a podle z trochu ke kameře. Dále je vypnuto defaultní světlo u kamery a ambientní světlo u SoEnvironment je nastaveno na 0.5, aby se nám krajina na odvrácených stranách od světla příliš nezatmívala.

Tímto jsme dokončili jednoduchý příklad provádějící zobrazování výškové mapy a můžeme přejít ke generovacím algoritmům.

Příklad 3.2: Midpoint Terrain

V tomto příkladu si zkusíme vygenerovat výškovou mapu sami. Podíváme-li se do zdrojáků, které jsou ke stažení zde, uvidíme v souboru Midpoint1.cpp dvě funkce: generateMap() a createModel(). První z nich je samotný midpoint algoritmus, který vygeneruje výškovou mapu, druhá pak z této výškové mapy vytvoří odpovídající vertexy v SoCoordinate3 a naplní SoTriangleStripSet. Sama vrátí kořen grafu scény, který zkonstruovala.

My se v prvé řadě podíváme na generovací algoritmus. Ten pracuje iteračně a v každém kroku zvyšuje dvojnásobně úroveň detailu. A každý krok se skládá ze dvou podkroků. Podívejme se tedy na celou situaci na obrázcích.

Na začátku máme čtverec, který reprezentuje celou naši výškovou mapu. Do jeho rohů je přiřazena počáteční výška:

V prvním podkroku si vezme algoritmus prostřední bod čtverce, spočítá průměrnou výšku ze čtyř rohových bodů, připočte nebo odečte nějakou náhodnou hodnotu a výsledek uloží to tohoto prostředního bodu. Aktuální situaci demonstruje obrázek:

Ve druhém podkroku pootočíme naši hlavu o 45 stupňů a vidíme čtyři půlky čtverců, kterým však byla uříznuta jedna polovina. V dalších krocích už nebudeme mít tolik „půlek“, ale i tam musíme tento stav ošetřit. Takže vezmeme prostřední body těchto rádoby čtverců, spočítáme aritmetický průměr výšky okolních třech bodů, modifikujeme výšku nějakou náhodnou hodnotou a výsledek uložíme. Stav ukazuje následující obrázek:

Vidíme tedy, že jsme z jednoho čtverce výškové mapy vygenerovali čtyři další, jinak řečeno, zdvojnásobili jsme rozlišení výškové mapy dvakrát. Při další iteraci vše probíhá podobně pro všechny čtyři nově vygenerované čtverce. Nejprve generujeme prostřední body:

A ve druhém kroku opět pootočíme hlavu o 45 stupňů, ale tentokrát už mnoha čtvercům nechybí druhá polovina. Vygenerujeme tedy opět prostřední body:

Tímto rekurzivním způsobem pokračujeme, až je celá výšková mapa vyplněna hodnotami.

Máme-li výškovou mapu, potřebujeme z ní zkonstruovat zobrazitelný graf scény. To provádíme ve funkci createModel(). Nejprve naplníme nód SoCoordinate3 souřadnicemi bodů. Místo slova bod budeme používat odbornější výraz v počítačové grafice: vertex (a množné číslo: vertices). Pro každou dlaždici v krajině vygenerujeme vždy všechny čtyři rohové body – levý spodní, pravý spodní, levý horní a pravý horní, přičemž pořadí vrcholů má svůj význam. Následující SoTriangleStripSet pak vše zobrazí. Hodnota čtyři v jeho fieldu numVertices říká, že každá dlaždice terénu je triangle strip složený ze čtyřech vertexů, což odpovídá dvěma vyrendrovaným trojúhelníkům.

Nebudeme si uvádět kód, neboť je velmi jednoduchý. Místo toho si ukážeme výsledný screen shot. Jen upozorňuji – nelekněte se. Výsledek se může zdát jako velmi „bídný“.

A vcelku oprávněně se ptáme: Proč naše vylepšená krajina vypadá mnohem hůře než v minulém příkladu? Důvod je v normálách a v použití SoTriangleStripSet místo SoQuadMesh. Celý vizuální nedostatek spravíme a ještě dalece předčíme původní výsledek v dalším pokračování.

Teď ale k důvodu: Při osvětlování krajiny potřebuje znát OpenGL normály povrchu. My jsme nikde třídu SoNormal nepoužili, a proto nejsou normály specifikované. Ale aby celé rendrování neskončilo fiaskem, vychází nám Open Inventor vstříc a vygeneruje normály za nás. To je vcelku triviální věc pro SoQuadMesh, kde na mřížce známe sousední vrcholy pro každý bod, a tak je vizuální výsledek perfektní. SoTriangleStripSet ale reprezentuje obecnou síť trojúhelníků, proto se normály nepodařilo tak dobře vygenerovat a výsledek je „bídný“. Můžeme však být vděční i za to, neboť bez normál by to dopadlo ještě mnohem hůře.

Tímto dnes končíme tento příklad, abychom mohli příště navázat a podstatně vylepšit vizuální výsledek naší aplikace.

Závěr

Dnes jsme se zabořili do problematiky vytváření virtuální krajiny. Ukázali jsme si, jak vyrendrovat výškovou mapu a jak ji vygenerovat midpoint algoritmem. Příště jej budeme nejen vylepšovat, aby byl použitelný v nějaké hře, ale ukážeme si i druhý algoritmus – fault formation – a porovnáme charaktery obou vygenerovaných krajin.

Našli jste v článku chybu?