Naučite osnovne koncepte i napredne tehnike renderiranja sjena u stvarnom vremenu u WebGL-u. Ovaj vodič pokriva mapiranje sjena, PCF, CSM i rješenja za uobičajene artefakte.
WebGL mapiranje sjena: Sveobuhvatan vodič za renderiranje u stvarnom vremenu
U svijetu 3D računalne grafike, malo elemenata doprinosi realizmu i uranjanju više od sjena. One pružaju ključne vizualne znakove o prostornim odnosima između objekata, lokaciji izvora svjetlosti i ukupnoj geometriji scene. Bez sjena, 3D svjetovi mogu djelovati ravno, odvojeno i umjetno. Za web-bazirane 3D aplikacije pokretane WebGL-om, implementacija visokokvalitetnih sjena u stvarnom vremenu je obilježje iskustava profesionalne razine. Ovaj vodič pruža duboki uron u najosnovniju i najčešće korištenu tehniku za postizanje ovoga: Mapiranje sjena.
Bilo da ste iskusni programer grafike ili web programer koji se upušta u treću dimenziju, ovaj će vas članak opremiti znanjem za razumijevanje, implementaciju i rješavanje problema sa sjenama u stvarnom vremenu u vašim WebGL projektima. Putovat ćemo od temeljne teorije do praktičnih detalja implementacije, istražujući uobičajene zamke i napredne tehnike korištene u modernim grafičkim mehanizmima.
Poglavlje 1: Osnove mapiranja sjena
U svojoj srži, mapiranje sjena je pametna i elegantna tehnika koja određuje je li točka u sceni u sjeni postavljanjem jednostavnog pitanja: "Može li izvor svjetlosti vidjeti ovu točku?" Ako je odgovor ne, to znači da nešto blokira svjetlost, a točka mora biti u sjeni. Da bismo programski odgovorili na ovo pitanje, koristimo pristup renderiranja u dva prolaza.
Što je mapiranje sjena? Osnovni koncept
Cijela tehnika se vrti oko renderiranja scene dva puta, svaki put iz različite perspektive:
- 1. prolaz: Prolaz dubine (Perspektiva svjetla). Prvo, renderiramo cijelu scenu iz točnog položaja i orijentacije izvora svjetlosti. Međutim, u ovom prolazu ne marimo za boje ili teksture. Jedina informacija koja nam je potrebna je dubina. Za svaki renderirani objekt bilježimo njegovu udaljenost od izvora svjetlosti. Ova zbirka vrijednosti dubine pohranjena je u posebnoj teksturi koja se naziva mapa sjena ili mapa dubine. Svaki piksel u ovoj mapi predstavlja udaljenost do najbližeg objekta iz perspektive svjetla u određenom smjeru.
- 2. prolaz: Prolaz scene (Perspektiva kamere). Zatim, renderiramo scenu kao što bismo to inače činili, iz perspektive glavne kamere. Ali za svaki pojedini piksel koji se crta, vršimo dodatni izračun. Određujemo položaj tog piksela u 3D prostoru, a zatim pitamo: "Koliko je ova točka udaljena od izvora svjetlosti?" Zatim uspoređujemo tu udaljenost s vrijednošću pohranjenom u našoj mapi sjena (iz 1. prolaza) na odgovarajućoj lokaciji.
Logika je jednostavna:
- Ako je trenutna udaljenost piksela od svjetla veća od udaljenosti pohranjene u mapi sjena, to znači da postoji drugi objekt bliže svjetlu duž iste linije vida. Stoga je trenutni piksel u sjeni.
- Ako je udaljenost piksela manja ili jednaka udaljenosti u mapi sjena, to znači da ga ništa ne blokira, a piksel je potpuno osvijetljen.
Postavljanje scene
Da biste implementirali mapiranje sjena u WebGL-u, trebate nekoliko ključnih komponenti:
- Izvor svjetlosti: To može biti usmjereno svjetlo (poput sunca), točkasto svjetlo (poput žarulje) ili reflektor. Vrsta svjetla će odrediti vrstu matrice projekcije koja se koristi tijekom prolaza dubine.
- Objekt okvira (FBO): WebGL se normalno renderira na zadani okvir zaslona. Da bismo stvorili našu mapu sjena, trebamo cilj renderiranja izvan zaslona. FBO nam omogućuje renderiranje u teksturu umjesto na zaslon. Naš FBO će biti konfiguriran s prilogom teksture dubine.
- Dva skupa shadera: Trebat će vam jedan program shadera za prolaz dubine (vrlo jednostavan) i drugi za konačni prolaz scene (koji će sadržavati logiku izračuna sjene).
- Matrice: Trebat će vam standardne matrice modela, pogleda i projekcije za kameru. Ključno je da će vam trebati i matrica pogleda i projekcije za izvor svjetlosti, često kombinirane u jednu "matricu svjetlosnog prostora".
Poglavlje 2: Rendering Pipeline u dva prolaza detaljno
Razmotrimo korak po korak dva prolaza renderiranja, fokusirajući se na uloge matrica i shadera.
1. prolaz: Prolaz dubine (Iz perspektive svjetla)
Cilj ovog prolaza je popuniti našu teksturu dubine. Evo kako to funkcionira:
- Vezati FBO: Prije crtanja, upućujete WebGL da renderira na vaš prilagođeni FBO umjesto na platno.
- Konfigurirati viewport: Postavite dimenzije viewporta tako da odgovaraju veličini vaše teksture mape sjena (npr. 1024x1024 piksela).
- Očistiti međuspremnik dubine: Osigurajte da je međuspremnik dubine FBO-a očišćen prije renderiranja.
- Stvoriti matrice svjetla:
- Matrica pogleda svjetla: Ova matrica transformira svijet u perspektivu svjetla. Za usmjereno svjetlo, ovo se obično stvara pomoću funkcije `lookAt`, gdje je "oko" položaj svjetla, a "cilj" smjer u kojem je usmjereno.
- Matrica projekcije svjetla: Za usmjereno svjetlo, koje ima paralelne zrake, koristi se ortografska projekcija. Za točkasta svjetla ili reflektore koristi se perspektivna projekcija. Ova matrica definira volumen u prostoru (kutiju ili frustum) koji će bacati sjene.
- Koristiti program shadera dubine: Ovo je minimalni shader. Jedina svrha vertex shadera je množenje položaja vertexa s pogledom i matricama projekcije svjetla. Fragment shader je još jednostavniji: samo upisuje vrijednost dubine fragmenta (njegovu z-koordinatu) u teksturu dubine. U modernom WebGL-u često vam čak ne treba prilagođeni fragment shader, jer se FBO može konfigurirati za automatsko snimanje međuspremnika dubine.
- Renderirati scenu: Nacrtajte sve objekte koji bacaju sjene u vašoj sceni. FBO sada sadrži našu dovršenu mapu sjena.
2. prolaz: Prolaz scene (Iz perspektive kamere)
Sada renderiramo konačnu sliku, koristeći mapu sjena koju smo upravo stvorili da bismo odredili sjene.
- Odvezati FBO: Vratite se na renderiranje u zadani okvir platna.
- Konfigurirati viewport: Vratite viewport na dimenzije platna.
- Očistiti zaslon: Očistite međuspremnike boje i dubine platna.
- Koristiti program shadera scene: Ovdje se događa magija. Ovaj shader je složeniji.
- Vertex Shader: Ovaj shader mora učiniti dvije stvari. Prvo, izračunava konačni položaj vertexa koristeći uobičajene matrice modela, pogleda i projekcije kamere. Drugo, mora također izračunati položaj vertexa iz perspektive svjetla pomoću matrice svjetlosnog prostora iz 1. prolaza. Ova druga koordinata se prosljeđuje fragment shaderu kao varijabla.
- Fragment Shader: Ovo je srž logike sjene. Za svaki fragment:
- Primite interpolirani položaj u svjetlosnom prostoru iz vertex shadera.
- Izvršite perspektivnu podjelu na ovu koordinatu (podijelite x, y, z s w). To ga transformira u Normalizirane koordinate uređaja (NDC), u rasponu od -1 do 1.
- Transformirajte NDC u koordinate teksture (koje se kreću od 0 do 1) kako bismo mogli uzorkovati našu mapu sjena. Ovo je jednostavna operacija skaliranja i predrasuda: `texCoord = ndc * 0.5 + 0.5;`.
- Koristite ove koordinate teksture za uzorkovanje teksture mape sjena stvorene u 1. prolazu. To nam daje `depthFromShadowMap`.
- Trenutna dubina fragmenta iz perspektive svjetla je njegova z-komponenta iz transformirane koordinate svjetlosnog prostora. Nazovimo to `currentDepth`.
- Usporedite dubine: Ako je `currentDepth > depthFromShadowMap`, fragment je u sjeni. Morat ćemo dodati malu predrasudu ovoj provjeri kako bismo izbjegli artefakt koji se naziva "akne sjene", o čemu ćemo razgovarati sljedeće.
- Na temelju usporedbe, odredite faktor sjene (npr. 1.0 za osvijetljeno, 0.3 za osjenčano).
- Primijenite ovaj faktor sjene na konačni izračun boje (npr. pomnožite ambijentalne i difuzne komponente osvjetljenja s faktorom sjene).
- Renderirati scenu: Nacrtajte sve objekte u sceni.
Poglavlje 3: Uobičajeni problemi i rješenja
Implementacija osnovnog mapiranja sjena brzo će otkriti nekoliko uobičajenih vizualnih artefakata. Razumijevanje i njihovo ispravljanje ključno je za postizanje visokokvalitetnih rezultata.
Akne sjene (Artefakti samo-sjenčanja)
Problem: Možda ćete vidjeti čudne, netočne uzorke tamnih linija ili uzorke slične moiréu na površinama koje bi trebale biti potpuno osvijetljene. To se naziva "akne sjene". Pojavljuju se jer je vrijednost dubine pohranjena u mapi sjena i vrijednost dubine izračunata tijekom prolaza scene za istu površinu. Zbog netočnosti s pomičnim zarezom i ograničene rezolucije mape sjena, male pogreške mogu uzrokovati da fragment netočno utvrdi da je iza sebe, što rezultira samo-sjenčanjem.
Rješenje: Predrasuda dubine. Najjednostavnije rješenje je uvesti malu predrasudu prema `currentDepth` prije usporedbe. Čineći da fragment izgleda malo bliže svjetlu nego što stvarno jest, guramo ga "izvan" vlastite sjene.
float shadow = currentDepth > depthFromShadowMap + bias ? 0.3 : 1.0;
Pronalaženje prave vrijednosti predrasude je delikatna ravnoteža. Premala, i akne ostaju. Prevelika, i dobivate sljedeći problem.
Peter Panning
Problem: Ovaj artefakt, nazvan po liku koji je mogao letjeti i izgubio sjenu, očituje se kao vidljiva praznina između objekta i njegove sjene. Zbog toga se čini da objekti plutaju ili su odvojeni od površina na kojima bi trebali biti. Izravan je rezultat korištenja predrasude dubine koja je prevelika.
Rješenje: Nagibno-skalirana predrasuda dubine. Robusnije rješenje od konstantne predrasude je učiniti predrasudu ovisnom o strmini površine u odnosu na svjetlo. Strmiji poligoni su skloniji aknama i zahtijevaju veću predrasudu. Ravniji poligoni trebaju manju predrasudu. Većina grafičkih API-ja, uključujući WebGL, pružaju funkcionalnost za automatsku primjenu ove vrste predrasuda tijekom prolaza dubine, što je općenito poželjnije od ručne predrasude u fragment shaderu.
Aliasing perspektive (nazubljeni rubovi)
Problem: Rubovi vaših sjena izgledaju blokovito, nazubljeno i pikselizirano. Ovo je oblik aliasinga. Događa se jer je rezolucija mape sjena konačna. Pojedinačni piksel (ili teksel) u mapi sjena može pokriti veliko područje na površini u konačnoj sceni, posebno za površine blizu kamere ili one koje se gledaju pod kosim kutom. Ovo neusklađivanje u rezoluciji uzrokuje karakterističan blokoviti izgled.
Rješenje: Povećanje rezolucije mape sjena (npr. od 1024x1024 do 4096x4096) može pomoći, ali dolazi uz značajnu cijenu memorije i performansi i ne rješava u potpunosti temeljni problem. Prava rješenja leže u naprednijim tehnikama.
Poglavlje 4: Napredne tehnike mapiranja sjena
Osnovno mapiranje sjena pruža temelj, ali profesionalne aplikacije koriste sofisticiranije algoritme za prevladavanje svojih ograničenja, posebno aliasinga.
Filtriranje postotka bliže (PCF)
PCF je najčešća tehnika za omekšavanje rubova sjena i smanjenje aliasinga. Umjesto da uzima jedan uzorak iz mape sjena i donosi binarnu (u sjeni ili ne-u-sjeni) odluku, PCF uzima više uzoraka iz područja oko ciljne koordinate.
Koncept: Za svaki fragment uzorkujemo mapu sjena ne samo jednom, već u uzorku mreže (npr. 3x3 ili 5x5) oko projicirane koordinate teksture fragmenta. Za svaki od ovih uzoraka vršimo usporedbu dubine. Konačna vrijednost sjene je prosjek svih ovih usporedbi. Na primjer, ako su 4 od 9 uzoraka u sjeni, fragment će biti osjenčan za 4/9, što rezultira glatkom polusjenom (meki rub sjene).
Implementacija: To se radi isključivo unutar fragment shadera. Uključuje petlju koja iterira preko malog jezgra, uzorkujući mapu sjena na svakom pomaku i akumulirajući rezultate. WebGL 2 nudi hardversku podršku (`texture` s `sampler2DShadow`) koja može učinkovitije izvoditi usporedbu i filtriranje.
Prednost: Drastično poboljšava kvalitetu sjene zamjenom tvrdih, aliasiranih rubova glatkim, mekim rubovima.
Cijena: Performanse se smanjuju s brojem uzoraka uzetih po fragmentu.
Kaskadne mape sjena (CSM)
CSM je industrijski standardno rješenje za renderiranje sjena iz jednog usmjerenog izvora svjetlosti (poput sunca) preko vrlo velike scene. Izravno se bavi problemom perspektivnog aliasinga.
Koncept: Osnovna ideja je da objektima blizu kamere treba puno veća rezolucija sjene nego objektima daleko. CSM dijeli vidno frustum kamere u nekoliko dijelova ili "kaskada" duž njegove dubine. Zasebna, visokokvalitetna mapa sjena tada se renderira za svaku kaskadu. Kaskada najbliža kameri pokriva malo područje svjetskog prostora i stoga ima vrlo visoku efektivnu rezoluciju. Kaskade dalje pokrivaju progresivno veća područja s istom veličinom teksture, što je prihvatljivo jer su ti detalji manje vidljivi igraču.
Implementacija: Ovo je znatno složenije.
- Na CPU-u, podijelite frustum kamere u 2-4 kaskade.
- Za svaku kaskadu izračunajte čvrsto prilagođenu ortografsku matricu projekcije za svjetlo koja savršeno obuhvaća taj dio frustuma.
- U petlji renderiranja izvedite prolaz dubine nekoliko puta - jednom za svaku kaskadu, renderirajući na drugu mapu sjena (ili regiju atlasa tekstura).
- U završnom fragment shaderu prolaza scene odredite kojoj kaskadi pripada trenutni fragment na temelju njegove udaljenosti od kamere.
- Uzorak mape sjena odgovarajuće kaskade za izračun sjene.
Prednost: Pruža dosljedno visoku rezoluciju sjena na velikim udaljenostima, što ga čini savršenim za vanjska okruženja.
Variance Shadow Maps (VSM)
VSM je još jedna tehnika za stvaranje mekih sjena, ali zauzima drugačiji pristup od PCF-a.
Koncept: Umjesto da u mapi sjena pohranjujete samo dubinu, VSM pohranjuje dvije vrijednosti: dubinu (prvi moment) i dubinu na kvadrat (drugi moment). Ove dvije vrijednosti nam omogućuju da izračunamo varijancu distribucije dubine. Koristeći matematički alat koji se naziva Čebiševljeva nejednakost, tada možemo procijeniti vjerojatnost da je fragment u sjeni. Ključna prednost je što se VSM tekstura može zamutiti pomoću standardnog hardverski ubrzanog linearnog filtriranja i mipmapinga, što je matematički nevažeće za standardnu mapu dubine. To omogućuje vrlo velike, meke i glatke polusjene sjena uz fiksnu cijenu performansi.
Nedostatak: Glavna slabost VSM-a je "krvarenje svjetla", gdje se svjetlost može pojaviti da krvari kroz objekte u situacijama s preklapajućim okluzijama, jer se statistička aproksimacija može raspasti.
Poglavlje 5: Praktični savjeti za implementaciju i performanse
Odabir rezolucije mape sjena
Rezolucija vaše mape sjena izravna je kompromisna mjera između kvalitete i performansi. Veća tekstura pruža oštrije sjene, ali troši više video memorije i duže se renderira i uzorkuje. Uobičajene veličine uključuju:
- 1024x1024: Dobra polazna točka za mnoge aplikacije.
- 2048x2048: Nudi primjetno poboljšanje kvalitete za desktop aplikacije.
- 4096x4096: Visoka kvaliteta, često se koristi za herojske resurse ili u mehanizmima s robusnim cullingom.
Optimiziranje frustuma svjetla
Da biste maksimalno iskoristili svaki piksel u vašoj mapi sjena, ključno je da je volumen projekcije svjetla (njegov ortografski okvir ili perspektivni frustum) što je moguće čvršće prilagođen elementima scene kojima su potrebne sjene. Za usmjereno svjetlo, to znači prilagođavanje njegove ortografske projekcije kako bi obuhvatila samo vidljivi dio frustuma kamere. Svaki izgubljeni prostor u mapi sjena je izgubljena rezolucija.
WebGL ekstenzije i verzije
WebGL 1 vs. WebGL 2: Iako je mapiranje sjena moguće u WebGL 1, puno je lakše i učinkovitije u WebGL 2. WebGL 1 zahtijeva ekstenziju `WEBGL_depth_texture` za stvaranje teksture dubine. WebGL 2 ima ovu funkcionalnost ugrađenu. Nadalje, WebGL 2 pruža pristup uzorkivačima sjena (`sampler2DShadow`), koji mogu izvoditi hardverski ubrzani PCF, nudeći značajno poboljšanje performansi u odnosu na ručne PCF petlje u shaderu.
Ispravljanje sjena
Sjene mogu biti notorno teške za ispravljanje. Najkorisnija tehnika je vizualizacija mape sjena. Privremeno modificirajte svoju aplikaciju kako biste renderirali teksturu dubine iz određenog izvora svjetlosti izravno na četverokut na zaslonu. To vam omogućuje da vidite točno što svjetlo "vidi". To može odmah otkriti probleme s matricama vašeg svjetla, frustum cullingom ili renderiranjem objekata tijekom prolaza dubine.
Zaključak
Mapiranje sjena u stvarnom vremenu je kamen temeljac moderne 3D grafike, transformirajući ravne, beživotne scene u uvjerljive i dinamične svjetove. Iako je koncept renderiranja iz perspektive svjetla jednostavan, postizanje visokokvalitetnih rezultata bez artefakata zahtijeva duboko razumijevanje osnovnih mehanizama, od pipeline-a s dva prolaza do nijansi predrasuda dubine i aliasinga.
Počevši od osnovne implementacije, možete progresivno rješavati uobičajene artefakte poput akni sjena i nazubljenih rubova. Odatle možete podići svoje vizuale naprednim tehnikama poput PCF-a za meke sjene ili Kaskadnih mapa sjena za okruženja velikih razmjera. Putovanje u renderiranje sjena savršen je primjer mješavine umjetnosti i znanosti koja čini računalnu grafiku tako uvjerljivom. Potičemo vas da eksperimentirate s ovim tehnikama, pomičete njihove granice i unesete novu razinu realizma u svoje WebGL projekte.