Lekce 41 - Volumetrická mlha a nahrávání obrázků pomocí IPicture

V tomto tutoriálu se naučíte, jak pomocí rozšíření EXT_fog_coord vytvořit volumetrickou mlhu. Také zjistíte, jak pracuje IPicture kód a jak ho můžete využít pro nahrávání obrázků ve svých vlastních projektech. Demo sice není až tak komplexní jako některá jiná, nicméně i přesto vypadá hodně efektně.

Pokud demo nebude na vašem systému fungovat, nejdříve se ujistěte, že máte nainstalované nejnovější ovladače grafické karty. Pokud to nepomohlo, zauvažujte o koupi nové (Překl.: :-] ). V současné době už ne zrovna nejnovější GeForce 2 pracuje dobře a ani nestojí tak moc. Pokud vaše grafická karta nepodporuje rozšíření mlhy, kdo může vědět, jaká další rozšíření nebude podporovat?

Pro ty z vás, kterým toto demo nejede a cítí se vyloučeni... mějte na paměti následující: Snad každý den dostávám nejméně jeden email s dotazem na nový tutoriál. Nejhorší z toho je, že většina z nich už je online. Lidé se neobtěžují číst to, co už je napsáno a přeskakují na témata, která je více zajímají. Některé tutoriály jsou příliš komplexní, a proto z mé strany vyžadují někdy i týdny programování. Pak jsou tady tutoriály, které bych sice mohl napsat, ale většinou se jim vyhýbám, protože nefungují na všech kartách. Nyní jsou už karty jako GeForce levné natolik, aby si je mohl dovolit téměř každý, takže už nebudu dále ospravedlňovat nepsání takovýchto tutoriálů. Popravdě, pokud vaše karta podporuje pouze základní rozšíření, budete s největší pravděpodobností chybět! Pokud se vrátím k přeskakování témat jako jsou např. rozšíření, tutoriály se brzy oproti ostatním začnou výrazně opožďovat.

Kód začíná velmi podobně jako starý základní kód a povětšinou je identický s novým NeHeGL kódem. Jediný rozdíl spočívá v inkludování OLECTL hlavičkového souboru, který, chcete-li používat IPicture pro loading obrázků, musí být přítomen.

Překl.: IPicture je podle mě sice hezký nápad a pracuje perfektně, nicméně je kompletně vystavěn na ABSOLUTNĚ NEPŘENOSITELNÝCH technologiích MS, které jdou tradičně používat výhradně pod nejmenovaným OS, všichni víme, o který jde.

#include <windows.h>// Windows

#include <gl\gl.h>// OpenGL

#include <gl\glu.h>// GLU

#include <olectl.h>// Knihovna OLE Controls Library (použita při nahrávání obrázků)

#include <math.h>// Matematika

#include "NeHeGL.h"// NeHeGL

#pragma comment(lib, "opengl32.lib")// Přilinkování OpenGL a GLU

#pragma comment(lib, "glu32.lib")

#ifndef CDS_FULLSCREEN// Některé kompilátory CDS_FULLSCREEN nedefinují

#define CDS_FULLSCREEN 4

#endif

GL_Window* g_window;// Struktura okna

Keys* g_keys;// Klávesnice

Deklarujeme čtyř prvkové pole fogColor, které bude ukládat barvu mlhy, v našem případě se jedná o tmavě oranžovou (trocha červené smíchaná se špetkou zelené). Desetinná hodnota camz bude sloužit pro umístění kamery na ose z. Před vykreslením vždy provedeme translaci.

GLfloat fogColor[4] = {0.6f, 0.3f, 0.0f, 1.0f};// Barva mlhy

GLfloat camz;// Pozice kamery na ose z

Ze souboru glext.h převezmeme symbolické konstanty GL_FOG_COORDINATE_SOURCE_EXT a GL_FOG_COORDINATE_EXT. Pokud chcete kód zkompilovat, musí být nastaveny.

// Převzato z glext.h

#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450// Symbolické konstanty potřebné pro rozšíření FogCoordfEXT

#define GL_FOG_COORDINATE_EXT 0x8451

Abychom mohli používat funkci glFogCoordfExt(), která bude vstupním bodem pro rozšíření, potřebujeme deklarovat její prototyp. Nejdříve pomocí typedef vytvoříme nový datový typ, ve kterém bude specifikován počet a typ parametrů (jedno desetinné číslo). Vytvoříme globální proměnnou tohoto typu - ukazatel na funkci a prozatím ho nastavíme na NULL. Jakmile mu přiřadíme pomocí wglGetProcAddress() adresu OpenGL ovladače rozšíření, budeme moci zavolat glFogCoordfEXT(), jako kdyby to byla normální funkce.

Takže co už máme... Víme, že PFNGLFOGCOORDFEXTPROC přebírá jednu desetinnou hodnotu (GLfloat coord). Protože je proměnná glFogCoordfEXT stejného typu můžeme říct, že také potřebuje jednu desetinnou hodnotu... tedy glFogCoordfEXT(GLfloat coord). Funkci máme definovanou, ale zatím nic nedělá, protože glFogCoordfEXT se v tuto chvíli rovná NULL. Dále v kódu jí přiřadíme adresu OpenGL ovladače pro rozšíření.

Doufám, že to všechno dává smysl. Pokud jednou víte, jak tento kód pracuje, je velmi jednoduchý, ale jeho popsání je, alespoň pro mě, extrémně složité.

typedef void (APIENTRY * PFNGLFOGCOORDFEXTPROC) (GLfloat coord);// Funkční prototyp

PFNGLFOGCOORDFEXTPROC glFogCoordfEXT = NULL;// Ukazatel na funkci glFogCoordfEXT()

GLuint texture[1];// Jedna textura

Pojďme se podívat na převod obrázků do textury pomocí magické IPicture. Funkci se předává řetězec se jménem obrázku a ID textury. Za jméno se může dosadit buď disková cesta nebo webové URL.

Pro pomocnou bitmapu budeme potřebovat kontext zařízení (hdcTemp) a místo, kam by se dala uložit (hbmpTemp). Ukazatel pPicture představuje rozhraní k IPicture. WszPath a szPath slouží k uložení absolutní cesty k souboru nebo URL. Dále potřebujeme dvě proměnné pro šířku a dvě proměnné pro výšku. LWidth a LHeight ukládají aktuální rozměry obrázku, lWidthpixels a lHeightpixels obsahují šířku a výšku v pixelech upravenou podle maximální velikosti textury, která může být uložena do grafické karty. Hodnotu maximální velikosti uložíme do glMaxTexdim.

int BuildTexture(char *szPathName, GLuint &texid)// Nahraje obrázek a konvertuje ho na texturu

{

HDC hdcTemp;// Pomocný kontext zařízení

HBITMAP hbmpTemp;// Pomocná bitmapa

IPicture *pPicture;// Rozhraní pro IPicture

OLECHAR wszPath[MAX_PATH+1];// Absolutní cesta k obrázku (unicode)

char szPath[MAX_PATH+1];// Absolutní cesta k obrázku (ascii)

long lWidth;// Šířka v logických jednotkách

long lHeight;// Výška v logických jednotkách

long lWidthPixels;// Šířka v pixelech

long lHeightPixels;// Výška v pixelech

GLint glMaxTexDim;// Maximální rozměr textury

V další části kódu zjistíme, zda je jméno obrázku diskovou cestou nebo URL. Jedná-li se o URL, zkopírujeme jméno do proměnné szPath. V opačném případě získáme pracovní adresář a spojíme ho se jménem. Děláme to, protože potřebujeme plnou cestu k souboru. Pokud máme např. demo uložené v adresáři C:\WOW\LESSON41 a pokoušíme se nahrát obrázek DATA\WALL.BMP. Uvedená konstrukce přidá doprostřed ještě zpětné lomítko a tak vznikne C:\WOW\LESSON41\DATA\WALL.BMP.

if (strstr(szPathName, "http://"))// Obsahuje cesta řetězec "http://"?

{

strcpy(szPath, szPathName);// Zkopírování do szPath

}

else// Nahrávání ze souboru

{

GetCurrentDirectory(MAX_PATH, szPath);// Pracovní adresář

strcat(szPath, "\\");// Přidá zpětné lomítko

strcat(szPath, szPathName);// Přidá cestu k souboru

}

Aby funkce OleLoadPicturePath() rozuměla cestě k souboru, musíme ji převést z ASCII do kódování UNICODE (dvoubytové znaky). Pomůže nám s tím MultiByteToWideChar(). První parametr, CP_ACP, znamená Ansi Codepage, druhý specifikuje zacházení s nenamapovanými znaky (ignorujeme ho). SzPath je samozřejmě převáděný řetězec a čtvrtý parametr představuje šířku řetězce s Unicode znaky. Pokud za něj předáme -1, předpokládá se, že bude ukončen pomocí NULL. Do wszPath se uloží výsledek, MAX_PATH je maximální velikostí cesty k souboru (256 znaků).

Po konverzi cesty do kódování Unicode se pokusíme pomocí OleLoadPicturePath nahrát obrázek. Při úspěchu bude pPicture obsahovat ukazatel na data obrázku, návratový kód se uloží do hr.

MultiByteToWideChar(CP_ACP, 0, szPath, -1, wszPath, MAX_PATH);// Konverze ascii kódování na Unicode

HRESULT hr = OleLoadPicturePath(wszPath, 0, 0, 0, IID_IPicture, (void**)&pPicture);// Loading obrázku

if(FAILED(hr))// Neúspěch

{

return FALSE;// Konec

}

Pokusíme se vytvořit kompatibilní kontext zařízení. Pokud se to nepovede uvolníme data obrázku a ukončíme program.

hdcTemp = CreateCompatibleDC(GetDC(0));// Pomocný kontext zařízení

if(!hdcTemp)// Neúspěch

{

pPicture->Release();// Uvolní IPicture

return FALSE;// Konec

}

Přišel čas na položení dotazu grafické kartě, jakou podporuje maximální velikost textury. Tato část kódu je důležitá, protože díky ní bude obrázek vypadat dobře na všech grafických kartách. Nejen, že umožní upravit velikost na mocninou dvou, ale také ho přizpůsobí podle velikosti paměti grafické karty. Zkrátka: budeme moci nahrávat obrázky s libovolnou šířkou a výškou. Jediná nevýhoda pro majitele málo výkonných grafických karet spočívá v tom, že se při zobrazení obrázků s vysokým rozlišením ztratí spousta detailů.

Funkce glGetIntegerv() vrátí maximální rozměry textur (256, 512, 1024, atd.), potom zjistíme aktuální velikost našeho obrázku a převedeme ji na pixely. Matematiku zde nebudu vysvětlovat.

glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glMaxTexDim);// Maximální podporovaná velikost textury

pPicture->get_Width(&lWidth);// Šířka obrázku a konvertování na pixely

lWidthPixels = MulDiv(lWidth, GetDeviceCaps(hdcTemp, LOGPIXELSX), 2540);

pPicture->get_Height(&lHeight);// Výška obrázku a konvertování na pixely

lHeightPixels = MulDiv(lHeight, GetDeviceCaps(hdcTemp, LOGPIXELSY), 2540);

Pokud je velikost obrázku menší než maximální podporovaná, změníme velikost na mocninu dvou, která ale bude založená na aktuální velikosti. Přičteme 0.5f, takže se bude vždy zvětšovat na následující velikost. Například rovná-li se šířka 400 pixelům a karta podporuje maximálně 512, bude lepší zvolit 512 než 256, protože by se zbytečně zahodily detaily. Naopak při větší velikosti než maximální musíme zmenšovat na podporovanou velikost. Totéž platí i pro výšku.

Překl.: Opravte mě, jestli se mýlím. Co se stane když např. vezmu obrázek, který má šířku 80 a výšku 300 pixelů? Té matematice sice moc nerozumím :-), ale z toho, co je zde uvedeno, logicky vychází, že vznikne obdélníkový (ne čtvercový!) obrázek o rozměrech 128x512 pixelů. Možná by bylo vhodné ještě přidat něco ve stylu: pokud je jeden rozměr menší než druhý, uprav hodnoty na čtverec.

if (lWidthPixels <= glMaxTexDim)// Je šířka menší nebo stejná než maximálně podporovaná

{

// Změna velikosti na nejbližší mocninu dvou

lWidthPixels = 1 << (int)floor((log((double)lWidthPixels)/log(2.0f)) + 0.5f);

}

else// Bude se zmenšovat na maximální velikost

{

lWidthPixels = glMaxTexDim;

}

if (lHeightPixels <= glMaxTexDim)// Je výška menší nebo stejná než maximálně podporovaná

{

// Změna velikosti na nejbližší mocninu dvou

lHeightPixels = 1 << (int)floor((log((double)lHeightPixels)/log(2.0f)) + 0.5f);

}

else// Bude se zmenšovat na maximální velikost

{

lHeightPixels = glMaxTexDim;

}

V tuto chvíli máme data nahraná a také známe požadovanou velikost obrázku, abychom ho mohli dále upravovat, musíme vytvořit pomocnou bitmapu. Bi bude obsahovat informace o hlavičce a pBits bude ukazovat na data obrázku. Požadujeme barevnou hloubku 32 bitů na pixel, správnou šířku i výšku v kódování RGB s jednou bitplane.

// Pomocná bitmapa

BITMAPINFO bi = {0};// Typ bitmapy

DWORD *pBits = 0;// Ukazatel na data bitmapy

bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);// Velikost struktury

bi.bmiHeader.biBitCount = 32;// 32 bitů

bi.bmiHeader.biWidth = lWidthPixels;// Šířka

bi.bmiHeader.biHeight = lHeightPixels;// Výška

bi.bmiHeader.biCompression = BI_RGB;// RGB formát

bi.bmiHeader.biPlanes = 1;// 1 Bitplane

Převzato z MSDN: Funkce CreateDIBSection() vytváří DIB, do kterého může aplikace přímo zapisovat. Vrací ukazatel na umístění bitů bitmapy, můžeme také nechat systém alokovat paměť.

HdcTemp ukládá pomocný kontext zařízení, bi je hlavička bitmapy. DIB_RGB_COLORS říká programu, že chceme uložit RGB data, která nebudou indexována do logické palety (každý pixel bude mít červenou, zelenou a modrou složku). Ukazatel pBits bude obsahovat adresu výsledných dat a poslední dva parametry budeme ignorovat. Pokud nenastane žádná chyba, pomocí Selectobject() připojíme bitmapu k pomocnému kontextu zařízení.

// Touto cestou je možné specifikovat barevnou hloubku a získat přístup k datům

hbmpTemp = CreateDIBSection(hdcTemp, &bi, DIB_RGB_COLORS, (void**)&pBits, 0, 0);

if(!hbmpTemp)// Neúspěch

{

DeleteDC(hdcTemp);// Uvolnění kontextu zařízení

pPicture->Release();// Uvolní IPicture

return FALSE;// Konec

}

SelectObject(hdcTemp, hbmpTemp);// Zvolí bitmapu do kontextu zařízení

Nastal čas pro vyplnění pomocné bitmapy daty obrázku. Funkce pPicture->Render() to udělá za nás a navíc upraví obrázek na libovolnou velikost, kterou potřebujeme. HdcTemp představuje pomocný kontext zařízení a další dva následující parametry specifikují vertikální a horizontální offset (počet prázdných pixelů zleva a seshora). My chceme, aby byla celá bitmapa kompletně vyplněna, takže zadáme dvě nuly. Další dva parametry určují požadovanou velikost výsledného obrázku (na kolik pixelů se má roztáhnout popř. zmenšit). Nula na dalším místě je horizontální offset ve zdrojových datech, od kterého chceme začít číst, z čehož plyne, že půjdeme zleva doprava. LHeight určuje vertikální offset, data chceme číst od zdola nahoru. Zadáním lHeight se přesuneme na samé dno zdrojového obrázku. LWidth je množstvím pixelů, které se budou kopírovat ze zdrojového obrázku, v našem případě se jedná o všechna horizontální data. Předposlední parametr, trochu odlišný, má zápornou hodnotu, záporné lHeight, abychom byli přesní. Ve výsledku to znamená, že chceme zkopírovat všechna vertikální data, ale od zdola nahoru. Touto cestou bude při kopírování do cílové bitmapy převrácen. Poslední parametr nepoužijeme.

// Vykreslení IPicture do bitmapy

pPicture->Render(hdcTemp, 0, 0, lWidthPixels, lHeightPixels, 0, lHeight, lWidth, -lHeight, 0);

Nyní máme k dispozici novou bitmapu se správnými rozměry, ale bohužel je uložena ve formátu BGR. (Překl.: Proč tomu tak je, bylo vysvětlováno v 35. tutoriálu na přehrávání AVI videa.) Pomocí jednoduchého cyklu tyto dvě složky prohodíme a zároveň nastavíme alfu na 255. Dá se říci, že jakákoli jiná hodnota stejně nebude mít nejmenší efekt, protože alfu ignorujeme.

// Konverze BGR na RGB

for(long i = 0; i < lWidthPixels * lHeightPixels; i++)// Cyklus přes všechny pixely

{

BYTE* pPixel = (BYTE*)(&pBits[i]);// Aktuální pixel

BYTE temp = pPixel[0];// Modrá složka do pomocné proměnné

pPixel[0] = pPixel[2];// Uložení červené složky na správnou pozici

pPixel[2] = temp;// Vložení modré složky na správnou pozici

pPixel[3] = 255;// Konstantní alfa hodnota

}

Po všech nutných operacích můžeme z obrázku vygenerovat texturu. Zvolíme ji jako aktivní a nastavíme lineární filtrování. Myslím, že glTexImage2D() už nemusím vysvětlovat.

glGenTextures(1, &texid);// Generování jedné textury

glBindTexture(GL_TEXTURE_2D, texid);// Zvolí texturu

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR);// Lineární filtrování

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR);

// Vytvoření textury

glTexImage2D(GL_TEXTURE_2D, 0, 3, lWidthPixels, lHeightPixels, 0, GL_RGBA, GL_UNSIGNED_BYTE, pBits);

Poté, co je textura vytvořena, můžeme uvolnit zabrané systémové zdroje. Už nebudeme potřebovat pomocnou ani bitmapu ani kontext zařízení ani pPicture.

DeleteObject(hbmpTemp);// Smaže bitmapu

DeleteDC(hdcTemp);// Smaže kontext zařízení

pPicture->Release();// Uvolní IPicture

return TRUE;// OK

}

Následující funkce zjišťuje, jestli grafická karta podporuje rozšíření EXT_fog_coord. Tento kód může být použit pouze, pokud už má program k dispozici renderovací kontext. Jestliže ho zkusíme zavolat před inicializací okna, dostaneme chyby.

Vytvoříme pole obsahující jméno našeho rozšíření. Alokujeme dynamickou paměť, do které následně zkopírujeme seznam všech podporovaných rozšíření. Pokud strstr() mezi nimi najde EXT_fog_coord, vrátíme false. (Překl.: Uvolnit dynamickou paměť!!!)

int Extension_Init()// Je rozšíření EXT_fog_coord podporováno?

{

char Extension_Name[] = "EXT_fog_coord";

// Alokace paměti pro řetězec

char* glextstring = (char *)malloc(strlen((char *)glGetString(GL_EXTENSIONS)) + 1);

strcpy (glextstring,(char *)glGetString(GL_EXTENSIONS));// Grabování seznamu podporovaných rozšíření

if (!strstr(glextstring, Extension_Name))// Není podporováno?

{

// free(glextstring);// Překl.: Uvolnění alokované paměti !!!

return FALSE;

}

free(glextstring);// Uvolnění alokované paměti

Na samém začátku programu jsme deklarovali proměnnou glFogCoordfEXT jako ukazatel na funkci. Protože už s jistotou víme, že grafická karta toto rozšíření podporuje, můžeme ho pomocí wglGetProcAddress() nastavit na správnou adresu. Od této chvíle máme k dispozici novou funkci glFogCoordfEXT(), které se předává jedna GLfloat hodnota.

glFogCoordfEXT = (PFNGLFOGCOORDFEXTPROC) wglGetProcAddress("glFogCoordfEXT");// Nastaví ukazatel na funkci

return TRUE;// OK

}

Při vstupu do Initialize() má program k dispozici renderovací kontext, takže se můžeme dotázat na podporu rozšíření. Pokud není dostupné, ukončíme program. Texturu nahráváme pomocí nového IPicture kódu. Pokud se z nějakého důvodu loading nezdaří, opět ukončíme program. Následuje obvyklá inicializace OpenGL.

BOOL Initialize(GL_Window* window, Keys* keys)// Inicializace

{

g_window = window;// Okno

g_keys = keys;// Klávesnice

if (!Extension_Init())// Je rozšíření podporováno?

{

return FALSE;// Konec

}

if (!BuildTexture("data/wall.bmp", texture[0]))// Nahrání textury

{

return FALSE;// Konec

}

glEnable(GL_TEXTURE_2D);// Zapne mapování textur

glClearColor(0.0f, 0.0f, 0.0f, 0.5f);// Černé pozadí

glClearDepth(1.0f);// Nastavení hloubkového bufferu

glDepthFunc(GL_LEQUAL);// Typ testování hloubky

glEnable(GL_DEPTH_TEST);// Zapne testování hloubky

glShadeModel(GL_SMOOTH);// Jemné stínování

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Nejlepší perspektivní korekce

Dále potřebujeme nastavit mlhu. Nejdříve ji zapneme, potom určíme lineární renderovací mód (vypadá lépe) a definujeme barvu na tmavší odstín oranžové. Startovní pozice mlhy je místo, kde bude nejméně hustá. Abychom udrželi věci jednoduché předáme číslo 0.0f. Naopak nejvíce hustá bude s hodnotou 1.0f. Podle všech dokumentací, které jsem kdy četl, nastavení hintu na GL_NICEST způsobí, že se bude působí mlhy určovat zvlášť pro každý pixel. Předáte-li GL_FASTEST, bude se počítat pro jednotlivé vertexy, nicméně nejde vidět žádný rozdíl. Poslední glFogi() příkaz oznámí OpenGL, že chceme nastavovat mlhu v závislosti na koordinátech vertexů. To způsobí, že ji budeme moci umístit kamkoli na scénu bez toho, že bychom tak ovlivnili její zbytek.

// Nastavení mlhy

glEnable(GL_FOG);// Zapne mlhu

glFogi(GL_FOG_MODE, GL_LINEAR);// Lineární přechody

glFogfv(GL_FOG_COLOR, fogColor);// Barva

glFogf(GL_FOG_START, 0.0f);// Počátek

glFogf(GL_FOG_END, 1.0f);// Konec

glHint(GL_FOG_HINT, GL_NICEST);// Výpočty na jednotlivých pixelech

glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FOG_COORDINATE_EXT);// Mlha v závislosti na souřadnicích vertexů

Počáteční hodnotu proměnné camz určíme na -19.0f. Protože chodbu renderujeme od -19.0f do +14.0f, bude to přesně na začátku.

camz = -19.0f;// Pozice kamery

return TRUE;// OK

}

Funkce zajišťující stisky kláves je dnes opravdu jednoduchá. Pomocí šipek nahoru a dolů nastavujeme pozici kamery ve scéně. Zároveň musíme ošetřit "přetečení", abychom se neocitli venku z chodby.

void Update(DWORD milliseconds)// Aktualizace scény

{

if (g_keys->keyDown[VK_ESCAPE])// ESC

{

TerminateApplication(g_window);// Konec programu

}

if (g_keys->keyDown[VK_F1])// F1

{

ToggleFullscreen(g_window);// Změna fullscreen/okno

}

if (g_keys->keyDown[VK_UP] && camz < 14.0f)// Šipka nahoru

{

camz+=(float)(milliseconds) / 100.0f;// Pohyb dopředu

}

if (g_keys->keyDown[VK_DOWN] && camz > -19.0f)// Šipka dolů

{

camz-=(float)(milliseconds) / 100.0f;// Pohyb dozadu

}

}

Jsem si jistý, že už netrpělivě čekáte na vykreslování. Smažeme buffery, resetujeme matici a v závislosti na hodnotě camz se přesuneme do hloubky.

void Draw(void)// Vykreslování

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Smaže obrazovku a hloubkový buffer

glLoadIdentity();// Reset matice

glTranslatef(0.0f, 0.0f, camz);// Translace v hloubce

Kamera je umístěna, takže zkusíme vykreslit první quad. Bude jím zadní stěna, která by měla být kompletně ponořená v mlze. Z inicializace si jistě pamatujete, že nejhustší mlhu nastavuje hodnota GL_FOG_END; určili jsme ji na 1.0f. Mlha se aplikuje podobně jako texturové koordináty, pro nejmenší viditelnost předáme funkci glFogCoordfEXT() číslo 1.0f a pro největší 0.0f. Zadní stěna je kompletně ponořená v mlze, takže předáme všem jejím vertexům jedničku.

glBegin(GL_QUADS);// Zadní stěna

glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 0.0f);glVertex3f(-2.5f,-2.5f,-15.0f);

glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 0.0f);glVertex3f( 2.5f,-2.5f,-15.0f);

glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 1.0f);glVertex3f( 2.5f, 2.5f,-15.0f);

glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 1.0f);glVertex3f(-2.5f, 2.5f,-15.0f);

glEnd();

První dva body podlahy navazují na vertexy zadní stěny, a proto také zde uvedeme 1.0f. Přední body jsou už naopak z mlhy venku, tudíž je musíme nastavit na 0.0f. Místa ležící mezi okraji se automaticky interpolují, a tak vznikne plynulý přechod. Všechny ostatní stěny budou analogické.

glBegin(GL_QUADS);// Podlaha

glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 0.0f);glVertex3f(-2.5f,-2.5f,-15.0f);

glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 0.0f);glVertex3f( 2.5f,-2.5f,-15.0f);

glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 1.0f);glVertex3f( 2.5f,-2.5f, 15.0f);

glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 1.0f);glVertex3f(-2.5f,-2.5f, 15.0f);

glEnd();

glBegin(GL_QUADS);// Strop

glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 0.0f);glVertex3f(-2.5f, 2.5f,-15.0f);

glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 0.0f);glVertex3f( 2.5f, 2.5f,-15.0f);

glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 1.0f);glVertex3f( 2.5f, 2.5f, 15.0f);

glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 1.0f);glVertex3f(-2.5f, 2.5f, 15.0f);

glEnd();

glBegin(GL_QUADS);// Pravá stěna

glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 0.0f);glVertex3f( 2.5f,-2.5f, 15.0f);

glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 1.0f);glVertex3f( 2.5f, 2.5f, 15.0f);

glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 1.0f);glVertex3f( 2.5f, 2.5f,-15.0f);

glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 0.0f);glVertex3f( 2.5f,-2.5f,-15.0f);

glEnd();

glBegin(GL_QUADS);// Levá stěna

glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 0.0f);glVertex3f(-2.5f,-2.5f, 15.0f);

glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 1.0f);glVertex3f(-2.5f, 2.5f, 15.0f);

glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 1.0f);glVertex3f(-2.5f, 2.5f,-15.0f);

glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 0.0f);glVertex3f(-2.5f,-2.5f,-15.0f);

glEnd();

glFlush();// Vyprázdnění renderovací pipeline

}

Doufám, že nyní už rozumíte, jak věci pracují. Čím vzdálenější je objekt, tím by měl být více ponořen v mlze a tudíž musí být nastavena hodnota 1.0f. Vždycky si také můžete pohrát s GL_FOG_START a GL_FOG_END a pozorovat, jak ovlivňují scénu. Efekt nebude pracovat podle očekávání, pokud prohodíte hodnoty. Iluze se vytvořila tím, že je zadní stěna kompletně oranžová. nejvýhodnější použití spočívá u temných koutů, kde se hráč nemůže dostat za mlhu.

Plánujete-li tento typ mlhy ve svém 3D enginu, bude možná vhodné upravovat počáteční a koncové hodnoty podle toho, kde hráč stojí, kterým směrem se dívá a podobně.

Doufám, že jste si užili tento tutoriál. Vytvářel jsem ho přes tři dny, čtyři hodiny denně. Většinu času zabralo psaní textů, které právě čtete. Původně jsme chtěl vytvořit kompletní 3D místnost s mlhou v jednom rohu, ale naneštěstí jsem měl velmi málo času na kódování. Přestože zamlžená chodba je velmi jednoduchá, vypadá perfektně a modifikace kódu pro váš projekt by také neměla být moc složitá.

Je důležité poznamenat, že toto je pouze jednou z nejrůznějších možností, jak vytvořit volumetrickou mlhu. Podobný efekt může být naprogramován pomocí blendingu, částicových systémů, maskování a podobných technologií. Pokud modifikujete pohled na scénu tak, aby byla kamera umístěna ne v chodbě, ale venku, zjistíte, že se mlha nachází uvnitř chodby.

Originální myšlenka tohoto tutoriálu ke mně dorazila už hodně dávno, což je jedním z důvodů, že jsem ztratil email. Osobě, která mi nápad zaslala, děkuji.

napsal: Jeff Molofee - NeHe <nehe (zavináč) connect.ab.ca>
přeložil: Michal Turek - Woq <WOQ (zavináč) seznam.cz>

Zdrojové kódy

Lekce 41

<<< Lekce 40 | Lekce 42 >>>