Osvetljenje je ono što čini razliku između ravne, dosadne 2D igre i pravog atmosferskog iskustva. Kada sam počeo da pravim Merciless Warrior, znao sam da želim da igrač oseti tamnu i toplu stranu tog sveta.
Međutim, implementacija osvetljenja u framework-u kao što je Java Swing (koji nije dizajniran za game grafiku visokih performansi) bila je ogroman izazov. Mučio sam se sa ovim nedeljama. Pokušao bih neki pristup, gledao kako FPS pada, isfrustrirao se i obrisao kod. Otišao bih da radim na sistemu inventara ili logici borbe samo da bih se osećao produktivno, ali nedostatak atmosfere me je uvek vraćao nazad.
Ovo je priča o mojim neuspelim pokušajima i specifičnim optimizacijama koje su na kraju sve rešile.
Naivni pristup - Isecanje rupa
Moja prva ideja je bila geometrijska. Mislio sam da, ako želim mrak, treba da nacrtam crni pravougaonik preko celog ekrana. Ako želim svetlost, iseći ću krug iz tog pravougaonika.
Pokušao sam da koristim Java Area klasu da bih izveo konstruktivnu geometriju (oduzimanje elipse od pravougaonika u svakom pojedinačnom frejmu).
| |

Rezultat: Igra je pala na 5 FPS. Izračunavanje preseka složene geometrije na CPU-u ~200 puta u sekundi je jednostavno preskupo, posebno kada imate više izvora svetlosti.
Rešenje - Alpha Compositing
Shvatio sam da moram da prestanem da razmišljam o geometriji i počnem da razmišljam o blending modovima. Umesto da isecam oblike, morao sam da brišem piksele sa slike.
Prešao sam na korišćenje BufferedImage-a kao lightmap.
- Popunim sliku polu-transparentnom crnom bojom (ambijentalna tama).
- Podesim Graphics Composite mod na
DST_OUT. Ovaj mod diktira da će sve što sledeće nacrtam ukloniti transparentnost (odnosno izbrisati boju) sa ciljne slike. - Nacrtam svoja svetla na ovoj mapi.
Evo osnovne logike iz mog LightManager-a:
| |

Bilo je bolje, ali je i dalje kočilo. Crtanje RadialGradientPaint-a od nule za svaku baklju, u svakom frejmu, i dalje je previše gušilo CPU.
Odustao sam i vratio se radu na AI-u neprijatelja.
Optimizacija 1 - Keširanje tekstura
Jednog dana sam došao na ideju da iskoristim keširanje. Generisanje gradijenta za baklju ili auru igrača je veoma zahtevno. Ako imam 20 baklji na ekranu, ne bi trebalo da računam 20 gradijenata svakog frejma.
Implementirao sam sistem keširanja. Gradijente unapred renderujem u BufferedImage objekte samo jednom, prilikom pokretanja igre.
| |
Sada, render loop samo crta postojeće slike na ekran, što je stvar u kojoj je Java Graphics2D najbolji.
Optimizacija 2 - Downscaling (Smanjenje rezolucije)
Čak i sa keširanjem, glavni problem sa FPS-om je i dalje bio tu. Konstantni padovi frejmova. Ovo je bio poslednji trik koji je konačno stabilizovao FPS. Osvetljenje u igrama je prirodno mutno i mekano. Ne mora da bude oštro i precizno u piksel.
Uveo sam LIGHTMAP_SCALE faktor (postavljen na 2).
- Kreiram lightmap-u u duplo manjoj rezoluciji od prozora igre.
- Radim svo crtanje i brisanje na ovoj manjoj slici (4 puta manje piksela za obradu!).
- Razvučem sliku nazad na punu veličinu kada je crtam preko sveta igre.
| |

Korišćenje bilinearne interpolacije ublažava pikselizaciju nastalu razvlačenjem slike, čineći da svetla izgledaju još mekše i prirodnije, dok se istovremeno drastično smanjuje opterećenje CPU-a.
Napredni efekti - Ciklus dana i noći
S obzirom na to da su performanse sada bile osigurane, dodao sam TimeCycleManager.
Umesto statičke tame, ambientAlpha vrednost interpolira između boja na osnovu vremena u igri.
- Noć: Visoka alpha vrednost (tamno), tamnoplava nijansa.
- Zora/Sumrak: Narandžasta/ljubičasta nijansa.
- Dan: Niska alpha vrednost (svetlo), žuta nijansa.
Čak sam dodao i efekat pomeranja za baklje koristeći jednostavnu sinusnu funkciju da blago modifikujem veličinu teksture svetlosti svakog frejma, čineći da vatra deluje živo.
Zaključak

Ova funkcionalnost je bila pravi test upornosti. Bilo bi lako zadržati se na geometrijskom pristupu i prihvatiti igru koja koči, ili jednostavno potpuno ukloniti osvetljenje. Ali povlačenjem, radom na drugim stvarima i vraćanjem sa svežom perspektivom (kao i učenjem o downscaling-u), pretvorio sam najveće usko grlo engine-a u njegov najbolji vizuelni detalj.