Implementace měkkých částic ve WebGL a OpenGL ES

Částice jsou jedním z nejjednodušších způsobů, jak zlepšit vizuální vzhled jakékoli scény. Když jsme se rozhodli aktualizovat vizuály naší 3D živé tapety Buddhy, nejviditelnějším způsobem, jak vyplnit prázdný prostor kolem sochy Buddhy, bylo přidat nějaké částice kouře/mlhy. A použitím měkkých částic jsme dosáhli docela dobře vypadajících výsledků. V tomto článku popíšeme implementaci měkkých částic v čistém WebGL / OpenGL ES bez jakékoli knihovny nebo enginu třetích stran.
Rozdíl mezi starou a aktualizovanou aplikací je ještě lepší, než jsme očekávali. Jednoduché částice kouře výrazně vylepšují scénu, díky čemuž je vizuálně příjemnější a bohatší. Částice dodávají scéně více detailů a zlepšují přechod z objektů v popředí na pozadí:

Živé demo můžete vidět zde

Měkké částice

Co jsou tedy měkké částice? Možná si vzpomínáte, že ve většině starších her (Quake 3 a CS 1,6krát) měly efekty kouře a výbuchu jasně viditelné ostré hrany na průsečíkech částic s jinými geometriemi. Všechny moderní hry se toho zbavily použitím částic s měkkými okraji kolem sousední geometrie.

Vykreslování

Co je potřeba, aby okraje částic byly měkké? Nejprve potřebujeme mít informace o hloubce scény pro shader částic, abychom detekovali průniky a změkčili je. Pak budeme schopni detekovat přesná místa, kde se částice protínají s geometrií porovnáním hloubky scény a částice ve fragment shaderu - průnik je tam, kde jsou tyto hodnoty hloubky stejné. Pojďme si projít proces vykreslování krok za krokem. Implementace vykreslování Android OpenGL ES i WebGL jsou stejné, hlavní rozdíl je v načítání zdrojů. Implementace WebGL je open source a můžete ji získat zde.

Vykreslení textury do hloubky

Abychom vykreslili hloubku scény, musíme nejprve vytvořit textury hloubky a barev mimo obrazovku a přiřadit je odpovídajícím FBO. Toto se provádí v initOffscreen() metoda BuddhaRenderer.js.
Skutečné vykreslování objektů hloubkové scény se provádí v drawDepthObjects() který kreslí sochu Buddhy a rovinu podlahy. Je tu však jeden trik. Protože nepotřebujeme informace o barvách, ale pouze hloubku, vykreslování barev je zakázáno pomocí gl.colorMask(false, false, false, false) volání a poté znovu povoleno gl.colorMask(true, true, true, true) . glcolorMask() dokáže jednotlivě přepínat vykreslování červených, zelených, modrých a alfa komponent, takže pro úplné přeskočení zápisu do vyrovnávací paměti barev nastavíme všechny komponenty na hodnotu false a poté je znovu povolíme nastavením na hodnotu true. Informace o výsledné hloubce scény lze zobrazit zrušením komentáře volání na drawTestDepth() v drawScene() metoda. Protože textura hloubky je jednokanálová, je považována za pouze červenou, takže zelené a modré kanály mají nulové hodnoty. Výsledek při vizualizaci vypadá takto:

Vykreslování částic

Shader používaný pro vykreslování měkkých částic lze nalézt v SoftDiffuseColoredShader.js. Pojďme se podívat, jak to funguje.
Hlavní myšlenkou detekce průniku mezi částicí a geometrií scény je porovnání hloubky fragmentu s hloubkou scény, která je uložena v textuře.
První věcí potřebnou k porovnání hloubky je linearizace hodnot hloubky, protože původní hodnoty jsou exponenciální. To se provádí pomocí calc_depth() funkce. Tato technika je popsána zde. K linearizaci těchto hodnot potřebujeme vec2 uCameraRange uniform which x a y komponenty mají blízkou a vzdálenou rovinu kamery. Poté shader vypočítá lineární rozdíl mezi geometrií částic a hloubkou scény – je uložen v proměnné a . Pokud však použijeme tento koeficient na barvu částic, dostaneme příliš slabé částice – budou lineárně mizet od jakékoli geometrie za nimi a toto blednutí je poměrně rychlé. Takto vypadá lineární rozdíl hloubky při vizualizaci (můžete odkomentovat odpovídající řádek v shaderu, abyste jej viděli):

Aby byly částice průhlednější pouze v blízkosti okraje průsečíku (což se děje na a=0 ) použijeme GLSL smoothstep() funkce s uTransitionSize koeficient, který definuje velikost měkké hrany. Pokud chcete pochopit, jak smoothstep() funkce funguje a podívejte se na několik dalších skvělých příkladů, jak ji používat, měli byste si přečíst tento skvělý článek. Tento konečný koeficient prolnutí je uložen v proměnné jednoduše nazvané b . Pro režim míchání používaný našimi částicemi jednoduše vynásobíme difúzní barvu částice tímto koeficientem, v jiných implementacích může být aplikován na alfa kanál. Pokud odkomentujete řádek v shaderu pro vizualizaci tohoto koeficientu, uvidíte obrázek podobný tomuto:

Zde můžete vidět vizuální rozdíl mezi různými hodnotami jednotné měkkosti částic:

Síť billboardů Sprite

Malé prachové částice jsou vykresleny jako bodové skřítky (vykreslení pomocí GL_POINTS ). Tento režim je snadno použitelný, protože automaticky vytváří čtvercový tvar ve fragment shaderu. Jsou však špatnou volbou pro velké částice kouře. Za prvé, jsou oříznuty středem bodu, a tak by náhle zmizely na okrajích obrazovky. Také tvar quad není příliš efektivní a může přidat značné přečerpání. Rozhodli jsme se použít vlastní částicovou síťovinu s optimalizovaným tvarem – s oříznutými rohy, kde je textura zcela průhledná:

Tyto vlastní quady nelze vykreslit v dávkách s GL_POINTS , každá částice je vykreslena samostatně. Musí být umístěny na libovolných světových souřadnicích, ve správném měřítku, ale měly by být vždy otočeny směrem ke kameře. Toho lze dosáhnout technikou popsanou v této odpovědi na StackOverflow. V BuddhaRenderer.js je calculateMVPMatrixForSprite() metoda, která vytváří matice MVP pro sítě billboardů. Provádí pravidelné měřítko a překlad sítě a poté používá resetMatrixRotations() pro resetování rotace matice zobrazení modelu před jejím vynásobením projekční maticí. Výsledkem je matice MVP, která vždy směřuje ke kameře.

Výsledek

Na konečný výsledek se můžete podívat zde - https://keaukraine.github.io/webgl-buddha/index.html.
Neváhejte klonovat zdrojový kód a upravovat jej podle svých potřeb z Github - https://github.com/keaukraine/webgl-buddha.