Lekce 1 - Vytvoření OpenGL okna ve Windows

Naučíte se jak nastavit a vytvořit OpenGL okno ve Windows. Program, který vytvoříte zobrazí "pouze" prázdné okno. Černé pozadí nevypadá nic moc, ale pokud porozumíte této lekci, budete mít velmi dobrý základ pro jakoukoliv další práci. Zjistíte jak OpenGL pracuje, jak probíhá vytváření okna a také jak napsat jednoduše pochopitelný kód.

Jsem obyčejný kluk s vášní pro OpenGL. Když jsem o něm poprvé slyšel, vydalo 3Dfx zrychlené ovladače pro Voodoo 1. Hned jsem věděl, že OpenGL je něco, co se musím naučit. Bohužel bylo velice těžké najít nějaké informace, jak v knihách, tak na internetu. Strávil jsem hodiny pokusy o napsání funkčního kódu a přesvědčováním lidí emaily a na IRC. Zjistil jsem, že lidé, kteří rozuměli OpenGL, se považovali za elitu a nehodlali se o své vědomosti dělit. Velice frustrující... Vytvořil jsem tyto tutoriály, aby je zájemci o OpenGL mohli použít, když budou potřebovat pomoc. V každém tutoriálu se vše snažím vysvětlit do detailů, aby bylo jasné, co každý řádek dělá. Snažím se svůj kód psát co nejjednodušeji (nepoužívám MFC)! I absolutní nováček, jak v C++, tak v OpenGL, by měl být schopen tento kód zvládnout a mít další dobré nápady, co dělat dál. Je mnoho tutoriálů o OpenGL. Pokud jste hardcorový OpenGL programátor asi Vám budou připadat příliš jednoduché, ale pokud právě začínáte mají mnoho co nabídnout!

Začnu tento tutoriál přímo kódem. První, co se musí udělat, je vytvořit projekt. Pokud nevíte jak to udělat, neměli byste se učit OpenGL, ale Visual C++. Některé verze Visual C++ vyžadují, aby byl bool změněn na BOOL, true na TRUE a false na FALSE. Pokud to budete mít na paměti neměly by být s kompilací žádné problémy. Potom co vytvoříte novou Win32 Application (NE console application) ve Visual C++, budete potřebovat připojit OpenGL knihovny. Jsou dvě možnosti, jak to udělat: Vyberte Project>Settings, pak zvolte záložku Link a do kolonky Object/Library Modules napište na začátek řádku (před kernel32.lib) OpenGL32.lib Glu32.lib Glaux.lib. Potom klikněte na OK. Nebo napište přímo do kódu programu následující řádky.

// Vložení knihoven

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

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

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

Nyní jste připraveni napsat svůj první OpenGL program pro Windows. Začneme vložením hlavičkových souborů.

#include <windows.h>// Hlavičkový soubor pro Windows

#include <gl\gl.h>// Hlavičkový soubor pro OpenGL32 knihovnu

#include <gl\glu.h>// Hlavičkový soubor pro Glu32 knihovnu

#include <gl\glaux.h>// Hlavičkový soubor pro Glaux knihovnu

Dále potřebujete deklarovat globální proměnné, které chcete v programu použít. Tento program vytváří prázdné OpenGL okno, proto jich nebudeme potřebovat mnoho. Ty, které nyní použijeme jsou ovšem velmi důležité a budete je používat v každém programu založeném na tomto kódu. Nastavíme Rendering Context. Každý OpenGL program je spojen s Rendering Contextem. Rendering Context říká, která spojení volá OpenGL, aby se spojilo s Device Context (kontext zařízení). Nám stačí vědět, že OpenGL Rendering Context je definován jako hRC. Aby program mohl kreslit do okna potřebujete vytvořit Device Context. Ve Windows je Device Context definován jako hDC. Device Context napojí okno na GDI (grafické rozhraní). Proměnná hWnd obsahuje handle přidělený oknu a čtvrtý řádek vytvoří instanci programu.

HDC hDC = NULL;// Privátní GDI Device Context

HGLRC hRC = NULL;// Trvalý Rendering Context

HWND hWnd = NULL;// Obsahuje Handle našeho okna

HINSTANCE hInstance;// Obsahuje instanci aplikace

První řádek deklaruje pole, které budeme používat na sledování stisknutých kláves. Je mnoho způsobů, jak to udělat, ale takto to dělám já. Je to spolehlivé a můžeme sledovat stisk více kláves najednou. Proměnná active bude použita, aby náš program informovala, zda je jeho okno minimalizováno nebo ne. Když je okno minimalizováno můžeme udělat cokoliv od pozastavení činnosti kódu až po opuštění programu. Já použiji pozastavení běhu programu. Díky tomu zbytečně nepoběží na pozadí, když bude minimalizován. Proměnná fullscreen bude obsahovat informaci, jestli náš program běží přes celou obrazovku - v tom případě bude fullscreen mít hodnotu true, když program poběží v okně bude mít hodnotu false. Je důležité, aby proměnná byla globální a tím pádem každá funkce věděla, jestli program běží ve fullscreenu, nebo v okně.

bool keys[256];// Pole pro ukládání vstupu z klávesnice

bool active = TRUE;// Ponese informaci o tom, zda je okno aktivní

bool fullscreen = TRUE;// Ponese informaci o tom, zda je program ve fullscreenu

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);// Deklarace procedury okna (funkční prototyp)

Následující funkce se volá vždy, když uživatel mění velikost okna. I když nejste schopni změnit velikost okna (například ve fullscreenu), bude tato funkce volána alespoň jednou, aby nastavila perspektivní pohled při spuštění programu. Velikost OpenGL scény se bude měnit v závislosti na šířce a výšce okna, ve kterém je zobrazena.

GLvoid ReSizeGLScene(GLsizei width, GLsizei height)// Změna velikosti a inicializace OpenGL okna

{

if (height==0)// Zabezpečení proti dělení nulou

{

height=1;// Nastaví výšku na jedna

}

glViewport(0,0,width,height);// Resetuje aktuální nastavení

Nastavíme obraz na perspektivní pohled. To znamená, že vzdálenější objekty budou menší. glMatrixMode(GL_PROJECTION) ovlivní formu obrazu. Forma obrazu určuje, jak výrazná bude perspektiva. Vytvoříme realisticky vypadající scénu. glLoadIdentity() resetuje matici. Vrátí ji do jejího původního stavu. Po glLoadIdentity() nastavíme perspektivní pohled scény. Perspektiva je vypočítána s úhlem pohledu 45 stupňů a je založena na výšce a šířce okna. Číslo 0.1f je počáteční a 100.0f konečný bod, který říká jak hluboko do obrazovky můžeme kreslit. glMatrixMode(GL_MODELVIEW) oznamuje, že forma pohledu bude znovu změněna. Nakonec znovu resetujeme matici. Pokud předcházejícímu textu nerozumíte, nic si z toho nedělejte, vysvětlím ho celý v dalších tutoriálech. Jediné co nyní musíte vědět je, že následující řádky musíte do svého programu napsat, pokud chcete, aby scéna vypadala pěkně.

glMatrixMode(GL_PROJECTION);// Zvolí projekční matici

glLoadIdentity();// Reset matice

gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);// Výpočet perspektivy

glMatrixMode(GL_MODELVIEW);// Zvolí matici Modelview

glLoadIdentity();// Reset matice

}

Nastavíme vše potřebné pro OpenGL. Definujeme černé pozadí, zapneme depth buffer, aktivujeme smooth shading (vyhlazené stínování), atd.. Tato funkce se volá po vytvoření okna. Vrací hodnotu, ale tím se nyní nemusíme zabývat, protože naše inicializace není zatím úplně komplexní.

int InitGL(GLvoid)// Všechno nastavení OpenGL

{

Následující řádek povolí jemné stínování, aby se barvy na polygonech pěkně promíchaly. Více detailů o smooth shading si povíme v jiných tutoriálech.

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

Nastavíme barvu pozadí prázdné obrazovky. Rozsah barev se určuje ve stupnici od 0.0f do 1.0f. 0.0f je nejtmavší a 1.0f je nejsvětlejší. První parametr ve funkci glClearColor() je intenzita červené barvy, druhý zelené a třetí modré. Čím bližší je hodnota barvy 1.0f, tím světlejší složka barvy bude. Poslední parametr je hodnota alpha (průhlednost). Když budeme čistit obrazovku, tak se o průhlednost starat nemusíme. Nyní ji necháme na 0.0f. Můžete vytvářet různé barvy kombinováním světlosti tří základních barev (červené, zelené, modré). Pokud budete mít glClearColor(0.0f,0.0f,1.0f,0.0f), bude obrazovka modrá. Když budete mít glClearColor(0.5f,0.0f,0.0f,0.0f), bude obrazovka středně tmavě červená. Abyste udělali bílé pozadí nastavte všechny hodnoty na nejvyšší hodnotu (1.0f), pro černé pozadí zadejte pro všechny složky 0.0f.

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

Následující tři řádky ovlivňují depth buffer. Depth buffer si můžete představit jako vrstvy/hladiny obrazovky. Obsahuje informace, o tom jak hluboko jsou zobrazované objekty. Tento program sice nebude deep buffer používat (nic nevykreslujeme). Objekty se seřadí tak, aby bližší překrývaly vzdálenější.

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

glEnable(GL_DEPTH_TEST);// Povolí hloubkové testování

glDepthFunc(GL_LEQUAL);// Typ hloubkového testování

Dále oznámíme, že chceme použít nejlepší korekce perspektivy. Jen nepatrně se sníží výkon, ale zlepší se vzhled celé scény

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

Nakonec vrátíme true. Když budeme chtít zjistit zda inicializace proběhla bez problémů, můžeme zkontrolovat, zda funkce vrátila hodnotu true nebo false. Můžete přidat vlastní kód, který vrátí false, když se inicializace nezdaří - např. loading textur. Nyní se tím nebudeme dále zabývat.

return TRUE;// Inicializace proběhla v pořádku

}

Do této funkci umístíme všechno vykreslování. Následující tutoriály budou přepisovat především tento a inicializační kód této lekce. ( Pokud již nyní rozumíte základům OpenGL, můžete si zde připsat kreslení základních tvarů (mezi glLoadIdentity() a return). Pokud jste nováček, tak počkejte do dalšího tutoriálu. Jediné co nyní uděláme, je vymazání obrazovky na barvu, pro kterou jste se rozhodli, vymažeme obsah hloubkového bufferu a resetujeme scénu. Zatím nebudeme nic kreslit. Příkaz return true nám říká, že při kreslení nenastaly žádné problémy. Pokud z nějakého důvodu chcete přerušit běh programu, stačí přidat return false před return true - to říká našemu programu, že kreslení scény selhalo a program se ukončí.

int DrawGLScene(GLvoid)// Vykreslování

{

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

glLoadIdentity();// Reset matice

// Sem můžete kreslit

return TRUE;// Vykreslení proběhlo v pořádku

}

Následující část kódu je volána těsně před koncem programu. Úkolem funkce KillGLWindow() je uvolnění renderovacího kontextu, kontextu zařízení a handle okna. Přidal jsem zde nezbytné kontrolování chyb. Když není program schopen něco uvolnit, oznámí chybu, která říká co selhalo. Usnadní složité hledání problémů.

GLvoid KillGLWindow(GLvoid)// Zavírání okna

{

Zjistíme zda je program ve fullscreenu. Pokud ano, tak ho přepneme zpět do systému. Mohli bychom vypnout okno před opuštěním fullscreenu, ale na některých grafických kartách tím způsobíme problémy a systém by se mohl zhroutit.

if (fullscreen)// Jsme ve fullscreenu?

{

K návratu do původního nastavení systému používáme funkci ChangeDisplaySettings(NULL,0). Jako první parametr zadáme NULL a jako druhý 0 - použijeme hodnoty uložené v registrech Windows (původní rozlišení, barevnou hloubku, obnovovací frekvenci, atd.). Po přepnutí zviditelníme kurzor.

ChangeDisplaySettings(NULL,0);// Přepnutí do systému

ShowCursor(TRUE);// Zobrazí kurzor myši

}

Zkontrolujeme zda máme renderovací kontext (hRC). Když ne, program přeskočí část kódu pod ním, který kontroluje, zda máme kontext zařízení.

if (hRC)// Máme rendering kontext?

{

Zjistíme, zda můžeme odpojit hRC od hDC. Všimněte si, jak kontroluji chyby. Nejdříve programu řeknu, ať odpojí Rendering Context (s použitím wglMakeCurrent(NULL,NULL)), pak zkontroluji zda akce byla úspěšná. Takto dám více řádku do jednoho.

if (!wglMakeCurrent(NULL,NULL))// Jsme schopni oddělit kontexty?

{

Pokud nejsme schopni uvolnit DC a RC, použijeme zobrazíme zprávu, že DC a RC nelze uvolnit. NULL v parametru znamená, že informační okno nemá žádného rodiče. Text ihned za NULL je text, který se vypíše do zprávy. Další parametr definuje text lišty. Parametr MB_OK znamená, že chceme mít na chybové zprávě jen jedno tlačítko s nápisem OK. MB_ICONINFORMATION zobrazí ikonu.

MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

}

Zkusíme vymazat Rendering Context. Pokud se pokus nezdaří, opět se zobrazí chybová zpráva. Nakonec nastavíme hRC a NULL.

if (!wglDeleteContext(hRC))// Jsme schopni smazat RC?

{

MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

}

hRC=NULL;// Nastaví hRC na NULL

}

Zjistíme zda má program kontext zařízení. Když ano odpojíme ho. Pokud se odpojení nezdaří, zobrazí se chybová zpráva a hDC bude nastaven na NULL.

if (hDC && !ReleaseDC(hWnd,hDC))// Jsme schopni uvolnit DC

{

MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

hDC=NULL;// Nastaví hDC na NULL

}

Nyní zjistíme zda máme handle okna a pokud ano pokusíme se odstranit okno použitím funkce DestroyWindow(hWnd). Pokud se pokus nezdaří, zobrazí se chybová zpráva a hWnd bude nastaveno na NULL.

if (hWnd && !DestroyWindow(hWnd))// Jsme schopni odstranit okno?

{

MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

hWnd=NULL;// Nastaví hWnd na NULL

}

Odregistrováním třídy okna oficiálně uzavřeme okno a předejdeme zobrazení chybové zprávy "Windows Class already registered" při opětovném spuštění programu.

if (!UnregisterClass("OpenGL",hInstance))// Jsme schopni odregistrovat třídu okna?

{

MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

hInstance=NULL;// Nastaví hInstance na NULL

}

}

Další část kódu má na starosti vytvoření OpenGL okna. Strávil jsem mnoho času přemýšlením zda mám udělat pouze fullscreen mód, který by vyžadoval méně kódu, nebo jednoduše upravitelnou, uživatelsky příjemnou verzi s variantou jak pro okno tak pro fullscreen, která však vyžaduje mnohem více kódu. Rozhodl jsem se pro druhou variantu, protože jsem dostával mnoho dotazů jako například: Jak mohu vytvořit okno místo fullscreenu? Jak změním popisek okna? Jak změním rozlišení ne formát pixelů? Následující kód dovede všechno. Jak si můžete všimnout funkce vrací bool a přijímá 5 parametrů v pořadí: název okna, šířku okna, výšku okna, barevnou hloubku, fullscreen (pokud je parametr true program poběží ve fullscreenu, pokud bude false program poběží v okně). Vracíme bool, abychom věděli zda bylo okno úspěšně vytvořeno.

BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)

{

Za chvíli požádáme Windows, aby pro nás našel pixel format, který odpovídá tomu, který chceme. Toto číslo uložíme do proměnné PixelFormat.

GLuint PixelFormat;// Ukládá formát pixelů

Wc bude použijeme k uchování informací o struktuře Windows Class. Změnou hodnot jednotlivých položek, lze ovlivnit vzhled a chování okna. Před vytvořením samotného okna se musí zaregistrovat nějaká struktura pro okno.

WNDCLASS wc;// Struktura Windows Class

DwExStyle a dwStyle ponesou informaci o normálních a rozšířených informacích o oknu. Použiji proměnné k uchování stylů, takže mohu měnit vzhled okna, který potřebuji vytvořit (pro fullscreen bez okraje a pro okno okraj).

DWORD dwExStyle;// Rozšířený styl okna

DWORD dwStyle;// Styl okna

Zjistíme polohu levého horního a pravého dolního rohu okna. Tyto proměnné využijeme k tomu, abychom nakreslili okno v takovém rozlišení, v jakém si ho přejeme mít. Pokud vytvoříme okno s rozlišením 640x480, okraje budou zabírat část našeho rozlišení.

RECT WindowRect;// Obdélník okna

WindowRect.left = (long)0;// Nastaví levý okraj na nulu

WindowRect.right = (long)width;// Nastaví pravý okraj na zadanou hodnotu

WindowRect.top = (long)0;// Nastaví horní okraj na nulu

WindowRect.bottom = (long)height;// Nastaví spodní okraj na zadanou hodnotu

Přiřadíme globální proměnné fullscreen, hodnotu fullscreenflag. Takže pokud naše okno poběží ve fullscreenu, proměnná fullscreen se bude rovnat true. Kdybychom zavírali okno ve fullscreenu, ale hodnota proměnné fullscreen by byla false místo true, jak by měla být, počítač by se nepřepl zpět do systému, protože by si myslel, že v něm již je. Jednoduše shrnuto, fullscreen vždy musí obsahovat správnou hodnotu.

fullscreen = fullscreenflag;// Nastaví proměnnou fullscreen na správnou hodnotu

Získáme instanci pro okno a poté definujeme Window Class. CS_HREDRAW a CS_VREDRAW donutí naše okno, aby se překreslilo, kdykoliv se změní jeho velikost. CS_OWNDC vytvoří privátní kontext zařízení. To znamená, že není sdílen s ostatními aplikacemi. WndProc je procedura okna, která sleduje příchozí zprávy pro program. Žádná extra data pro okno nepoužíváme, takže do dalších dvou položek přiřadíme nulu. Nastavíme instanci a hIcon na NULL, což znamená, že nebudeme pro náš program používat žádnou speciální ikonu a pro kurzor myši používáme standardní šipku. Barva pozadí nás nemusí zajímat (to zařídíme v OpenGL). Nechceme, aby okno mělo menu, takže i tuto hodnotu nastavíme na NULL. Jméno třídy může být libovolné. Já použiji pro jednoduchost "OpenGL".

hInstance = GetModuleHandle(NULL);// Získá instanci okna

wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;// Překreslení při změně velikosti a vlastní DC

wc.lpfnWndProc = (WNDPROC) WndProc;// Definuje proceduru okna

wc.cbClsExtra = 0;// Žádná extra data

wc.cbWndExtra = 0;// Žádná extra data

wc.hInstance = hInstance;// Instance

wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);// Standardní ikona

wc.hCursor = LoadCursor(NULL, IDC_ARROW);// Standardní kurzor myši

wc.hbrBackground = NULL;// Pozadí není nutné

wc.lpszMenuName = NULL;// Nechceme menu

wc.lpszClassName = "OpenGL";// Jméno třídy okna

Zaregistrujeme právě definovanou třídu okna. Když nastane chyba a zobrazí se chybové hlášení. Zmáčknutím tlačítka OK se program ukončí.

if (!RegisterClass(&wc))// Registruje třídu okna

{

MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);

return FALSE;// Při chybě vrátí false

}

Nyní si zjistíme zda má program běžet ve fullscreenu, nebo v okně.

if (fullscreen)// Budeme ve fullscreenu?

{

S přepínáním do fullscreenu, mívají lidé mnoho problémů. Je zde pár důležitých věcí, na které si musíte dávat pozor. Ujistěte se, že šířka a výška, kterou používáte ve fullscreenu je totožná s tou, kterou chcete použít v okně. Další věc je hodně důležitá. Musíte přepnout do fullscreenu předtím než vytvoříte okno. V tomto kódu se o rovnost výšky a šířky nemusíte starat, protože velikost ve fullscreenu i v okně budou stejné.

DEVMODE dmScreenSettings;// Mód zařízení

memset(&dmScreenSettings,0,sizeof(dmScreenSettings));// Vynulování paměti

dmScreenSettings.dmSize=sizeof(dmScreenSettings);// Velikost struktury Devmode

dmScreenSettings.dmPelsWidth= width;// Šířka okna

dmScreenSettings.dmPelsHeight= height;// Výška okna

dmScreenSettings.dmBitsPerPel= bits;// Barevná hloubka

dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

Funkce ChangeDisplaySettings() se pokusí přepnout do módu, který je uložen v dmScreenSettings. Použiji parametr CDS_FULLSCREEN, protože odstraní pracovní lištu ve spodní části obrazovky a nepřesune nebo nezmění velikost okna při přepínání z fullscreenu do systému nebo naopak.

// Pokusí se použít právě definované nastavení

if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)

{

Pokud právě vytvořený fullscreen mód neexistuje, zobrazí se chybová zpráva s nabídkou spuštění v okně nebo opuštění programu.

// Nejde-li fullscreen, může uživatel spustit program v okně nebo ho opustit

if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)

{

Když se uživatel rozhodne pro běh v okně, do proměnné fullscreen se přiřadí false a program pokračuje dále.

fullscreen=FALSE;// Běh v okně

}

else

{

Pokud se uživatel rozhodl pro ukončení programu, zobrazí se uživateli zpráva, že program bude ukončen. Bude vrácena hodnota false, která našemu programu říká, že pokus o vytvoření okna nebyl úspěšný a potom se program ukončí.

// Zobrazí uživateli zprávu, že program bude ukončen

MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);

return FALSE;// Vrátí FALSE

}

}

}

Protože pokus o přepnutí do fullscreenu může selhat, nebo se uživatel může rozhodnout pro běh programu v okně, zkontrolujeme ještě jednou zda je proměnná fullscreen true nebo false. Až poté nastavíme typ obrazu.

if (fullscreen)// Jsme stále ve fullscreenu?

{

Pokud jsme stále ve fullscreenu nastavíme rozšířený styl na WS_EX_APPWINDOW, což donutí okno, aby překrylo pracovní lištu. Styl okna určíme na WS_POPUP. Tento typ okna nemá žádné okraje, což je pro fullscreen výhodné. Nakonec vypneme kurzor myši. Pokud váš program není interaktivní, je většinou vhodnější ve fullscreenu kurzor vypnout. Pro co rozhodnete je na vás.

dwExStyle=WS_EX_APPWINDOW;// Rozšířený styl okna

dwStyle=WS_POPUP;// Styl okna

ShowCursor(FALSE);// Skryje kurzor

}

else

{

Pokud místo fullscreenu používáme běh v okně, nastavíme rozšířený styl na WS_EX_WINDOWEDGE. To dodá oknu trochu 3D vzhledu. Styl nastavíme na WS_OVERLAPPEDWINDOW místo na WS_POPUP. WS_OVERLAPPEDWINDOW vytvoří okno s lištou, okraji, tlačítky pro minimalizaci a maximalizaci. Budeme moci měnit velikost.

dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;// Rozšířený styl okna

dwStyle=WS_OVERLAPPEDWINDOW;// Styl okna

}

Přizpůsobíme okno podle stylu, který jsme vytvořili. Přizpůsobení udělá okno v takovém rozlišení, jaké požadujeme. Normálně by okraje překrývaly část okna. S použitím příkazu AdjustWindowRectEx žádná část OpenGL scény nebude překryta okraji, místo toho bude okno uděláno o málo větší, aby se do něj vešly všechny pixely tvořící okraj okna. Ve fullscreenu tato funkce nemá žádný efekt.

AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);// Přizpůsobení velikosti okna

Vytvoříme okno a zkontrolujeme zda bylo vytvořeno správně. Použijeme funkci CreateWindowEx() se všemi parametry, které vyžaduje. Rozšířený styl, který jsme se rozhodli použít. Jméno třídy (musí být stejné jako to, které jste použili, když jste registrovali Window Class).Titulek okna. Styl okna. Horní levá pozice okna (0,0 je nejjistější). Šířka a výška okna. Nechceme mít rodičovské okno ani menu, takže nastavíme tyto parametry na NULL. Zadáme instanci okna a konečně přiřadíme NULL na místo posledního parametru. Všimněte si, že zahrnujeme styly WS_CLIPSIBLINGS a WS_CLIPCHILDREN do stylu, který jsme se rozhodli použít. WS_CLIPSIBLINGS a WS_CLIPCHILDREN jsou potřebné pro OpenGL, aby pracovalo správně. Tyto styly zakazují ostatním oknům, aby kreslily do našeho okna.

// Vytvoření okna

if (!(hWnd=CreateWindowEx(dwExStyle,// Rozšířený styl

"OpenGL",// Jméno třídy

title,// Titulek

dwStyle |// Definovaný styl

WS_CLIPSIBLINGS |// Požadovaný styl

WS_CLIPCHILDREN,// Požadovaný styl

0, 0,// Pozice

WindowRect.right-WindowRect.left,// Výpočet šířky

WindowRect.bottom-WindowRect.top,// Výpočet výšky

NULL,// Žádné rodičovské okno

NULL,// Bez menu

hInstance,// Instance

NULL)))// Nepředat nic do WM_CREATE

Dále zkontrolujeme zda bylo vytvořeno. Pokud bylo, hWnd obsahuje handle tohoto okna. Když se vytvoření okna nepovede, kód zobrazí chybovou zprávu a program se ukončí.

{

KillGLWindow();// Zruší okno

MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);

return FALSE;// Vrátí chybu

}

Vybereme Pixel Format, který podporuje OpenGL, dále zvolíme double buffering a RGBA (červená, zelená, modrá, průhlednost). Pokusíme se najít formát, který odpovídá tomu, pro který jsme se rozhodli (16 bitů, 24 bitů, 32 bitů). Nakonec nastavíme Z-Buffer. Ostatní parametry se nepoužívají nebo pro nás nejsou důležité.

static PIXELFORMATDESCRIPTOR pfd=// Oznámíme Windows jak chceme vše nastavit

{

sizeof(PIXELFORMATDESCRIPTOR),// Velikost struktury

1,// Číslo verze

PFD_DRAW_TO_WINDOW |// Podpora okna

PFD_SUPPORT_OPENGL |// Podpora OpenGL

PFD_DOUBLEBUFFER,// Podpora Double Bufferingu

PFD_TYPE_RGBA,// RGBA Format

bits,// Zvolí barevnou hloubku

0, 0, 0, 0, 0, 0,// Bity barev ignorovány

0,// Žádný alpha buffer

0,// Ignorován Shift bit

0,// Žádný akumulační buffer

0, 0, 0, 0,// Akumulační bity ignorovány

16,// 16-bitový hloubkový buffer (Z-Buffer)

0,// Žádný Stencil Buffer

0,// Žádný Auxiliary Buffer

PFD_MAIN_PLANE,// Hlavní vykreslovací vrstva

0,// Rezervováno

0, 0, 0// Maska vrstvy ignorována

};

Pokud nenastaly problémy během vytváření okna, pokusíme se připojit kontext zařízení. Pokud ho se nepřipojí, zobrazí se chybové hlášení a program se ukončí.

if (!(hDC=GetDC(hWnd)))// Podařilo se připojit kontext zařízení?

{

KillGLWindow();// Zavře okno

MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);

return FALSE;// Ukončí program

}

Když získáme kontext zařízení, pokusíme se najít odpovídající Pixel Format. Když ho Windows nenajde formát, zobrazí se chybová zpráva a program se ukončí.

if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))// Podařilo se najít Pixel Format?

{

KillGLWindow();// Zavře okno

MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);

return FALSE;// Ukončí program

}

Když Windows najde odpovídající formát, tak se ho pokusíme nastavit. Pokud při pokusu o nastavení nastane chyba, opět se zobrazí chybové hlášení a program se ukončí.

if(!SetPixelFormat(hDC,PixelFormat,&pfd))// Podařilo se nastavit Pixel Format?

{

KillGLWindow();// Zavře okno

MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);

return FALSE;// Ukončí program

}

Pokud byl nastaven Pixel Format správně, pokusíme se získat Rendering Context. Pokud ho nezískáme, program zobrazí chybovou zprávu a ukončí se.

if (!(hRC=wglCreateContext(hDC)))// Podařilo se vytvořit Rendering Context?

{

KillGLWindow();// Zavře okno

MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);

return FALSE;// Ukončí program

}

Pokud nenastaly žádné chyby při vytváření jak Device Context, tak Rendering Context, vše co musíme nyní udělat je aktivovat Rendering Context. Pokud ho nebudeme moci aktivovat, zobrazí se chybová zpráva a program se ukončí.

if(!wglMakeCurrent(hDC,hRC))// Podařilo se aktivovat Rendering Context?

{

KillGLWindow();// Zavře okno

MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);

return FALSE;// Ukončí program

}

Pokud bylo okno vytvořeno, zobrazíme ho na obrazovce, nastavíme ho, aby bylo v popředí (vyšší priorita) a pak nastavíme zaměření na toto okno. Zavoláme funkci ResizeGLScene() s parametry odpovídajícími výšce a šířce okna, abychom správně nastavili perspektivu OpenGL.

ShowWindow(hWnd,SW_SHOW);// Zobrazení okna

SetForegroundWindow(hWnd);// Do popředí

SetFocus(hWnd);// Zaměří fokus

ReSizeGLScene(width, height);// Nastavení perspektivy OpenGL scény

Konečně se dostáváme k volání výše definované funkce InitGL(), ve které nastavujeme osvětlení, loading textur a cokoliv jiného, co je potřeba. Můžete vytvořit svou vlastní kontrolu chyb ve funkci InitGL() a vracet true, když vše proběhne bez problémů, nebo false, pokud nastanou nějaké problémy. Například, nastane-li chyba při nahrávání textur, vrátíte false, jako znamení, že něco selhalo a program se ukončí.

if (!InitGL())// Inicializace okna

{

KillGLWindow();// Zavře okno

MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);

return FALSE;// Ukončí program

}

Pokud jsme se dostali až takhle daleko, můžeme konstatovat, že vytvoření okna proběhlo bez problémů. Vrátíme true do WinMain(), což říká, že nenastaly žádné chyby. To zabrání programu, aby se sám ukončil.

return TRUE;// Vše proběhlo v pořádku

}

Nyní se vypořádáme se systémovými zprávami pro okno. Když máme zaregistrovanou naši Window Class, můžeme podstoupit k části kódu, která má na starosti zpracování zpráv.

LRESULT CALLBACK WndProc(HWND hWnd,// Handle okna

UINT uMsg,// Zpráva pro okno

WPARAM wParam,// Doplňkové informace

LPARAM lParam)// Doplňkové informace

{

Napíšeme mapu zpráv. Program se bude větvit podle proměnné uMsg, která obsahuje jméno zprávy.

switch (uMsg)// Větvení podle příchozí zprávy

{

Po příchodu WM_ACTIVE, zkontrolujeme, zda je okno stále aktivní. Pokud bylo minimalizováno, nastavíme hodnotu active na false. Pokud je naše okno aktivní, proměnná active bude mít hodnotu true.

case WM_ACTIVATE:// Změna aktivity okna

{

if (!HIWORD(wParam))// Zkontroluje zda není minimalizované

{

active=TRUE;// Program je aktivní

}

else

{

active=FALSE;// Program není aktivní

}

return 0;// Návrat do hlavního cyklu programu

}

Po příchodu WM_SYSCOMMAND (systémový příkaz) porovnáme wParam s možnými stavy, které mohly nastat. Když je wParam WM_SCREENSAVE nebo SC_MONITORPOWER snaží se systém zapnout spořič obrazovky, nebo přejít do úsporného režimu. Jestliže vrátíme 0 zabráníme systému, aby tyto akce provedl.

case WM_SYSCOMMAND:// Systémový příkaz

{

switch (wParam)// Typ systémového příkazu

{

case SC_SCREENSAVE:// Pokus o zapnutí šetřiče obrazovky

case SC_MONITORPOWER:// Pokus o přechod do úsporného režimu?

return 0;// Zabrání obojímu

}

break;// Návrat do hlavního cyklu programu

}

Přišlo-li WM_CLOSE bylo okno zavřeno. Pošleme tedy zprávu pro opuštění programu, která přeruší vykonávání hlavního cyklu. Proměnnou done (ve WinMain()) nastavíme na true, hlavní smyčka se přeruší a program se ukončí.

case WM_CLOSE:// Povel k ukončení programu

{

PostQuitMessage(0);// Pošle zprávu o ukončení

return 0;// Návrat do hlavního cyklu programu

}

Pokud byla stisknuta klávesa, můžeme zjistit, která z nich to byla, když zjistíme hodnotu wParam. Potom zadáme do buňky, specifikované wParam, v poli keys[] true. Díky tomu potom můžeme zjistit, která klávesa je právě stisknutá. Tímto způsobem lze zkontrolovat stisk více kláves najednou.

case WM_KEYDOWN:// Stisk klávesy

{

keys[wParam] = TRUE;// Oznámí to programu

return 0;// Návrat do hlavního cyklu programu

}

Pokud byla naopak klávesa uvolněna uložíme do buňky s indexem wParam v poli keys[] hodnotu false. Tímto způsobem můžeme zjistit zda je klávesa ještě stále stisknuta nebo již byla uvolněna. Každá klávesa je reprezentována jedním číslem od 0 do 255. Když například stisknu klávesu číslo 40, hodnota key[40] bude true, jakmile ji pustím její hodnota se vrátí opět na false.

case WM_KEYUP:// Uvolnění klávesy

{

keys[wParam] = FALSE;// Oznámí to programu

return 0;// Návrat do hlavního cyklu programu

}

Kdykoliv uživatel změní velikost okna, pošle se WM_SIZE. Přečteme LOWORD a HIWORD hodnoty lParam, abychom zjistili jaká je nová šířka a výška okna. Předáme tyto hodnoty do funkce ReSizeGLScene(). Perspektiva OpenGL scény se změní podle nových rozměrů.

case WM_SIZE:// Změna velikosti okna

{

ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Šířka, HiWord=Výška

return 0;// Návrat do hlavního cyklu programu

}

}

Zprávy, o které se nestaráme, budou předány funkci DefWindowProc(), takže se s nimi vypořádá systém.

return DefWindowProc(hWnd, uMsg, wParam, lParam);// Předání ostatních zpráv systému

}

Funkce WinMain() je vstupní bod do aplikace, místo, odkud budeme volat funkce na otevření okna, snímaní zpráv a interakci s uživatelem.

int WINAPI WinMain(HINSTANCE hInstance,// Instance

HINSTANCE hPrevInstance,// Předchozí instance

LPSTR lpCmdLine,// Parametry příkazové řádky

int nCmdShow)// Stav zobrazení okna

{

Deklarujeme dvě lokální proměnné. Msg bude použita na zjišťování, zda se mají zpracovávat nějaké zprávy. Proměnná done bude mít na počátku hodnotu false. To znamená, že náš program ještě nemá být ukončen. Dokud se done rovná false, program poběží. Jakmile se změní z false na true, program se ukončí.

MSG msg;// Struktura zpráv systému

BOOL done=FALSE;// Proměnná pro ukončení programu

Další část kódu je volitelná. Zobrazuje zprávu, která se zeptá uživatele, zda chce spustit program ve fullscreenu. Pokud uživatel vybere možnost Ne, hodnota proměnné fullscreen se změní z výchozího true na false, a tím pádem se program spustí v okně.

// Dotaz na uživatele pro fullscreen/okno

if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?", MB_YESNO | MB_ICONQUESTION) == IDNO)

{

fullscreen=FALSE;// Běh v okně

}

Vytvoříme OpenGL okno. Zadáme text titulku, šířku, výšku, barevnou hloubku a true (fullscreen), nebo false (okno) jako parametry do funkce CreateGLWindow(). Tak a je to! Je to pěkně lehké, že? Pokud se okno nepodaří z nějakého důvodu vytvořit, bude vráceno false a program se okamžitě ukončí.

if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))// Vytvoření OpenGL okna

{

return 0;// Konec programu při chybě

}

Smyčka se opakuje tak dlouho, dokud se done rovná false.

while(!done)// Hlavní cyklus programu

{

První věc, kterou uděláme, je zkontrolování zpráv pro okno. Pomocí funkce PeekMessage() můžeme zjistit zda nějaké zprávy čekají na zpracování bez toho, aby byl program pozastaven. Mnoho programů používá funkci GetMessage(). Pracuje to skvěle, ale program nic nedělá, když nedostává žádné zprávy.

if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))// Přišla zpráva?

{

Zkontrolujeme, zda jsme neobdrželi zprávu pro ukončení programu. Pokud je aktuální zpráva WM_QUIT, která je způsobena voláním funkce PostQuitMessage(0), nastavíme done na true, čímž přerušíme hlavní cyklus a ukončíme program.

if (msg.message==WM_QUIT)// Obdrželi jsme zprávu pro ukončení?

{

done=TRUE;// Konec programu

}

else// Předáme zprávu proceduře okna

{

Když zpráva nevyzývá k ukončení programu, tak předáme funkcím TranslateMessage() a DispatchMessage() referenci na tuto zprávu, aby ji funkce WndProc() nebo Windows zpracovaly.

TranslateMessage(&msg);// Přeloží zprávu

DispatchMessage(&msg);// Odešle zprávu

}

}

else// Pokud nedošla žádná zpráva

{

Pokud zde nebudou již žádné zprávy, překreslíme OpenGL scénu. Následující řádek kontroluje, zda je okno aktivní. Naše scéna je vyrenderována a je zkontrolována vrácená hodnota. Když funkce DrawGLScene() vrátí false nebo je stisknut ESC, hodnota proměnné done je nastavena na true, což ukončí běh programu.

if (active)// Je program aktivní?

{

if (keys[VK_ESCAPE])// Byl stisknut ESC?

{

done=TRUE;// Ukončíme program

}

else// Překreslení scény

{

Když všechno proběhlo bez problémů, prohodíme obsah bufferů (s použitím dvou bufferů předejdeme blikání obrazu při překreslování). Použitím dvojtého bufferingu všechno vykreslujeme do obrazovky v paměti, kterou nevidíme. Jakmile vyměníme obsah bufferů, to co je na obrazovce se přesune do této skryté obrazovky a to, co je ve skryté obrazovce se přenese na monitor. Díky tomu nevidíme probliknutí.

DrawGLScene();// Vykreslení scény

SwapBuffers(hDC);// Prohození bufferů (Double Buffering)

}

}

Při stisku klávesy F1 přepneme z fullscreenu do okna a naopak.

if (keys[VK_F1])// Byla stisknuta klávesa F1?

{

keys[VK_F1]=FALSE;// Označ ji jako nestisknutou

KillGLWindow();// Zruší okno

fullscreen=!fullscreen;// Negace fullscreen

// Znovuvytvoření okna

if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))

{

return 0;// Konec programu pokud nebylo vytvořeno

}

}

}

}

Pokud se proměnná done rovná true, hlavní cyklus se přeruší. Zavřeme okno a opustíme program.

KillGLWindow();// Zavře okno

return (msg.wParam);// Ukončení programu

}

V této lekci jsem se vám pokoušel co nejpodrobněji vysvětlit každý krok při nastavování a vytváření OpenGL programu. Program se ukončí při stisku klávesy ESC a sleduje, zda je okno aktivní či nikoliv.

napsal: Jeff Molofee - NeHe <nehe (zavináč) connect.ab.ca>
přeložil: Václav Slováček - Wessan <horizont (zavináč) host.sk>

Zdrojové kódy

Lekce 1

<<< Lekce 0 | Lekce 2 >>>