Journal of Applied Multimedia 1./VIII./2013 HU
HARDVERESEN GYORSÍTOTT 3D/2D RENDERELÉS Zentai Norbert Zsolt - Ágnecz Gergely - Takács Roland - Kaczur Sándor Gábor Dénes Főiskola
[email protected],
[email protected],
[email protected],
[email protected]
Absztrakt - A cikk előadás fő témája a hardveresen gyorsított 3D/2D renderelés, beleértve a korai technikákat is. Megmutatjuk, hogy mi a grafikai könyvtár (például DirectX, OpenGL), valamint mire alkalmasak/használhatók és mire nem. Ismertetjük a működés alapelveit, kezdve a fixedpipeline funkcióin és korlátozásain keresztül a programmable pipeline-ig, hogyan működnek a shaderek és hogyan hajtódnak végre alacsony szinten. Kitérünk több problémára (például átlátszóság, tükröződés, árnyékok, nem tömör objektumok (felhő, füst) renderelése), és mutatunk bizonyos trükköket arra, hogyan lehet egyszerű 2D-s felületen a mélység látszatát kelteni a normál mapping és a parallax mapping segítségével. Végül rátérünk az utómunkára (például blur, bloom), valamint túllépünk a hardveres limitáción a deferred shading használatával.
elavultak és a régebbi, gyengébb hardverekre lettek tervezve. A projekt egy egyszerű tanulási vágyból indult és kinőtte magát egy modern shader alapú render motorrá. Ezt a motort NZEngine-nek hívják (fejlesztette: Zentai Norbert Zsolt) és a Gábor Dénes Főiskola 2009-es TDK versenyén 1. helyezést ért el [1, 5]. Tanáraink ajánlásának köszönhetően részt vehettünk az ERASMUS Life Long Learning program 2012 tavaszán megrendezett Intenzív Programján [6] Finnországban. Három diák közösen előadást tartott, hogy működik a renderelés, és hogy mik azok a gyakori trükkök, amikkel különböző effekteket érhetünk el kezdve az egyszerű átlátszóságtól, és hogy hogyan láthat a szem a világosabbnál is világosabb képrészletet a monitoron. Ez a cikk a prezentációnk anyagát tartalmazza. Reméljük sikerül bemutatnunk a hardveresen gyorsított valós idejű renderelést. Zentai Norbert Zsolt jelenleg a diplomamunkáját írja, ami a renderelő motorjáról szól.
Kulcsszavak: multimédia, számítógépes grafika, renderelés I. BEVEZETŐ Kezdjük a rendereléssel, ami a végső kép létrehozását jelenti, amely megjelenik a monitoron. A renderelésnek komoly szabályai vannak. Érdemes megfontolni, milyen minőségű képet szeretnénk, mivel minél jobb minőséget szeretnénk, annál tovább fog tartani a renderelés! Nem említjük a jó minőségű renderelést, ami az úgynevezett render farmokon folyik, hanem inkább az igazán gyors, játékprogramok által használt valós idejű renderelést ismertetjük. A renderelés folyamatosan fejlődik. Ahogy egyre erősebb hardverek állnak rendelkezésünkre, egyre jobb grafikát, vizuális élményt várunk. Újabbnál újabb trükköt hozunk létre a kívánt effektekhez és elvetjük a régieket, amelyek már
II. KORAI TECHNIKÁK Ezek régen voltak használatosak. Akkoriban előre lerenderelt képeket használtak és ezeket tették egymásra. Mivel már korábban lerenderelték a képeket, így nem volt szükséges az adott pillanatban renderelni. Remek példa erre egy 1992-es játék, a Wolfenstein 3D (1. ábra) [7]. A fal, a föld és minden más tárgy egy-egy külön kép.
33
Journal of Applied Multimedia 1./VIII./2013 HU Az első input lehetőséget uniformnak hívják. Ezek konstans értékek a shader futása alatt, amik futtatások között változtathatóak. Egy tipikus példa erre a fény pozíciója, a modell mátrix, a projekciós mátrix. A második inputot attributenak nevezzük. Ezek, akárcsak a uniformok, konstans értékek, de minden csúcsra más értéket adnak meg a futás alatt egy érték helyett. Egy tipikus attribute a pixel pozíciója vagy a textúra koordinátája, a normálvektor. A legfontosabb kimenete a vertex shadernek az adott csúcspont pozíciója. Ez a pozíció összeszorzódik a viewportban a méretaránynak megfelelően, és ez lesz az adott csúcspont pozíciója a monitoron. A második kimenete ennek a shadernek a varying. Ez a fragment shader bemenete, később részletezzük.
1. ábra Wolfenstein 3D 1992
III. GRAFIKUS KÖNYVTÁRAK A két legismertebb az OpenGL [8] és a DirectX [9]. Ezek szabványos interfészek, nem renderelő motorok! Ezek az interfészek tartalmazzák a szabályokat, amik alapján létrehozhatunk objektumokat és az alap funkciókat is hozzájuk. IV. FIX FUNKCIÓK A fix funkciók esetén minden bele volt égetve a videókártya áramköreibe. Limitált funkcionalitással és fix változókkal rendelkeztünk ezzel a technikával. Így nem hozhattunk létre saját változót, és nem írhattunk saját algoritmust sem. Az emberek már akkoriban is szerettek volna többet, így megjelent az igény a fejlődésre és idővel megjelent a programozható pipeline [10]. Ezzel lehetőségünk nyílt saját változók és algoritmusok létrehozására. Minden pipeline-nak saját matematikája van. Alapját tekintve, a pipeline egy hardver, amin szoftver fut, ami nem más, mint a shader. V. PROGRAMOZHATÓ PIPELINE (SHADER) Kétfajta shader létezik: a vertex shader és a fragment shader. Mindkettőre szükség van egy programhoz, ami futni tud a GPU-n. A shader egy algoritmus, amit le kell programozni [2]. Nem egy jelölőnégyzet csoport, ahol ki tudjuk választani, milyen effekteket szeretnénk. Emlékezzünk vissza, saját változókat akartunk, hogy ne függjünk a hardveres implementációktól (2. ábra). Az első shader a vertex shader [11]. Ez minden csúcspontra lefut. A csúcsokra ellenőrző pontokként gondolhatunk. A shaderek rendelkeznek bemenettel, ami meghatározza viselkedésüket.
2. ábra A shaderek forráskódjai
A fragment shader egy olyan shader, ami minden pixelre lefut [11]. Ezek a pixelek a háromszög lapjáról származnak, amelyet három vertex shader állít elő.
34
Journal of Applied Multimedia 1./VIII./2013 HU mélység puffer. Nem láthatjuk az objektumokat teljes egészükben, mert eltakarják egymást. Például az épületek is eltakarják egymást, vagy a fa kitakar egy kisebb részt a mögötte lévő épületből. A mélység puffer a pixelek mélység értékét tárolja, tehát azt, hogy egy objektum milyen messze van a nézőponttól. Mikor egy objektum renderelés alatt áll, minden generált pixel mélysége (z koordináta) eltárolódik a mélység pufferben. Ha a jelenet egy másik objektumának pixelét is ugyanabba a pixelbe kellene lerenderelni, akkor egy metódus összehasonlítja a két mélység értéket, és a nézőponthoz közelebbit választja. Ezután az új mélység értéket elmentjük a mélység pufferben felülírva a régi értéket, és természetesen a pixel színe is frissítésre kerül. Fontos, hogy egy pixel csak akkor lesz lerenderelve, ha teljesíti a mélység tesztet. Összefoglalásképpen kijelenthetjük, hogy a mélység puffer segítségével eldönthetjük, hogy egy jelenet mely objektumai láthatóak és melyek nem.
3. ábra A varyingek tesztelése
Erősen optimalizálni kell, mivel ez a shader minden emittált pixelre végrehajtódik. Ezen shader bemenetei a uniformok és a varyingek. A uniform megegyezik a vertex shader uniformjával, a varyingek viszont érdekesek. A változó értékét az adott hely interpolált pozíciója adja meg, amelyet a három vertex shader futása eredményez (3. ábra). Ennek a shadernek a kimenete az adott pixel színe. Erre a kontroll pont rendszerre szükségünk van a GPU működése miatt. A GPU egy párhuzamos processzor, ami több száz szálat futtat egyszerre párhuzamosan. Így felmerülhet a szálak szinkronizációs problémája, amit viszont ezzel a kontroll pont vezérelt felület technikával el tudunk kerülni, mialatt a nagy teljesítményt is fenntartjuk. VI. BUFFERS A puffereknek három típusáról beszélhetünk: pixel puffer, mélység puffer (z-puffer), valamint a stencil puffer. Minden puffer egy értéket tárol minden pixelre. A pixel puffer felelős a pixelek színéért, tehát ezt láthatjuk a képernyőn. A mélység puffer a pixelek mélység értékét tárolja. A mélység értékkel meghatározhatjuk, hogy egy pixel milyen messze van a megfigyelőtől. A stencil puffer egy extra puffer, amelyet a saját céljainkra használhatunk. Ha egy pixel teljesíti az általunk megadott feltételt, akkor le lesz renderelve és kirajzolódik a képernyőre [3, 12]. Az alábbi képen (4. ábra) látható néhány épület, lámpa és más különböző objektumok. Minél messzebb van egy objektum – amely pixelek halmaza – a nézőponttól, annál nagyobb a mélység érték. Ez a kép szemlélteti, hogy mire is jó a
4. ábra A mélység puffer vizualizációja
Nézzük meg részletesebben a stencil puffert is, amely egy extra puffer. A mélység pufferhez hasonlóan ez is korlátozza a renderelni kívánt területet. A stencil pufferben megadhatunk egy újabb feltételt, amelyet szintén teljesítenie kell minden pixelnek. Tulajdonképpen a stencil puffert a saját céljainkra használhatjuk, és csak az általunk kívánt részek lesznek láthatóak. Ebben az esetben egy pixel csak akkor lesz lerenderelve, ha teljesíti a mélység tesztet, valamint szintén teljesíti a stencil pufferben lévő feltételt is. Egy pixel teljesítheti vagy elbukhatja a mélység tesztet és a stencil feltételt. Ezen esetek minden kombinációjára meghatározhatjuk, hogy mi
35
Journal of Applied Multimedia 1./VIII./2013 HU történjen a stencil pufferben lévő értékkel. Megtarthatjuk az eredeti értéket, kicserélhetjük egy másik számmal, növelhetjük, csökkenthetjük és invertálhatjuk is. A mélység és stencil puffer közös használata számos effektet lehetővé tesz, mint például a tükröződést.
Azonban ez a módszer nagyon drága lenne valós idejű renderelés esetében, ezért csalnunk kell egy kicsit. Síkbeli tükröződés esetében az objektumot fejjel lefele tükrözzük, ahogy ezt az ábra is mutatja (5/a ábra). Viszont ha ilyen egyszerűen tükröznénk, akkor az objektum kilógna a tükröződő sík területéből. Ebben az esetben segítségül hívjuk a stencil puffert, és megadjuk azt a feltételt, hogy csak azok a pixelek rajzolódjanak ki a képernyőre, amelyeket a tükröződő sík a mélység teszt teljesítésével egyes értékűre állított (5/b ábra).
VII. TÜKRÖZŐDÉS A tükröződést ray trace renderer (sugárkövető renderelő) segítségével valósítjuk meg. A ray trace (sugárkövetés) azt jelenti, hogy követünk egy fénysugarat a szemünkből egy tükröződő felületig, például egy tükörig vagy egy fényes felületig, majd kiszámoljuk, hogy honnan pattan vissza és folytatjuk a folyamatot mindaddig, amíg el nem érünk egy nem tükröződő felületet [13].
VIII. ÁRNYÉK LEKÉPEZÉSE Ha a fény szemszögéből látnánk egy jelenetet, akkor minden objektumot világosnak (megvilágítottnak) látnánk. Azonban minden ezek mögött az objektumok mögött árnyékban lenne. Ez az az alapelv, amelyet a shadow map (árnyék térkép) elkészítéséhez használhatunk [14].
6/a-b-c. ábra Shadow mapping
5/a-b. ábra Stencil tükröződés
36
Journal of Applied Multimedia 1./VIII./2013 HU Tehát lerendereljük a jelenetet a fény szemszögéből, és eltároljuk minden pixel mélység értékét. Ha a mélység puffert textúrává konvertáljuk, ami csupán egyetlen kép, akkor megkapjuk a shadow mapet (6/a ábra). Ezután a normál kép, amelyet a képernyőn is látunk, úgy lesz lerenderelve, hogy minden pixel mélység értékét összehasonlítjuk a shadow mapben lévő értékkel [3]. Ha egy pixel távolsága nagyobb a fénytől, mint a shadow mapben tárolt érték, akkor a pixel távolabb van a fénytől, mint a fény által elsőnek megvilágított objektum, tehát a pixel árnyékban van (6/b ábra). Ha egy pixel mélység értéke kisebb, vagy egyenlő, mint a shadow mapben tárolt érték, akkor a pixel megvilágított lesz (6/c ábra). IX. ÁTLÁTSZÓSÁG
X. NEM TÖMÖR OBJEKTUMOK Nem tömör objektumok például a köd, az árnyék, a felhők. Ezek átlátszó képek. Úgynevezett sprite-okból hozzuk létre őket – amik mindig a kamera felé néző 2D képek – egyesítéssel. (8. ábra) [17].
Az átlátszóság egy új szín létrehozása egy meglévő színnel és egy másikkal, amit ahhoz hozzáadunk. Az informatikában ezt blendingnek nevezzük, míg a legtöbb ember színkeverésként ismeri [4, 15, 16]. Helyes renderelési sorrenddel kell dolgoznunk (7/a ábra), vagy hibás lesz az eredmény (7/b ábra).
8. ábra Egy egyszerű füst effekt
XI. NORMÁL MAPPING A normál mapping során fénnyel csalunk a részleteken. Ehhez normálvektorokat használunk. A normálvektorok segítségével meghatározzuk a fény intenzitását egy adott pontban. Bökdösve a normálvektorokat, érdes felületet fogunk látni, pedig maga a felület sík [3, 18, 19].
7/a-b. ábra Blending sorrend
37
Journal of Applied Multimedia 1./VIII./2013 HU
10. ábra Parallax mapping
Ezen a képen a fal valósnak/realisztikusnak tűnik (10. ábra), jó mélységgel, de ha egy extrém szögből néznénk, akkor látható lenne, hogy a kép csupán egy síkbeli kép. XIII. UTÓFELDOLGOZÁS
Azonban ha szabályozni akarjuk a felület érdességét, akkor valahogyan tárolnunk kell ezeket a vektorokat. Egy szokásos kép három csatornából áll: pirosból, zöldből és kékből. Ezeket használjuk a színek megjelenítésére, de ugyancsak használjuk bármi más tárolására is. Ha a pirosra, zöldre és kékre nem színként gondolunk, akkor nagyon egyszerűvé válik. Az X, Y és Z koordinátákat a három csatornaként használjuk: RGBXYZ (9. ábra).
Az utómunka célja, hogy a végleges képet minél jobbá tegyük, vagy olyan effektet készítsünk, ami túl drága lenne. Például homályosíthatjuk a képet, rosszullét érzését kelthetjük, válthatunk színeket, vagy elvégezhetünk szín korrekciókat. Ez hasznos lehet egy naplementés jelenetnél, mert egy igazán szép meleg narancs árnyalatot ad a képnek. Az utómunka a jelenet textúrába való renderelésével történik, amely lényegében egy kép extra adatokkal a GPU számára. A pixelekkel való varázslat után lerendereljük a textúrát teljes képernyős téglalapot alkalmazva [3, 4]. Textúrákat használunk, mert sokkal rugalmasabban lehet velük dolgozni, mint a csúcs vagy más adatokkal.
XII. PARALLAX MAPPING
XIV. BLOOM
A parallax mappinget az élethűbb mélységérzet eléréséhez használjuk. Ez jóval lassabb, mint a normál mapping. Használata a textúrák mozgatásán alapszik [19]. Ezekkel lefedhetjük a textúra részeit, és csak azt fogjuk látni, amit látni is kell.
A bloom egy nagyon kedvelt utómunka effekt. Néhányan HDR renderelésnek hívják, de technikailag ez az inverze annak. Ez az effekt a kép világos részeit kifuttatja a kép világos részeinek határain kívülre, túl világossá téve azt. Ugyanezt láthatjuk, ha a Napba nézünk. A Nap körvonalai helyett valamiféle ragyogó dolgot látunk. Ha a szemünk látja az általunk készített képet, akkor azonnal azt fogja feltételezni, hogy a kép nagyon világos és elkezdi egyre sötétebbé tenni azt, de
9. ábra Síkból normál mapping
38
Journal of Applied Multimedia 1./VIII./2013 HU nem képes eltávolítani a túlcsorduló fényt, mert ez csak egy kép [20]. Először itt is egy textúrába rendereljük a képet, mint mindig (11. ábra).
Végül kombinálhatjuk az eredeti képet a homályosított képpel, és már meg is kapjuk a bloom effektet. Az utolsó részben hozzáadhatunk még néhány effektet, mint például az árnyalat, és így nem pazaroljuk az időt arra, hogy ezt egy másik textúrába rendereljük (14. ábra).
11. ábra Bloom: kiinduló pont 14. ábra Bloom: végső állapot
Ezután levágjuk a kép „nem túl világos” részeit. Ezt egyszerű matematikával megtehetjük. Miután levágtuk ezeket a részeket, megkapjuk a kép igazán világos részeit és minden más fekete (12. ábra).
XV. ÖSSZEFOGLALÁS A projektmunka kiváló lehetőséget adott arra, hogy a 3D/2D renderelés elméleti hátterét megismerjük, ez gyakorlati alkalmazáshoz, grafikai algoritmusok értelmezéséhez, implementációjához nélkülözhetetlen. Bízunk abban, hogy sikerült jól összefoglalni a szakterület lehetőségeit és a megvalósítás tapasztalatait. Zentai Norbert Zsolt tervezi, hogy publikálja NZEngine nevű multiplatformos 3D motorját. A motor nagyon gyors, kiváló minőségű képek, videók előállítására/renderelésére képes. A cikkhez tartozó képek és bemutató videó letölthető [21].
12. ábra Bloom: világos részek
HIVATKOZÁSOK
Homályosítjuk a képet, ami „túlcsordult” fényt fog biztosítani (13. ábra).
[1] [2] [3]
[4] 13. ábra Bloom: túlcsordulás
39
Zentai, N. Zs. (2011): Multiplatformos 3D motor – NZEngine, OTDK dolgozat Budai, A., Vári Kakas, I. (2007): Számítógépes grafika, Inok Kft., Budapest, ISBN 963 9625 32 7 Munshi, A., Ginsburg, D., Shreiner, D. (2008): OpenGL® ES 2.0 Programming Guide, http://my.safaribooksonline.com/book/pro gramming/opengl/9780321563835, ISBN 978-0-321-56383-5 Apple Inc. (2011): OpenGL ES Programming Guide for iOS, https://developer.apple.com/library/ios/do
Journal of Applied Multimedia 1./VIII./2013 HU
[5]
[6]
[7] [8]
[9] [10] [11] [12]
cumentation/3DDrawing/Conceptual/ OpenGLES_ProgrammingGuide/OpenGL ES_ProgrammingGuide.pdf GDF honlap, TDK szekció: http://www.gdf.hu/tudomanyoselet/tudomanyos-diakkor-tdk/tdkkonferenciak/2010 ERASMUS Life Long Learning Intensive Programme, Virrat, Finland: http://ec.europa.eu/education/erasmus/ip_ en.htm Wolfenstein 3D: http://www.idsoftware.com/games/wolfen stein/wolf3d OpenGL tutorial: http://www.khronos.org/opengles/sdk/doc s/man, http://www.opengl.org/sdk/docs/tutorials DirextX tutorial: http://msdn.microsoft.com/enus/directx/aa937781.aspx OpenGL ES 1.1 Reference Pages: http://www.khronos.org/opengles/sdk/1.1/ docs/man OpenGL Shading Language: http://www.opengl.org/registry/doc/GLSL angSpec.4.20.8.clean.pdf Vector vs. Raster Displays: http://www.cs.uic.edu/~jbell/CourseNotes /ComputerGraphics/VectorVsRaster.html
[13]
[14] [15]
[16] [17]
[18] [19]
[20] [21]
40
Rendering Fast reflections with OpenGL: http://titan.cs.ukzn.ac.za/opengl/opengld2/lectures/Rendering%20Fast%20reflecti ons%20with%20OpenGL.htm Shadow mapping: http://en.wikipedia.org/wiki/Shadow_map ping Transparency, Translucency, and Using Blending: http://www.opengl.org/archives/resources/ faq/technical/transparency.htm Transparency Sorting: http://www.opengl.org/wiki/Transparency _Sorting Jack's Secret Stash - Tutorial: 2D Smoke Sprites mit Standardpartikeln: http://www.c4djack.de/html/tutorials/tut_smokesprites/index.html NormalMap – Polycount Wiki: http://wiki.polycount.com/NormalMap OpenGL Shading Language, Course, Chapter 4 – Advanced Shaders: http://www.opengl.org/sdk/docs/tutorials/ TyphoonLabs/Chapter_4.pdf Gamasutra – Features – Real-Time Glow: http://www.gamasutra.com/view/feature/1 30520/realtime_glow.php Forrásanyagok: http://zenorbi.com/MMO2012