A napokban ástam bele magam a címben említett témába. Sajnos ennek a neve megegyezik egy GPU architektúra nevével. Ebben a posztban a compute shaderrel megvalósított tile-based deferred rendering / shadingről lesz szó, amit mellesleg a Battlefield 3 (Frostbite 2 engine) is használ.
Ez a technika a deferred renderelés lighting szakaszát hivatott lecserélni. Ez a szakasz legtöbb esetben a következően szokott felépülni:
- fények frustum (és occlusion) cullja
- renderelés beállítása a lighting szakaszhoz
- fények renderelése egyesével / batchelve / instance-olva
- additív blendelés (adott esetben floating point rendertargettel)
Az első rész leginkább CPU-n végzendő műveleteket jelent (occlusion cull használata azonban igen költséges lehet).
Második részben a grafikus pipeline beállítása (blendelés, shaderek, shader változók stb.)
Harmadik részben sok-sok drawcall, shader váltások és beállítások, aminek a végeredménye blendelődik a rendertargettel.
Mint látható az egész procedúra sok GPU és driver műveletbe kerül, mint drawcallok, floating point blendelés / ROP, fill rate stb.
Ezeket azonban egy lépésbe össze lehet fogni compute shader segítségével - ezzel szinte ingyen kaphatunk occlusion cullt is (furcsa helyzet, hogy eddig CPU-n használt algoritmust írunk úgy, hogy hozzáférünk GPU erőforrásokhoz, mint pl. depth buffer)!
Az alapötlet az alábbi: a képernyőt felosztjuk N x M darab tile-ra, amikkel a compute shader fog dolgozni. Ez a shader fogja elvégezni a fenti 4 lépést egy lépésben:
- minden egyes tile-ra kiszámol egy-egy frustumot (4 vagy 6 sík)
- ezzel megvizsgálja az összes átadott fényt - ami akár több ezer is lehet - és kigyűjti azokat egy listába, amelyek potenciális hathatnak az adott tile-ba tartozó pixelekre
- majd ezen lista és a g-buffer felhasználásával elvégzi az árnyalást
- a végeredmény pedig legtöbbször egy textúrába kerül, amit már csak meg kell jeleníteni.
Azonban nincs végtelen kapacitásunk a compute shader használatakor sem. Az árnyaláson kívül a fény - frustum vágás algoritmusán lehet spórolni ( és persze a fények számán).
Ha az árnyalást már a nekünk tetsző formára alakítottuk ki és optimalizáltuk akkor megnézhetjük a fény - frustum vágást. Ebből nézzük a két leggyakoribb esetet a pontfényt és szpotfényt.
A pontfény esetén nincs túl sok választási lehetőségünk, de szerencsére ez az algoritmus nagyon egyszerű.
Bonyolultabb eset azonban a szpotfény. Ehhez négyféle algoritmussal próbálkoztam:
- gömb - frustum vágás hasonlóan a pontfényhez
- félgömb - frustum vágás
- szpotfény köré vont gömb - frustum vágás
- szpotfényt befoglaló négyzetalapú piramis - frustum vágás
És hogy miért is volt érdemes a 2 - 3- 4 esetekkel foglalkozni. A szpotfényt, mint egy gömbcikket közelíthetjük ugyan a teljes gömbbel, de a jóval nagyobb térfogat miatt nagyobb felületen fog lefutni az árnyalás. Emiatt tehát megpróbáljuk pontosabban közelíteni a szpotfény térfogatát.
Végeredmény pedig következik az alábbiakból.
Minél pontosabban illeszkedik a gömbcikk térfogatához az algoritmus annál több / bonyolultabb számolást igényel. Tapasztalatom szerint a 2. változat a befutó. A 3. és 4. esetében a szpotfény - frustum vágás már annyival "bonyolultabb", hogy olyan helyzetben éri csak meg a bonyolultabb algoritmus ha azzal sok árnyalandó felületet spórolunk meg. Mivel ez igen jelenet- ill. bevilágításfüggő végül a szpotfényeket vágásához a 2. változatot használom.
Megjegyzés:
A 2. változat megoldásához először először csak a frustum síkjait valamint a félgömb sugarát, középpontját és félbevágó síkot használtam fel emiatt nem igazán tudtam megoldást találni.
Jóval később esett csak le, hogy a frustum síkjait a frustum pontjaiból számolom ki vagyis a frustum pontjaira is tudok építeni.
Innentől viszont egyszerű az algoritmus: a gömb - frustum vágás után - amennyiben szükséges - már csak a sík - ponthalmaz vágás eredménye kell.