Światło, które zna kształt: mapy normalnych dla prerenderowanych sprite'ów
W grze strategicznej 2D czołg to obrazek z wypalonym w nim słońcem. Kiedy nocą obok spada pocisk, obrazek nie wie, która jego strona jest zwrócona do wybuchu. Ta lekcja pokazuje jeden kawałek danych, który to naprawia, mapę normalnych, i to, jak wbudowaliśmy ją w Iron Sector.
- arkuszy sprite'ów z mapami normalnych
- 135arkuszy sprite'ów z mapami normalnych
- klatki, każda z własnymi normalnymi
- 55 204klatki, każda z własnymi normalnymi
- rozdzielczości stron normalnych
- ½rozdzielczości stron normalnych
- na klatkę przy 50 oświetlonych sprite'ach
- +0,5 msna klatkę przy 50 oświetlonych sprite'ach
Sprite to obrazek
W grze strategicznej 2D czołg nie jest modelem 3D. To obrazek wyrenderowany raz z nieruchomej kamery i zapisany w atlasie. Każda klatka to tylko siatka kolorów, a światło słońca zostało w nie wypalone w chwili tworzenia sprite'a.
To szybkie i wygląda dobrze, dopóki w scenie nie zaświeci coś innego: błysk wystrzału, wybuch, płonący nocą dom. Obrazek nie wie, w którą stronę zwrócone są jego powierzchnie, więc gra może co najwyżej rozjaśnić cały sprite. Wygląda to jak naklejka.


Czym jest normalna
Normalna to strzałka o długości jeden, która wystaje prostopadle z powierzchni i pokazuje, w którą stronę ta powierzchnia jest zwrócona. Aby oświetlić powierzchnię, renderer porównuje tę strzałkę z drugą, skierowaną do źródła światła.
W 1760 roku Johann Lambert opisał regułę, której gry używają do dziś: matowa powierzchnia otrzymuje światło proporcjonalnie do cosinusa kąta między dwiema strzałkami. Ten cosinus to po prostu ich iloczyn skalarny: x razy x, y razy y, z razy z, i dodać. Ściana zwrócona prosto do światła dostaje całe; ustawiona krawędzią lub odwrócona nie dostaje nic.
brightness ∝ cos θ = n · l n · l = nx·lx + ny·ly + nz·lz
| Kąt do światła | Otrzymane światło |
|---|---|
| 0° | 1.00 |
| 45° | 0.71 |
| 60° | 0.50 |
| ≥ 90° | 0 |
Mapy normalnych w grach
W 1978 roku James Blinn pokazał, że drobne nierówności można podrobić, wyginając normalne powierzchni, bez dodawania geometrii. W 2004 roku, z Doom 3 i Half-Life 2, był to już standard: prosty model nosi teksturę normalnych wypaloną z dużo bardziej szczegółowego, a nity i cegły łapią światło.
Gry 2D przejęły ten pomysł. W Sprite Lamp artysta maluje postać oświetloną z kilku stron, a narzędzie wyprowadza z tego mapę normalnych. Dead Cells renderuje postacie z modeli 3D i do każdej klatki eksportuje mapę normalnych. Iron Sector jest najbliżej tego przypadku: nasze sprite'y są renderowane z modeli 3D, więc normalne nie są malowane ani zgadywane. Są mierzone.
Jak normalna mieści się w obrazku
Normalna ma trzy liczby, a obrazek trzy kanały, więc mapa normalnych zapisuje strzałkę jako kolor. Czerwony mówi, jak bardzo powierzchnia patrzy w prawo, zielony w górę, niebieski w stronę kamery.
Każda wartość leży między −1 a 1, więc ściska się ją do zakresu koloru: dzieli na pół i dodaje połowę. Dlatego mapy normalnych są fioletowe: powierzchnia patrząca prosto w kamerę to (128, 128, 255). Aby odczytać piksel, dzielimy przez 255, podwajamy i odejmujemy jeden.
colour = n × 0.5 + 0.5 n = colour × 2 − 1 (218, 83, 205) → (0.71, −0.35, 0.61)


Skąd biorą się nasze normalne
Modele w Iron Sector są pisane jako kod i zamieniane na woksele, sześcianiki o boku około dwóch centymetrów. Woksel na powierzchni ma co najmniej jednego pustego sąsiada. Aby znaleźć jego normalną, generator patrzy na wszystkich 26 sąsiadów: każdy wypełniony odpycha strzałkę od siebie, przekątne nieco słabiej. Suma pchnięć sprowadzona do długości jeden wskazuje na zewnątrz materiału.
Na płaskim dachu wszystko poniżej jest wypełnione, a powyżej puste, więc strzałka patrzy prosto w górę; na okrągłej wieży strzałki rozchodzą się płynnym wachlarzem. Ten sam przebieg, który zapisuje kolor piksela, zapisuje w tym samym pikselu jego normalną, więc mapa normalnych pasuje do sprite'a idealnie. Dla gry oznaczało to 135 arkuszy sprite'ów i 55 204 klatki, zbudowane obok istniejących atlasów.
n = normalize( −Σ filled(k) · dₖ / |dₖ| ) over 26 neighbours

Słońce, które już tam jest
Każdy sprite ma już światło: słońce z lewej u góry, wypalone podczas renderowania, 30 % światła otoczenia i 70 % słońca według tego samego iloczynu skalarnego.
Najpierw spróbowaliśmy drogi podręcznikowej: zapisać kolory bez światła i oświetlać wszystko na nowo w trakcie gry. Przeoświetlony czołg mylił się nawet o 32 stopnie z 255 przy naszym limicie 3; miękkiego cienia na ziemi i detali mniejszych niż piksel nie da się odzyskać z map. Dlatego zatwierdzony dzienny wygląd zostaje bez zmian, a nowe światło jest dodawane na wierzch. Dokładnie tak działa prawdziwy błysk.
intensity = 0.30 + 0.70 · max(0, n · L) L = (−0.55, 0.30, 0.78)
Równanie jednego światła
Dodany kolor to własny kolor sprite'a razy siła k, razy kolor światła, razy człon kierunku, razy człon odległości. Człon kierunku to iloczyn skalarny z wrap lighting: dodanie 0,2 przed dzieleniem pozwala światłu zajść trochę za krawędzie, jak na zakurzonej malowanej stali. Człon odległości łagodnie spada od jedności do zera na promieniu światła.
Weźmy piksel wieży w nocy i błysk w połowie jego promienia. Z prawej n · l = 0,71, po wrap 0,76; spadek 0,56; przy k = 2,2 piksel jaśnieje o 93 %. Z lewej n · l jest ujemne i piksel nie dostaje nic. Bez normalnych obie strony dostają te same 124 % i czołg wygląda płasko.
added = colour × k × light × wrap(n · l) × falloff(d / r) wrap(x) = max(0, (x + 0.2) / 1.2) falloff(q) = (1 − q²)² k: day 0.35 · dawn 1.2 · night 2.2




Dwie przestrzenie, jedna kamera
Normalne są zapisane w przestrzeni ekranu, tak jak widzi je kamera. Światła żyją w świecie, jako punkt na ziemi i wysokość nad nim. Nasza kamera jest ustalona na zawsze, 45° w poziomie i 30° w dół, więc przeliczenie to jeden obrót o 30°.
Dla każdego piksela shader bierze wektor do światła, przechyla go o kąt kamery i dopiero wtedy liczy iloczyn skalarny. Ogień 10 jednostek nad ziemią i 10 jednostek przed czołgiem staje się strzałką skierowaną głównie w ekran i trochę z góry: oświetla ściany zwrócone do nas i odrobinę dach.
v = ( dx, dz·cos 30° − dy·sin 30°, dy·cos 30° + dz·sin 30° )
W grze
Nocą Tygrys strzela z głównego działa. Błysk trwa kilka klatek, ale jego światło sięga sześć razy dalej niż widoczna poświata, bo w ciemności zauważa się właśnie światło. Bez normalnych błysk maluje jasne koło tak samo na ziemi i na czołgu. Z normalnymi rozjaśniają się płyta czołowa i jarzmo działa, a tył zostaje ciemny. W dzień ten sam błysk jest ledwie widoczny, tak jak powinien.
Przy wybuchach normalne naprawdę się opłacają. Pocisk z lewej oświetla lewą burtę, pocisk z prawej przenosi światło na drugą stronę. Podczas ostrzału każdy sprite bierze osiem najsilniejszych świateł i je sumuje, więc czołg może być oświetlony z dwóch, trzech stron naraz, każda w swoim kolorze.







Światło, które zatrzymuje się na ścianie
Decyzja, gdzie rysować światło, to połowa pracy. Narysowane na wierzchu wszystkich sprite'ów sprawiało, że czołg za domem świecił przez ścianę. Narysowane pod nocnym filtrem trafiało na ciemny błękit nocy: czerwień ucinała się do jednej trzeciej i ogień wychodził brązowy.
Rozwiązaniem był osobny obraz światła. Renderer rysuje scenę drugi raz, w tej samej kolejności, do osobnej tekstury: oświetlone sprite'y jako czyste światło, wszystkie inne jako czarną sylwetkę, więc dom z przodu zasłania czołg za nim. Potem ten obraz jest dodawany nad nocną korekcję kolorów i ogień zostaje ciepły. Zostaje jedno uczciwe ograniczenie: światło nie rzuca jeszcze cieni.
Ile to kosztuje
Mapa normalnych jest tak duża jak obrazek, do którego należy, ale kształt zmienia się wolniej niż kolor, więc normalne są zapisane w połowie rozdzielczości. Po przeskalowaniu różnią się o około jeden stopień z 255. Scena potrzebuje od jednej dziesiątej do jednej czwartej więcej pamięci, a na szybkiej karcie graficznej pięćdziesiąt oświetlonych sprite'ów dodaje około pół milisekundy na klatkę.
Cały system jest opcjonalny. Przy wyłączonym oświetleniu nie zmienia się ani jeden piksel i nie jest ładowana żadna mapa normalnych. „Średnie” oświetla 50 sprite'ów po 3 światła, „Wysokie” 150 sprite'ów po 8 świateł.
Podsumowanie
Trzy liczby w pikselu, jeden iloczyn skalarny na światło i staranny wybór miejsca, w którym je rysować. Sprite'y wciąż są obrazkami wyrenderowanymi raz z nieruchomej kamery, ale teraz każdy piksel wie, w którą stronę patrzy. A kiedy w ciemności coś płonie, czołg obraca pancerz do światła.