Naučte se základní koncepty a pokročilé techniky pro rendering stínů v reálném čase ve WebGL. Tento průvodce pokrývá shadow mapping, PCF, CSM a řešení běžných artefaktů.
WebGL Shadow Mapping: Komplexní průvodce renderingem v reálném čase
Ve světě 3D počítačové grafiky přispívá jen málo prvků k realističnosti a pohlcení více než stíny. Poskytují zásadní vizuální vodítka o prostorových vztazích mezi objekty, umístění zdrojů světla a celkové geometrii scény. Bez stínů mohou 3D světy působit ploše, odpojeně a uměle. Pro webové 3D aplikace využívající WebGL je implementace vysoce kvalitních stínů v reálném čase charakteristickým znakem profesionálních zážitků. Tento průvodce poskytuje hluboký ponor do nejzákladnější a nejrozšířenější techniky pro dosažení tohoto cíle: Shadow Mapping.
Ať už jste zkušený programátor grafiky nebo webový vývojář, který se pouští do třetího rozměru, tento článek vás vybaví znalostmi k pochopení, implementaci a odstraňování problémů se stíny v reálném čase ve vašich projektech WebGL. Projdeme cestu od základní teorie k praktickým implementačním detailům, prozkoumáme běžné úskalí a pokročilé techniky používané v moderních grafických enginech.
Kapitola 1: Základy Shadow Mapping
Jádrem shadow mapping je chytrá a elegantní technika, která určuje, zda je bod ve scéně ve stínu, a to položením jednoduché otázky: "Je tento bod vidět ze zdroje světla?" Pokud je odpověď ne, znamená to, že něco blokuje světlo a bod musí být ve stínu. Abychom na tuto otázku odpověděli programově, používáme dvoufázový renderingový přístup.
Co je Shadow Mapping? Základní koncept
Celá technika se točí kolem vykreslování scény dvakrát, pokaždé z jiného úhlu pohledu:
- Fáze 1: Hloubková fáze (perspektiva světla). Nejprve vykreslíme celou scénu z přesné pozice a orientace zdroje světla. V této fázi nás však nezajímají barvy ani textury. Jediná informace, kterou potřebujeme, je hloubka. Pro každý vykreslený objekt zaznamenáme jeho vzdálenost od zdroje světla. Tato kolekce hloubkových hodnot je uložena ve speciální textuře zvané shadow map nebo hloubková mapa. Každý pixel v této mapě představuje vzdálenost k nejbližšímu objektu z pohledu světla v určitém směru.
- Fáze 2: Fáze scény (perspektiva kamery). Dále vykreslíme scénu tak, jak bychom normálně dělali, z pohledu hlavní kamery. Ale pro každý jednotlivý pixel, který se kreslí, provedeme dodatečný výpočet. Určíme pozici tohoto pixelu v 3D prostoru a pak se zeptáme: "Jak daleko je tento bod od zdroje světla?" Poté porovnáme tuto vzdálenost s hodnotou uloženou v naší shadow mapě (z fáze 1) v odpovídajícím umístění.
Logika je jednoduchá:
- Pokud je aktuální vzdálenost pixelu od světla větší než vzdálenost uložená ve shadow mapě, znamená to, že existuje jiný objekt blíže ke světlu podél stejné linie pohledu. Proto je aktuální pixel ve stínu.
- Pokud je vzdálenost pixelu menší než nebo rovna vzdálenosti ve shadow mapě, znamená to, že nic neblokuje, a pixel je plně osvětlen.
Nastavení scény
K implementaci shadow mapping ve WebGL potřebujete několik klíčových komponent:
- Zdroj světla: Může to být směrové světlo (jako slunce), bodové světlo (jako žárovka) nebo reflektor. Typ světla určí typ projekční matice použité během hloubkové fáze.
- Framebuffer Object (FBO): WebGL normálně vykresluje do výchozího framebufferu obrazovky. K vytvoření naší shadow mapy potřebujeme off-screen render target. FBO nám umožňuje vykreslovat do textury místo obrazovky. Naše FBO bude nakonfigurováno s připojením hloubkové textury.
- Dvě sady shaderů: Budete potřebovat jeden shader program pro hloubkovou fázi (velmi jednoduchý) a druhý pro finální fázi scény (který bude obsahovat logiku výpočtu stínů).
- Matice: Budete potřebovat standardní matice modelu, pohledu a projekce pro kameru. Zásadní je, že budete také potřebovat matici pohledu a projekce pro zdroj světla, často kombinované do jedné "matice světelného prostoru".
Kapitola 2: Dvoufázový renderingový pipeline v detailu
Pojďme si rozebrat dvě renderingové fáze krok za krokem a zaměřit se na role matic a shaderů.Fáze 1: Hloubková fáze (z perspektivy světla)
Cílem této fáze je naplnit naši hloubkovou texturu. Zde je návod, jak to funguje:
- Připojte FBO: Před kreslením instruujete WebGL, aby vykresloval do vašeho vlastního FBO místo do plátna.
- Konfigurujte Viewport: Nastavte rozměry viewportu tak, aby odpovídaly velikosti textury vaší shadow mapy (např. 1024x1024 pixelů).
- Vymažte hloubkový buffer: Ujistěte se, že hloubkový buffer FBO je před vykreslením vymazán.
- Vytvořte matice světla:
- Matice pohledu světla: Tato matice transformuje svět do pohledu světla. Pro směrové světlo je to obvykle vytvořeno pomocí funkce `lookAt`, kde "oko" je pozice světla a "cíl" je směr, kam směřuje.
- Matice projekce světla: Pro směrové světlo, které má rovnoběžné paprsky, se používá ortografická projekce. Pro bodové světla nebo reflektory se používá perspektivní projekce. Tato matice definuje objem v prostoru (krabici nebo komolý jehlan), který bude vrhat stíny.
- Použijte hloubkový shader program: Toto je minimální shader. Jediným úkolem vertex shaderu je vynásobit pozici vrcholu maticemi pohledu a projekce světla. Fragment shader je ještě jednodušší: pouze zapisuje hloubkovou hodnotu fragmentu (jeho z-souřadnici) do hloubkové textury. V moderním WebGL často ani nepotřebujete vlastní fragment shader, protože FBO lze nakonfigurovat tak, aby automaticky zachytával hloubkový buffer.
- Vykreslete scénu: Nakreslete všechny objekty vrhající stíny ve vaší scéně. FBO nyní obsahuje naši dokončenou shadow mapu.
Fáze 2: Fáze scény (z perspektivy kamery)
Nyní vykreslíme finální obraz pomocí shadow mapy, kterou jsme právě vytvořili, k určení stínů.
- Odpojte FBO: Přepněte zpět na vykreslování do výchozího framebufferu plátna.
- Konfigurujte Viewport: Nastavte viewport zpět na rozměry plátna.
- Vymažte obrazovku: Vymažte barevné a hloubkové buffery plátna.
- Použijte shader program scény: Tady se dějí kouzla. Tento shader je složitější.
- Vertex Shader: Tento shader musí udělat dvě věci. Za prvé, vypočítá finální pozici vrcholu pomocí matic modelu, pohledu a projekce kamery jako obvykle. Za druhé, musí také vypočítat pozici vrcholu z pohledu světla pomocí matice světelného prostoru z fáze 1. Tato druhá souřadnice je předána fragment shaderu jako varying.
- Fragment Shader: Toto je jádro logiky stínů. Pro každý fragment:
- Přijměte interpolovanou pozici ve světelném prostoru z vertex shaderu.
- Proveďte perspektivní dělení na této souřadnici (vydělte x, y, z w). To ji transformuje do Normalizovaných Souřadnic Zařízení (NDC), v rozsahu od -1 do 1.
- Transformujte NDC do texturovacích souřadnic (které se pohybují od 0 do 1), abychom mohli samplovat naši shadow mapu. Toto je jednoduchá operace škálování a biasu: `texCoord = ndc * 0.5 + 0.5;`.
- Použijte tyto texturovací souřadnice k samplování textury shadow mapy vytvořené ve fázi 1. To nám dá `depthFromShadowMap`.
- Aktuální hloubka fragmentu z pohledu světla je jeho z-komponenta z transformované souřadnice světelného prostoru. Nazvěme ji `currentDepth`.
- Porovnejte hloubky: Pokud `currentDepth > depthFromShadowMap`, fragment je ve stínu. Budeme muset přidat malý bias k této kontrole, abychom se vyhnuli artefaktu zvanému "shadow acne", o kterém budeme diskutovat dále.
- Na základě porovnání určete faktor stínu (např. 1.0 pro osvětlené, 0.3 pro zastíněné).
- Aplikujte tento faktor stínu na finální výpočet barvy (např. vynásobte komponenty ambientního a difúzního osvětlení faktorem stínu).
- Vykreslete scénu: Nakreslete všechny objekty ve scéně.
Kapitola 3: Běžné problémy a řešení
Implementace základního shadow mapping rychle odhalí několik běžných vizuálních artefaktů. Pochopení a oprava je zásadní pro dosažení vysoce kvalitních výsledků.
Shadow Acne (Artefakty samo-stínování)
Problém: Můžete vidět podivné, nesprávné vzory tmavých čar nebo Moiré-podobné vzory na površích, které by měly být plně osvětlené. Tomu se říká "shadow acne". Dochází k tomu proto, že hloubková hodnota uložená ve shadow mapě a hloubková hodnota vypočítaná během fáze scény jsou pro stejný povrch. Kvůli nepřesnostem v plovoucí čárce a omezenému rozlišení shadow mapy mohou drobné chyby způsobit, že fragment nesprávně určí, že je za sebou samým, což má za následek samo-stínování.
Řešení: Hloubkový Bias. Nejjednodušším řešením je zavést malý bias do `currentDepth` před porovnáním. Tím, že fragment bude vypadat o něco blíže ke světlu, než ve skutečnosti je, ho "vytlačíme" z jeho vlastního stínu.
float shadow = currentDepth > depthFromShadowMap + bias ? 0.3 : 1.0;
Nalezení správné hodnoty biasu je delikátní balancování. Příliš malý a acne zůstane. Příliš velký a máte další problém.
Peter Panning
Problém: Tento artefakt, pojmenovaný po postavě, která uměla létat a ztratila svůj stín, se projevuje jako viditelná mezera mezi objektem a jeho stínem. Díky tomu se objekty jeví jako vznášející se nebo odpojené od povrchů, na kterých by měly spočívat. Je to přímý důsledek použití hloubkového biasu, který je příliš velký.
Řešení: Slope-Scale Hloubkový Bias. Robustnější řešení než konstantní bias je učinit bias závislým na strmosti povrchu vzhledem ke světlu. Strmější polygony jsou náchylnější k acne a vyžadují větší bias. Plošší polygony potřebují menší bias. Většina grafických API, včetně WebGL, poskytuje funkce pro automatické aplikování tohoto druhu biasu během hloubkové fáze, což je obecně preferovanější než ruční bias ve fragment shaderu.
Perspective Aliasing (Zubaté hrany)
Problém: Hrany vašich stínů vypadají kostrbatě, zubatě a pixelizovaně. Toto je forma aliasingu. Stává se to proto, že rozlišení shadow mapy je konečné. Jeden pixel (nebo texel) ve shadow mapě může pokrýt velkou plochu na povrchu ve finální scéně, zejména pro povrchy blízko kamery nebo ty, které jsou pozorovány pod úhlem. Tento nesoulad v rozlišení způsobuje charakteristický kostrbatý vzhled.
Řešení: Zvýšení rozlišení shadow mapy (např. z 1024x1024 na 4096x4096) může pomoci, ale přichází s významnými náklady na paměť a výkon a plně neřeší základní problém. Skutečná řešení spočívají v pokročilejších technikách.
Kapitola 4: Pokročilé techniky Shadow Mapping
Základní shadow mapping poskytuje základ, ale profesionální aplikace používají sofistikovanější algoritmy k překonání jeho omezení, zejména aliasingu.
Percentage-Closer Filtering (PCF)
PCF je nejběžnější technika pro změkčení hran stínů a redukci aliasingu. Místo toho, abychom vzali jeden vzorek ze shadow mapy a učinili binární rozhodnutí (ve stínu nebo ne ve stínu), PCF vezme více vzorků z oblasti kolem cílové souřadnice.
Koncept: Pro každý fragment samplujeme shadow mapu nejen jednou, ale v mřížkovém vzoru (např. 3x3 nebo 5x5) kolem projektované texturovací souřadnice fragmentu. Pro každý z těchto vzorků provedeme porovnání hloubky. Finální hodnota stínu je průměr všech těchto porovnání. Například pokud jsou 4 z 9 vzorků ve stínu, fragment bude zastíněn 4/9, což má za následek hladkou penumbru (měkký okraj stínu).
Implementace: To se provádí výhradně v rámci fragment shaderu. Zahrnuje smyčku, která iteruje přes malé jádro, sampluje shadow mapu při každém offsetu a akumuluje výsledky. WebGL 2 nabízí hardwarovou podporu (`texture` s `sampler2DShadow`), která může provádět porovnání a filtrování efektivněji.
Přínos: Drasticky zlepšuje kvalitu stínu nahrazením tvrdých aliasingových hran hladkými, měkkými.
Náklady: Výkon se snižuje s počtem vzorků odebraných na fragment.
Cascaded Shadow Maps (CSM)
CSM je průmyslové standardní řešení pro vykreslování stínů z jednoho směrového zdroje světla (jako slunce) přes velmi velkou scénu. Přímo řeší problém perspektivního aliasingu.
Koncept: Hlavní myšlenkou je, že objekty blízko kamery potřebují mnohem vyšší rozlišení stínu než objekty vzdálené. CSM rozdělí komolý jehlan pohledu kamery do několika sekcí, nebo "kaskád", podél jeho hloubky. Pro každou kaskádu je pak vykreslena samostatná, vysoce kvalitní shadow mapa. Kaskáda nejblíže ke kameře pokrývá malou oblast světelného prostoru, a proto má velmi vysoké efektivní rozlišení. Kaskády dále pokrývají postupně větší oblasti se stejnou velikostí textury, což je přijatelné, protože tyto detaily jsou pro hráče méně viditelné.
Implementace: To je výrazně složitější.
- V CPU rozdělte komolý jehlan kamery do 2-4 kaskád.
- Pro každou kaskádu vypočítejte těsně přiléhající ortografickou projekční matici pro světlo, která dokonale obklopuje danou sekci komolého jehlanu.
- V renderingové smyčce proveďte hloubkovou fázi několikrát – jednou pro každou kaskádu, vykreslujte do jiné shadow mapy (nebo do oblasti texturového atlasu).
- Ve fragment shaderu finální fáze scény určete, do které kaskády patří aktuální fragment na základě jeho vzdálenosti od kamery.
- Samplujte shadow mapu příslušné kaskády pro výpočet stínu.
Přínos: Poskytuje trvale vysoce kvalitní stíny na rozsáhlé vzdálenosti, takže je ideální pro venkovní prostředí.
Variance Shadow Maps (VSM)
VSM je další technika pro vytváření měkkých stínů, ale zaujímá odlišný přístup než PCF.
Koncept: Místo ukládání pouze hloubky do shadow mapy ukládá VSM dvě hodnoty: hloubku (první moment) a hloubku na druhou (druhý moment). Tyto dvě hodnoty nám umožňují vypočítat rozptyl distribuce hloubky. Pomocí matematického nástroje zvaného Čebyševova nerovnost můžeme pak odhadnout pravděpodobnost, že je fragment ve stínu. Klíčovou výhodou je, že texturu VSM lze rozmazat pomocí standardního hardwarově akcelerovaného lineárního filtrování a mipmappingu, což je matematicky neplatné pro standardní hloubkovou mapu. To umožňuje velmi velké, měkké a hladké stínové penumbry s pevnými náklady na výkon.
Nevýhoda: Hlavní slabinou VSM je "prosakování světla", kde se může zdát, že světlo prosakuje objekty v situacích s překrývajícími se okludéry, protože statistická aproximace se může rozpadnout.
Kapitola 5: Praktické implementační tipy a výkon
Výběr rozlišení vaší Shadow Mapy
Rozlišení vaší shadow mapy je přímý kompromis mezi kvalitou a výkonem. Větší textura poskytuje ostřejší stíny, ale spotřebovává více video paměti a trvá déle vykreslovat a samplovat. Běžné velikosti zahrnují:
- 1024x1024: Dobrý základ pro mnoho aplikací.
- 2048x2048: Nabízí znatelné zlepšení kvality pro desktopové aplikace.
- 4096x4096: Vysoká kvalita, často používaná pro hero assets nebo v enginech s robustním cullingem.
Optimalizace komolého jehlanu světla
Chcete-li získat maximum z každého pixelu ve vaší shadow mapě, je zásadní, aby byl projekční objem světla (jeho ortografická krabice nebo perspektivní komolý jehlan) co nejtěsněji přizpůsoben prvkům scény, které potřebují stíny. Pro směrové světlo to znamená přizpůsobit jeho ortografickou projekci tak, aby obklopovala pouze viditelnou část komolého jehlanu kamery. Jakýkoli zbytečný prostor ve shadow mapě je zbytečné rozlišení.
WebGL rozšíření a verze
WebGL 1 vs. WebGL 2: I když je shadow mapping možný ve WebGL 1, je mnohem snazší a efektivnější ve WebGL 2. WebGL 1 vyžaduje rozšíření `WEBGL_depth_texture` k vytvoření hloubkové textury. WebGL 2 má tuto funkci zabudovanou. Kromě toho WebGL 2 poskytuje přístup ke stínovým samplerům (`sampler2DShadow`), které mohou provádět hardwarově akcelerované PCF, což nabízí výrazné zvýšení výkonu oproti ručním smyčkám PCF ve shaderu.
Ladění stínů
Stíny mohou být notoricky obtížné ladit. Jedinou nejužitečnější technikou je vizualizace shadow mapy. Dočasně upravte svou aplikaci, aby vykreslovala hloubkovou texturu ze specifického zdroje světla přímo na quad na obrazovce. To vám umožní vidět přesně to, co světlo "vidí". To může okamžitě odhalit problémy s maticemi světla, cullingem komolého jehlanu nebo vykreslováním objektů během hloubkové fáze.
Závěr
Shadow mapping v reálném čase je základním kamenem moderní 3D grafiky, který transformuje ploché, bezživotné scény do uvěřitelných a dynamických světů. I když je koncept vykreslování z pohledu světla jednoduchý, dosažení vysoce kvalitních výsledků bez artefaktů vyžaduje hluboké porozumění základním mechanismům, od dvoufázového pipeline až po nuance hloubkového biasu a aliasingu.
Začnete-li se základní implementací, můžete postupně řešit běžné artefakty, jako je shadow acne a zubaté hrany. Odtamtud můžete pozvednout své vizuály pomocí pokročilých technik, jako je PCF pro měkké stíny nebo Cascaded Shadow Maps pro rozsáhlá prostředí. Cesta do renderingu stínů je dokonalým příkladem směsi umění a vědy, která dělá počítačovou grafiku tak poutavou. Doporučujeme vám experimentovat s těmito technikami, posouvat jejich hranice a přinést novou úroveň realismu do vašich WebGL projektů.