Dneškem začínáme seriál o programování s knihovnou OpenGL.
Úvod
Ve zkratce řečeno je OpenGL (zkr. OGL) jednou z knihoven, které jsou schopny komunikovat s hardwarem grafické karty a donutit ji, aby poslala sestavu barevných bodů do monitoru. Takovýchto knihoven je samozřejmě více. My jsme ovšem zvolili OGL a dobře jsme udělali. Proč? To nechám do různých diskusí, kde se neustále řeší problémy typu: Je lepší PC nebo Mac? DirectX nebo OpenGL? Housky nebo rohlíky?
S OGL se dá zacházet prostřednictvím takřka všech programovacích jazyků. Já jsem zvolil C++. Poté, co si osvojíte základní funkce a příkazy, budete schopni s ní ale pracovat i ve svém oblíbeném Delphi, Pythonu či čemkoli jiném. OGL není sama osobě API (tedy Application Interface), tzn. potřebujeme ještě nějakou chytrou knihovnu, které nám umožní, abychom k tomu, co jsme naprogramovali, dokázali přistupovat. Budeme používat GLUT, má sice spoustu omezení, ale učíme se OGL a ne nějaké API. GLUT je knihovna na používání velice jednoduchá, zejména v začátcích a i pro těch několik prvních hrách případně programů, které vytvoříte, bude stačit. I když je jasné, že až budete chtít vyrobit něco jako Oblivion případně 3Ds Max, bude se muset naučit např. WinApi.
Takže s chutí do toho a půl je hotovo!
Instalace
V dnešní části se naučíme zinicializovat GLUT okno a vykreslíme si do něj první objekt. Nenechte se odradit tím, že to na vás bude spousta informací najednou, klidně si prostě zkopírujte kód, jelikož ten se v zásadě měnit nebude a budeme pořád pracovat ve dvou funkcích, jedné pro inicializaci, druhé pro vykreslení. Doporučuji si ale GLUT nastudovat, jelikož později se bude hodit (třeba na Root.cz). Sami uvidíte, že za tři dny to máte v hlavě. Navíc posléze přijdou i věci typu ovládání myši, klávesnice, případně časování. Pokusím se je sice trochu vysvětlit, ale v zásadě se budu tvářit, jakože je znáte.
Nejdřív je potřeba nainstalovat jak GLUT, tak OpenGL do vašeho oblíbeného kompilátoru… tím mám samozřejmě na mysli DevC++. Pomocí volby Nástroje → Zjistit updaty
si stáhněte instalační balíčky pro GLUT a OGL. V DevC++ se proklikejte skrz Nástroje → Package manager
, tam dejte Install
, vyberte balíček a nainstalujete (pokud se balíček nenainstaluje automaticky při stažení). Tak se vám bez větší námahy dostanou tam, kam mají, ty knihovny, které mají. Pokud se vše podařilo nainstalovat, v Soubory → Nový → Projekt
pod záložkou Multimédia
by se vám měl objevit projekt v GLUT. Ten si otevřete. Pokud se na něj podíváte, uvidíte tam spoustu písmenek nedávajících absolutně žádný smysl, ale to je v pořádku, za chvíli tohle bude vaše abeceda. Jestli jste se už pokochali, tak to všechno s klidem smažte (to myslím vážně)!
Začínáme
Vytvoříme si vlastní šablonu, se kterou budeme posléze pracovat.
Nejdřív je třeba includovat knihovnu GLUT (měli byste ji mít v adresáři ../include/GL/
).
#include <gl/glut.h>
Teď si postupně ve zkratce projedeme funkci main, ve které se vše v zásadě odehrává.
int main (int argc, char **argv) {
Inicializujeme okno.
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
glutInitWindowSize(640,480);
glutInitWindowPosition(10,10);
glutCreateWindow("Moje prvni glut okno");
V prvním řádku se inicializuje samotná knihovna GLUT. Druhý řádek nastavuje tzv. framebuffer. Framebuffer si lze představit jako dvourozměrné pole o velikosti šířka okna krát výška okna. Jsou v něm obsaženy barevné informace jednotlivých pixelů, které jsou posléze poslány do monitoru. Všechno, co se v OGL vypočítá a vykreslí během jednoho průchodu, se uloží do framebufferu a pokud je vykreslování dokončeno, barevné hodnoty se najednou „nalejí“ do výstupu grafické karty.
Prvním parametrem GLUT_RGBA tedy GLUTu říkáme aby inicializoval framebuffer, do kterého se budou ukládat RGBA informace (Red, Blue, Green, Alpha).
Mimo barevného bufferu je zde ještě tzv. depth buffer GLUT_DEPTH (někdy z-buffer), tedy paměť hloubky; v ní se ukládá hloubka jednotlivých pixelů. V zásadě se jedná také o barevnou hodnotu ve stupních šedi s tím, že nejtmavší znamená nejbližší k pozorovateli. Vzdálenější pixely jsou automaticky přepsány pixely, které je překrývají a jsou blíže k pozorovateli (jak uvidíme v některé z dalších lekcí, toto chování se dá změnit). Díky depth bufferu ví grafická karta, které barevné informace má zahodit a které je třeba zobrazit, jelikož jsou vidět.
Posledním parametr – GLUT_DOUBLE – inicializuje tzv. double buffering. To znamená, že není pouze jeden framebuffer, který se vytvoří a „naleje“ do monitoru, to by totiž znamenalo prodlevu mezi jednotlivými vykresleními, nýbrž jsou dva. Tedy, zatímco jeden se zobrazuje v monitoru, druhý se zpracovává.
K bufferům si ještě někdy povíme více. Zatím to je spíše o tom, abyste věděli, co píšete do okna kompilátoru.
Nyní následuje registrace tzv. callback funkcí.
glutDisplayFunc(onDisplay);
glutIdleFunc(onDisplay);
glutReshapeFunc(onResize);
Tyto funkce mají za parametr ukazatel na jinou funkci, kterou pak vykonávají. Jsou volány znovu a znovu při každém průchodu smyčky funkce main (f-ce main je v zásadě nekonečný cyklus, který vykonává tyhle f-ce dokud není zastaven).
- void glutDisplayFunc(void (*func)(void));
- Registruje funkci pro zobrazení. Já jsem si ji pojmenoval onDisplay, ale pojmenujte si ji jak chcete. To je tedy jedna ze dvou funkcí, které budeme tvořit celý tento seriál, odehrávají se v ní všechny OGL příkazy.
- void glutIdleFunc (void (*func)());
- Tento příkaz registruje f-ci, která se provádí, když nejsou přijímány žádné povely (např. pohyb myší atd). Všimněte si, že je v ní opět zaregistrována funkce onDisplay. To je logické, jelikož když zrovna není počítač zaměstnán něčím jiným, tak vykresluje, proto vám na obrazovce neuhnije jediný obraz, ale mění se v průběhu.
- void glutReshapeFunc (void (*func)(int width, int height));
- Tato funkce se volá při každé změně velikosti okna. Zajišťuje to, aby se nám obraz nedeformoval při roztažení okna (v našem případě), na druhou stranu se ale může starat i o to, aby se naopak deformoval. Touto funkcí se zatím nebudeme moc zabývat. Probereme si ji později.
Teď už zbývá jenom:
init();
glutMainLoop();
return 0;
Init() je funkce, ve které budeme provádět inicializaci OpenGL, opět se může jmenovat jakkoli. Vlastně zde ani nemusí být, ale to by znamenalo, že příkazy, které jsou v ní, by se musely provádět ve vykreslovací funkci každý frame a to by dost zpomalovalo chod programu. Proto je třeba si vždy promyslet, co budeme v průběhu programu měnit a co stačí, aby bylo určeno jenom jednou na začátku.
GlutMainLoop() pak spouští a udržuje v chodu onu nekonečnou smyčku, jež byla zmíněna v souvislosti s callback funkcemi.
Zbytek je doufám jasný.
Teď se podívejme na funkce init() a onDisplay() a onResize(). Nebudu je zatím vysvětlovat, pouze je stručně okomentuji, jelikož věci, které jsou v nich obsaženy, si probereme postupně v následujících dílech.
Tato funkce probíhá vždy při změně velikosti okna:
void onResize(int w, int h) {
glMatrixMode(GL_PROJECTION); // bude se upravovat projekční matice
glLoadIdentity(); // nastaví se na jednotkovou matici
glViewport(0, 0, w, h); // nastaví se vlastnosti viewportu
gluPerspective(45,(double)w/(double)h,1,100);
glMatrixMode(GL_MODELVIEW); // přepne se na modelview matici
}
Následující funkce se spustí jenom jednou, a to při startu programu:
void init() {
glEnable(GL_DEPTH_TEST); // povolí se testování hloubky
glClearColor(1.0,1.0,1.0,1.0); // nastaví se barva pozadí
glEnable(GL_LIGHTING);// zapnou se světla
glEnable(GL_LIGHT0); // zapne se světlo 0
}
Tato funkce probíhá každý frame znovu a znovu:
void onDisplay(void){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); // vymaže se frame buffer a depth buffer, aby se do něj mohlo začít znovu kreslit
glLoadIdentity(); // modelview matice se nastaví na jednotkovou
glTranslatef(0.0, 0.0, -4.0); // provede se posun
glutSolidTeapot(1); // vykreslí se konvice (to je jeden ze tvarů, které má GLUT přednastavené)
glutSwapBuffers(); // prohodí se buffery (jeden se pošle na výstup a do druhého se začne znovu vykreslovat).
}
Tady je ještě jednou celý zdrojový kód dnešního dílu vcelku:
#include <gl/glut.h>
void onResize(int w, int h) {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glViewport(0, 0, w, h);
gluPerspective(45,(double)w/(double)h,1,100);
glMatrixMode(GL_MODELVIEW);
}
void init() {
glEnable(GL_DEPTH_TEST);
glClearColor(1.0,1.0,1.0,1.0);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
}
void onDisplay(void){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glLoadIdentity();
glTranslatef(0.0, 0.0, -4.0);
glutSolidTeapot(1);
glutSwapBuffers();
}
int main (int argc, char **argv) {
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(640,480);
glutInitWindowPosition(10,10);
glutCreateWindow("okno");
glutDisplayFunc(init);
glutIdleFunc(onDisplay);
glutReshapeFunc(onResize);
glutMainLoop();
return 0;
}
Závěr
Teď už zbývá jenom uložit to, co jsme dnes vytvořili, jako šablonu, která nám bude sloužit pro další používání. Proklikejte se k Soubor → Nový → Šablona
, zde si šablonu nějak pojmenujte, jako kategorii zadejte Multimedia
a vytvořte. Šablona bude k nalezení v Soubor → Nový → Projekt → Multimedia
.
To je pro dnešek vše. V příštím díle i povíme o vykreslování. Jaké jsou základní způsoby vykreslování v OGL, řekneme si něco o normálách, vertexech a základních transformacích. Taky se dozvíte, co to znamená, že OGL je stavový automat. V dalších dílech pak budou následovat základní transformace a vysvětlíme si, jak je to s maticemi. Pak přijdou světla, textury a spousta dalších šíleností, které už začnou být opravdu zajímavé. A pokud vám i mně vydrží trpělivost, vrhneme se posléze i na různé OGL extenze a programování shaderů a to už začne být opravdu, ale opravdu zajímavé a věřte mi, že se nad tím potrápíte nejenom vy, ale i vaše grafické karty :-).