English Welcome Kecy Programování 3D - Engine Guestbook Odkazy Downloady O autorovi Napiš mi Mailform |
3D - Engine - OpenGL 01 - začínáme s OpenGL Tak a jsem tu znova. Omlouvám se za zpoždění, ale vlezla mi do toho jedna dokonalá slečna ... Teď už spolu (bohužel pro mně a naštěstí pro vás) nejsme, takže je tu další (první) díl o OpenGL! Dnes si vysvětlíme nějaké základní věci o OpenGL. Předně - OpenGL znamená open graphics library - otevřená grafická knihovna. Slouží jako rozhraní mezi programem a 3D-kartou (nebo referenčním (software) rasterizérem, pokud počítač nemá kompatibilní 3d-kartu) Hodně lidí se ptá, jestli je lepší OpenGL, nebo Direct-X. Já preferuji OpenGL kvůli jeho rozhraní. OGL má client-server rozhraní, kde nejsložitější typ je float. Direct-X mají hromadu struktur, jejichž jména se navíc s verzemi mění, což mi přijde dost špatné. No ale začneme, není na tom nic složitého. Budu počítat s tím, že jste si už přečetli předchozí seriál o software renderingu a 3d a vektorům dobře rozumíte ... Začneme s inicializací OpenGL:
Asi to vypadá hrozivě, že? Ale nebojte se! Tohle budete kopírovat z projektu do projektu, stejně jako kód pro vytvoření okna - je to pokaždé stejné. Ale teď k tomu co se tam děje ... Úplně první segment kódu volá API funkci ChangeDisplaySettings(), určenou pro přepínání módu našeho monitoru. Tím si změníme rozlišení, pokud chceme. Dál už přichází to zajímavé - nastavíme si pixelformat. Chceme po něm PFD_DRAW_TO_WINDOW (budeme kreslit do našeho okna), PFD_SUPPORT_OPENGL (chceme hardware podporu open-gl, pokud máme) a PFD_DOUBLEBUFFER (chceme double-buffer, tzn. obraz se kreslí do jednoho bufferu a druhý se zobrazuje. když je obraz hotový, buffery se přehodí (jenom pointery, nic se nekopíruje) - takže nevidíme jak se postupně kreslí věci, které po grafické kartě chceme). PFD_TYPE_RGBA znamená že budeme chtít pixely s RGB (red-green-blue barvou) a A (alfa-kanálem - o tom si povíme víc dál) Nastavíme si počet bitů na barvu, na depth buffer (z-buffer, "depth" = hloubka), akumulátor nechceme takže dáme nulu (akumulátor je speciální buffer, do kterého můžeme přičíst obsah bufferu, který kreslíme. je to dobré k tomu, že když máme scénu, osvětlenou více světly než zvládne karta (většinou 8) tak ji nakreslíme jen s několika světly a uložíme do akumulátoru. potom ji vykreslíme s jinými světly a znovu uložíme. takhle dosáhneme možnosti "nekonečného" počtu světel ve scéně) Stencil buffer taky nechceme. Stencil je buffer, do kterého se dají ukládat id polygonů a podobně. Většinou se používá na výpočet stínů (viz. seriál o software renderingu), ale dá se efektivně použít také na různé maskování, třeba při kreslení zrcadel, kdy chcete aby obraz v zrcadle byl jen po polygonech zrcadla. To jednoduše nakreslíme polygony zrcadla do stencil bufferu, nastavíme příslušnou kontrolu a můžeme kreslit obsah zrcadla. Ale o tom někdy jindy, zkusíme si se stencilem ještě pěkných pár triků ... Dál zavoláme několik API funkcí, obdržíme svoje kontexty, nastavíme pixel-formát a aktivujeme náš kontext. (pokud si uděláte víc kontextů pro víc oken, budete mezi nimi pořád přepínat podle toho, do kterého okna budete chtít kreslit) Zavoláme si glViewport(), kterému řekneme že chceme kreslit do celé plochy okna. Potom vynulujeme všechny matice, nastavíme perspektivní korekci a uložíme stavové proměnné. Potom se zavolá pár příkazů, kterým teď nerozumíme. Ale to přijde ... zatím pro kompletnost ještě kód jak to udělat, abysme OpenGL ukončili a kód jak překlopit buffer (technický slang, budeme to volat když budeme myslet že máme nakresleno a budeme chtít náš výtvor ukázat)
To by bylo. Nebudu to ani vysvětlovat, stejně to vždycky jen zkopírujete ... Jak už jsem říkal, OpenGL je client-server rozhraní. My jsme klient a grafická karta (její driver) je server. My máme skupinu funkcí, kterou server řídíme. Mezi ty základní patří:
To bylo pár funkcí. Můžeme třeba volat glGetString(GL_RENDERER), kdy vrácená hodnota bude odpovídat jménu grafické karty, která se právě používá. Dobré, že? Takhle můžeme zjistit verzi OpenGL:
Asi by bylo dobré říct, co OpenGL umí. Máme tu možnost kreslení různých geometrických tvarů, můžeme je různě barvit, texturovat a stínovat. Máme tu matice, kterými můžeme transformovat geometrii (GL_MODELVIEW matice) nebo souřadnice textur (GL_TEXTURE). Máme vestavěný Z-Buffer. Máme vestavěnou podporu světel. Máme vestavěné míchání barev (blending), takže to co kreslíme nemusí prostě přepsat to, co tam bylo předtím, ale může se k tomu třeba příčíst. Máme alpha-test (podobné jako z-buffer; testuje hodnotu složky A proti určité úrovni kterou můžete nastavit a pokud je [nižší/vyšší/stejná/jiná/nikdy/vždycky - můžete si vybrat] tak pixel zapíše) OpenGL taky má podporu pro kouř, a to v té podobě že karta pro každý vrchol buď spočítá hodnotu "zakouřenosti" v závislosti na parametrech kouře, nebo hodnoty zadáte sami a potom pro každý pixel podle interpolované hodnoty "zakouřenosti" v daném pixelu míchá mezi barvou pixelu a barvou kouře. Potom je tu ještě scissor - "nůžkový" test, kdy můžete nastavit část obrazovky do které budete kreslit jako horní a spodní roh - a nikam jinam se nekreslí. My chceme kreslit! ... mno dobře. OpenGL umí hlavně kreslit. Můžeme kreslit body, čáry, multičáry (čára, složená z více čar), smyčky (jako multičára, ale navíc vede ještě jedna čára od posledního bodu k prvnímu, takže smyčku uzavírá), trojůhelníky, řetězy trojůhelníků (triangle strips, užitečné, protože se hodně vrcholů použije dvakrát a karta jich tudíž nemusí tolik transformovat), "kytice" trojúhelníků (triangle fans), čtyřúhelníky (konvexní! jinak se budou kreslit podivně, stejně jako v našem minulém engine), řetězy čtyřúhelníků (quad strips) a polygony (n-úhelníky, zase jen konvexní). Pro body můžete nastavit velikost, pro čáry tloušťku (tlusté čáry DirectX neumí) a pro všechny plošné útvary (trojúhelníky / čtyřúhelníky / polygony) můžete nastavit, zda se budou kreslit jako vrcholy, jako čáry nebo s vyplněnou plochou. (je možnost nastavit to pro přední a pro zadní stranu polygonu zvlášť!, takže zepředu se můžou polygony kreslit plné a zezadu jako čáry) OpenGL samozřejmě má zabudovaný backface culling, můžete dokonce nastavit jestli máte vrcholy po směru hodinových ručiček, nebo proti směru! Kreslení probíhá tak, že zavoláte funkci glBegin(x), kde x je jedna z konstant, odpovídající tomu, co chcete kreslit. Potom nastavujete barvy (glColor{3,4}{b,s,i,f,d} - teď se asi divíte co to je za binec. Znamená to, že můžete zadat barvu o třech (RGB) nebo čtyřech (RGBA) souřadnicích a buď jako bajty, shorty, integery, floaty nebo doubly. My budeme používat floaty a většinou budeme nastavovat RGB barvu, takže budeme volat glColor3f(float r, float g, float b) - chytré, že?) Bílá se nastaví jako glColor3b(255, 255, 255), nebo glColor3s(65535, 65535, 65535) nebo glColor3i(4294901760, 4294901760, 4294901760) nebo glColor3f(1, 1, 1). OpenGL počítá s barvami jako s floaty, takže při tom zůstaneme a budeme zadávat floaty. Jednak je to pohodlné a jednak karta nemusí dělat konverze. Dál můžeme udat normálu (glNormal{3}{b,s,i,f,d}), souřadnice textury (glTexCoord{1,2,3,4}{s,i,f,d}), nebo souřadnice kouře (glFogCoord{f,d}). Nakonec zadáme pozici a tím vrchol pošleme kartě. Pozice se zadává funkcí (glVertex{2,3,4}{s,i,f,d}). Divíte se co je čtyřrozměný vertex? O tom si popovídáme jindy, jen nastíním že normálně je čtvrtá souřadnice 1. Je to stejná souřadnice, jaká je v maticích - pro první tři sloupce, jež symbolizují vektory - směry je nula a pro čtvrtý sloupec, kde je uložená pozice je to jedna. Pokud zadáte vrchol se souřadnicí w = 0 (tak se ona souřadnice jmenuje), bude to vrchol v nekonečnu. Zatím asi nevidíte praktické využití, ale opravdu tu je. Nekonečné souřadnice je jedna z věcí, které DirectX neumí. Naproti tomu pokud zadáváte 2D vertex, je to jako kdybyste zadali 3D vertex se z = 1. Teď už bysme skoro mohli kreslit, ale ... ještě souřadnice. OpenGL má souřadnicový systém, že když jsou všechny matice jednotkové, je uprostřed obrazovky souřadnice (2D) [0, 0], v levém spodním rohu je [-1,-1] a v pravém horním rohu je [1,1], takže osa y jde prostředkem obrazovky nahoru, x jde doprava a z kolmo k povrchu obrazovky, dovnitř. Když nastavujete perspektivní korekci, nastaví se klipovací pyramida, podobně jako v našem engine. Jenže tady má pyramida šest rovin, čtyři jako my po stranách obrazu a potom jednu blízko nule (ta uřezává polygony, které jsou moc blízko) a potom jednu daleko, která uřezává polygony, které jsou za určenou hranicí. My si určíme obě hranice, ve worldspace jednotkách a hodnoty, které se budou ukládat do z-bufferu budou v rozmezí 0 - 1. Mimo klipovací pyramidy máme ještě několik uživatelských klipovacích rovin, které můžeme vesele používat. No ale teď už se můžeme vrhnout na věc a něco si nakreslit ... Napřed budeme chtít nastavit matice. Pro matice máme tyhle funkce:
Tak. Pokud chceme kreslit 2D grafiku, prostě jen vynulujeme matici projekce a modelview matici a máme 2D souřadnice tak, jak jsme si říkali ... Můžeme to hned zkusít:
Vypadá to jednoduše, že? Je to jednoduché! Tenhle kousek kódu nakreslí trojúhelník s červeným, modrým a zeleným vrcholem. Pokud je vše nastavené správně, výsledek bude vypadat takhle: ... ale mluvil jsem o tom, že OpenGL umí hromadu různých zvěřáren. Tady je několik funkcí, ovlivňujících rasterizér:
Teď se ještě malinko rozepíšu o tom, jak se kreslí všechny ty zajímavé věci, které OpenGL umí kreslit.
Možná jste si všimli, že používám množná čísla - pokud kreslíte body, čáry nebo trojúhelníky, můžete jich mezi glBegin() a glEnd() nasypak kolik se vám zlíbí. Pokud kreslíte řetězy / smyčky / kytice / polygony, můžete nakreslit jen jeden řetěz / smyčku / kytici / polygon, potom musíte zavolat glEnd(), čímž řeknete že už toho víc nebude a až potom můžete pomocí glBegin() začít další útvar. Ještě si ukážeme v jakém pořadí se zadávají vrcholy pro řetězy troj/čtyř-úhelníků a pro kytice trojůhelníků:
No a teď už si můžete stáhnout jednoduchý sampl i se zdrojáky: (měl bych ještě říct, že abyste mohli zkompilovat předchozí kód, musíte přidat knihovnu opengl32.lib k seznamu knihoven pro linkování (project-settings-link) a includovat si <gl/gl.h>) OpenGL Engine 01 To by bylo ... Ale co by to bylo za seriál o 3D-enginech, kdybysme se nedostali ke 3D!? Ještě si napíšeme jeden examplesák, kde nastavíme matici projekce a budeme poletovat okolo nějakého objektu ... V Předchozím example jsme jen vynulovali matice. To ale na 3D nestačí. Musíme nastavit klipovací pyramidu. K tomu můžeme použít jednoduše gluPerspective(), kterému předáme zorný úhel, poměr stran obrazu a blízkou a dalekou klipovací rovinu (jen vzdálensoti). Ale protože kvůli téhle funkci musíte přilinkovat celou knihovnu glu32.lib a tahat sebou glu32.dll a inkludovat <gl/glu.h>, nevyplatí se to. Potom to dopadne že hra je na sedmi dvd a když se nainstluje, zabere na disku padesát giga (jak už to tak poslední dobou bývá :o)). Tenhle kousek kódu použijeme místo gluPerspective():
Máme tam několik konstant, udávající pí, blízkou a dalekou klipovací rovinu, zorný úhel a poměr stran. Na začátku si nastavíme projekční matici jako aktivní a vynulujeme si ji na jednotkovou. Potom spočítáme polovinu vzdálenosti protilehlých klipovacích rovin v blízké rovině (rovina, rovnoběžná s x-y rovinou = s obrazovkou a vzdálená f_z_near) jednak vertikálně a podle poměru stran odvodíme i horizontálně. Potom už to jen nasypeme do glFrustum(). OpenGL nabízí ještě ortogonální projekci, kód je úplně stejný, jen glFrustum() zaměníte za glOrtho(). Tahle projekce není "pyramidová", ale "krychlová", použijete ji když chcete napsat okno jako má třeba 3ds-max, tedy pohled z určité roviny. Jinak pokud jste si všimli, kód je náramně poobný tomu co máme ve staré klipovací pyramidě v software-rendering enginu. Protože to určitě nechcete hledat, přikládám pro nahlédnutí: (pro ty zmatené - následující kód nikam nepatří, je tu jen pro srovnání)
Teď už máme perspektivu a klipovací pyramidu. Ale ještě odnikud nekoukáme. Tím že nastavíme mód matice na modelview a vynulujeme ji, docílíme toho že koukáme z [0 0 0] směrem po z+ (po vektoru [0 0 1]). Buďto můžeme invertovat svoji matici kamery a nahrát ji pomocí glLoadMatrixf(), nebo můžeme použít glTranslatef() a glRotatef() a pohled někam našoupat, nebo můžeme zavolat gluLookAt() a nechat si matici spočítat z pozice kamery, cíle pohledu a vektoru nahoru, jak už jsme to dělali nesčetněkrát v předchozím seriálu. Dnes budeme zobrazovat jen rotující bramboru, takže bude stačit poposunout se kousek od ní - pomocí glTranslatef(). Teď si tak ještě říkám - co takhle užít si HW podporu z-bufferu? Z-buffer se povoluje voláním glEnable(GL_DEPTH_TEST). Z-buffer má možnost nastavení jak porovnávat. To je pomocí void glDepthFunc(enum func) kde func může být GL_ALWAYS (kresli vždycky - stejné jako když máte z-buffer vypnutý), GL_NEVER (nekresli nic - použití se naskytne později u stencilu), GL_LEQUAL (kresli když je z menší nebo stejné jako hodnota v z-bufferu - výchozí nastavení), GL_LESS (kresli když je z menší, než v bufferu), GL_GEQUAL (větší nebo stejné), GL_GREATER (větší), GL_EQUAL (jen když je stejné - použitelné na fleky po zdech), GL_NOTEQUAL (když není stejné - nepoužitelné :o)). Z-buffer musíme taky vyčistit, to se dělá pomocí glClear(GL_DEPTH_BUFFER_BIT). Můžeme taky vypnout zapisování do z-bufferu (hodí se pro průhledné povrchy) pomocí glDepthMask(bool write). Takže jak to bude vypadat teď?
Na začátku uděláme pár operací a spočítáme si čas, aby nám brambora rotovala pěkně plynule. Potom smažeme buffery, nastavili jsme si příjemné bílé pozadí ... Potom spočítáme matici projekce a poodsuneme se 35 jednotek od bodu [0 0 0] dozadu, takže na bramboru pěkně uvidíme. Potom přepneme na modelview, narotujeme matici brambory, zapneme backface culling a z-buffer, nakreslíme bramboru a překlopíme buffery. Asi čekáte že budeme rotovat kostkou jako u všech seriálů o OpenGL. To byste podcenili swini! Budeme rotovat jakýmsi 3D fraktálem co o něm teď nevím jak se jmenuje ... dost možná je to něčí "houba", ale nejsem si jistej. (stejně - je už na první pohled vidět, že programátoři pečují o svoje zdraví a jsou vyloženě vysazení na stravu s vysokým obsahem zeleniny - a pak že se nestarají o svůj zevnějšek !! :o)) Kód je dost jednoduchej a už byste měli být po přečtení předchozího seriálu ve 3D dost zběhlí, takže ho nebudu vysvětlovat. Kreslíme jednak trojúhelníky a jednak trojúhelníky nastavené na kreslení hran, takže máme hrany pěkně vytažené. Stahujte druhý example, mějte se hezky a někdy se zase sejdeme u OpenGL: OpenGL brambíro-rotátor 02 -tHE SWINe-
|