» »

DirectX 9.0 - Osvetljevanje

Tokrat si bomo ogledali eno izmed možnosti osvetljevanja v knjižnici DirectX 9.0. Ker tudi DirectX 9.0 še vedno pozna le osnovne osvetlitvene modele (flat in Gouraud), kljub temu da je strojna oprema že precej časa zmožna tudi česa več, si bomo pomagali s pixel shaderji 2.0. Treba pa je vedeti, da tudi v prihodnosti DirectX ne bo podpiral kakšnih boljših modelov osvetljevanja, saj bodo zato skrbeli osenčevalniki (shaderji op. p.) (kar je dobro). V bistvu si bomo ogledali, kolikšna je razlika med istim osvetlitvenim modelom, če ga izvajamo na vsakem oglišču ozirom na vsaki točki na zaslonu. Upam, da boste vsi po malce bolj burnih treh odstavkih zopet razumeli, o čem teče beseda.


Difuzna osvetlitev

Celoten osvetljevalni model je običajno sestavljen iz dveh delov: difuznega (svetloba, ki se ob odboju razprši) in odbojnega (svetloba, ki se samo odbije). Za difuzno osvetlitev se običajno uporablja Lambertov zakon, ki pravi, da je osvetlitev enaka kosinusu kota med normalo (vektor pravokoten na površino) in smerjo luči. Če ta račun izvedemo za vsako oglišče trikotnika, dobimo praktično klasično Gouradovo senčenje (brez odbojev). Da lahko to izvedemo v praksi pa potrebujemo skalarni produkt, ki ga lahko večina grafičnih kartic (vse, ki podpirajo pixel shaderje, ter vsi GeForcei, Radeoni,...) že izvaja za vsako piko na zaslonu. Skalarni produkt nam namreč vrne kosinus med dvema vektorjema, vendar moramo pri tem paziti, da sta oba vektorja dolžine 1, sicer se stvari rahlo zapletejo, saj bi morali v nasprotnem primeru deliti rezultat še z obema dolžinama.

Pot do difuzne osvetlitve je zelo enostavna:

dp3 r0, r1, c0

To kodo lahko izvajamo ali v vertex shaderjih ali v pixel shaderjih. V register r1 samo shranimo normalo (ki jo lahko dobimo podano v vertex shaderjih ali pa jo preberemo iz teksture v pixel shaderjih) ter v register c0 smer iz katere prihaja svetloba. Da stvari ne zakompliciramo preveč, bomo tukaj delali samo z usmerjenimi lučmi (directional light).


Odbojna osvetlitev

Odbojna komponenta (svetloba, ki se od površine odbije neposredno) je bolj zapletena. Najbolj popularna metoda za računanje odbojnih odsevov je Phongov odbojni model, ki pravi, da je osvetlitev enaka kosinusu kota med normalo in tako imenovanim "half way" vektorjem potenciranim na stopnjo sija. Na prvi pogled je ta model podoben Lambertovemu zakonu (zopet se zanašamo na kosinus oziroma na skalarni produkt vektorjev), vendar se "half way" vektor obnaša precej drugače kot vektor, ki predstavlja smeri luči. Da izračunamo ta "half way" vektor pa potrebujemo kar nekaj matematike, vendar bomo za naš primer (ker uporabljamo le usmerjene luči) celoten izračun precej poenostavili.

Poenostavljeno lahko half way vektor izračunamo takole:

add r0, t1, c0 dp3 r0.w, r0, r0 rsq r0.w, r0.w mul r0, r0, r0.w

To kodo lahko poganjamo v vertex shaderjih ali pa v pixel shaderjih 2.0 ali kasnejših. Prvi ukaz sešteje smer v katero kaže luč in koordinato oglišča, ostali trije pa poskrbijo, da je dolžina vektorja 1.


Ko spravimo oboje skupaj ...

Pa smo prišli čez najbolj buren del. Za dokončanje projekta moramo samo še sešteti vrednosti, ki ju dobimo, ko difuzno in odbojno komponento pomnožimo z neko barvo (barva difuzne komponente se lahko razlikuje od barve odbojne komponente) in zgodbe je praktično konec.

Tak model poznajo in uporabljajo vse grafične kartice, ki pospešujejo transformacije in osvetljevanje (transformation & lighting -- T&L), če pa kartica tega ni zmožna, pa bo Direct3D ali OpenGL programsko uporabil ta model med T&L fazo. Če pa smo fini, lahko tak model sprogramiramo tudi sami v vertex shaderjih ali pixel shaderjih.

Ker pa je ta model možno v celoti poganjati za vsako piko na zaslonu šele v pixel shaderjih 2.0 (ki pa so za zdaj podprti le na Radeonih 9500 in več ter GeForceih FX), se ga programerji iger izogibajo. Zakaj?


Točke vs. oglišča trikotnikov

Kot je že nekako razvidno iz članka, lahko ta model poganjamo za vsako oglišče naših trikotnikov na praktično vsaki grafični kartici (če ga pa že ne moremo pa lahko vedno vskoči CPU), vendar je bilo to v pospešeni 3D praksi (v igrah) uporabljeno bolj malokrat. Programerji so se vse od Voodoo 1 skoraj vedno odločili, da bodo osvetlitve zapekli kar na teksture ter na CD skupaj z igro. Ta metoda lahko izgleda zelo dobro (saj lahko izdelovalci igre porabijo veliko procesorskega časa za algoritme kot so radiosity in sledenje žarku (ray tracing)) vendar je precej omejena za premikanje. Če bi hoteli z našo metodo doseči približno enako kakovost senčenja, bi morali bistveno povečati število trikotnikov (kar bi bilo preveč še za tako hitre grafične kartice ter procesorje) ali pa premakniti precej računskih nalog iz oglišč (Per-Vertex) na zaslonske pike (Per-Pixel), kar pa je možno šele na zadnji generaciji grafičnih kartic.

Osvetlitveni model, predstavljen v tem članku, je precej podoben tistemu iz Doom 3. Razlika je predvsem v tem, da se za Doom 3 uporabljajo t.i. "normal mape" (točke na teksturi predstavljajo vektorje, ki jih vnesemo v izračun osvetlitve) kar omogoča bump mapping, ter da je John Carmack bolj prijazen in je vse skupaj napisal tako, da deluje na več grafičnih karticah. Ta model je namreč možno v celoti implementirati s pixel shaderji 1.4 (Radeon 8500) in skoraj v celoti na GeForce 3 (kjer moramo del izračuna za odbojno komponento prestaviti v vertex shader), vendar zato potrebujemo nekaj trikov s teksturami. Predstavitveni program na koncu tega članka pa ne uporablja nobenih tekstur.

Kaj je glavni problem izračunavanja tega modela v vsakem oglišču trikotnika in ne na vsaki točki zaslona? Če izračunavamo osvetlitev v vsakem oglišču (Per-Vertex), moramo osvetlitev interpolirati čez celoten trikotnik. To pa je podobno, kot če pozabimo na dejstvo, da morajo biti vektorji (normale) vedno dolžine 1, da se skalarni produkt obnaša kot kosinus. Rezultat je, da so točke znotraj trikotnika običajno preveč temne in so vidni ostri robovi med trikotniki. Problem je še posebej izrazit pri odbojni komponenti, ki je tako praktično neuporabna.

Če sta vektorja a in c dolžine 1 je vektor b običajno krajši od 1, kar povzroči, da je točka z vektorjem b pretemna

Da se izognemo anomalijam znotraj trikotnikov, moramo vse vektorje, ki jih uporabimo v izračunu osvetlitve, normirati (vektor delimo z njegovo dolžino in tako dobimo vektor dolžine 1). To lahko storimo v pixel shaderjih 2.0 z matematiko ali pa s pomočjo tekstur (cube mapping, ki ga najdemo na vsaj GeForceih in Radeonih). Primerjava med modelom, ki ga izračunavamo za vsako oglišče (v vertex shaderjih), in istim modelom, ki ga izračunavamo za vsako točko (v pixel shaderjih):

Per-Vertex (vertex shader 1.1) osvetljevanje:
Per-Pixel (pixel shader 2.0) osvetljevanje:
Razlika je očitna.

Za vse radovedneže pa še osenčevalniki iz dema:

Koda za vertex shader 1.1:
vs_1_1
 
dcl_position0 v0
dcl_normal0 v1
 
/* Transformiramo oglišče 
   v zaslonski prostor */
m4x4 oPos, v0, c0
 
/* Transformiramo normalo
   z inverzom rotacije */
m3x3 r0, v1, c4
mov oT0.xyz, r0
 
// Transformiramo oglišče
m4x4 r1, v0, c8
mov oT1.xyz, r1
 
// Izračunamo halfway vektor
add r1, -r1, -c16
dp3 r1.w, r1, r1
rsq r1.w, r1.w
mul r1, r1, r1.w
 
// Izračunamo osvetlitev
dp3 r2.x, r0, -c16
dp3 r2.y, r0, r1
mov r2.w, c17.w
lit r0, r2
 
mad oD0, r0.y, c17, r0.zzzz
Koda za pixel shader 2.0:
ps_2_0
 
dcl t0	// Normala
dcl t1	// Transformirano oglišče
 
// Normaliziramo normalo
dp3 r0.w, t0, t0
rsq r0.w, r0.w
mul r0, t0, r0.w
 
// Izračunamo halfway vektor
add r1, -t1, -c0
dp3 r1.w, r1, r1
rsq r1.w, r1.w
mul r1, r1, r1.w
 
// Izračunamo osvetlitev
dp3_sat r1, r0, r1	// N dot H
dp3_sat r0, r0, -c0	// N dot L
pow r1, r1.x, c1.w
mad r0, r0, c1, r1
 
mov oC0, r0

Zaključek

Naj še enkrat poudarim, da je edina bistvena razlika v tem, da se pri običajni (linearni) interpolaciji vektorji "pokvarijo" in jih moramo zato za vsako piko posebej nekako popraviti. Upam, da mi je uspelo vse dokaj človeško razložiti, tako da sedaj veste kaj več. Za vse ponosne lastine Radeonov 9500 in boljših ter GeForceov FX pa je tukaj še predstavitveni programček (izvorna koda). Poganjate ga lahko vsi (praktično edina zahteva je DirectX 9.0) vendar bodo le lastniki DirectX 9.0 kartic videli v čem je dejansko razlika. S tipko F1 lahko tako preklapljate med obema načinoma (osvetlitev z vertex shaderjem ali pixel shaderjem), če pa se hočete prepričati, da ne guljufam in razmetavam s trikotniki, pa lahko pritisnete tipko F2 in si pogledate žični model.

Čudežno popotovanje skozi grafični cevovod II

Čudežno popotovanje skozi grafični cevovod II

V prvem delu smo si ogledali strukturo sodobne grafične kartice. Med drugim smo omenili shaderje, ki so zaradi svoje programabilnosti, nekakšno srce in duša sodobnih GPU-jev. Različni shaderji so odgovorni za različne strukture v grafiki (v Direct3D 10 so to oglišča, ...

Preberi cel članek »

Novosti DirectXa 9 - 1. del

Novosti DirectXa 9 - 1. del

No pa smo vendarle dočakali novo, dolgo pričakovano, različico knjižnice DirectX, ki jo je zlobni Microsoft, bolj ali manj skrbno skrival pred radovednimi očmi javnosti. V približno enaki tajnosti sta nastala tudi ta dva članka, ki vam bosta, vsaj upam tako, uspela ...

Preberi cel članek »

Nova bitka v vojni grafičnih kartic

Nova bitka v vojni grafičnih kartic

Da je računalništvo hitro se razvijajoča panoga čivkajo že vrabčki na vejah. Da se grafične kartice razvijajo še hitreje od ostalih področij znotraj računalništva, je takisto lahko jasno vsakemu, ki vsaj vsake toliko preleti kakšen cenik ...

Preberi cel članek »

ATI Radeon 9700 - Nov kralj v grafični deželi

ATI Radeon 9700 - Nov kralj v grafični deželi

Če ne živite ravno v kakšnem zakotnem pragozdu ter lahko berete ta članek, potem ste gotovo že slišali, da je ATi izdal nov grafični čip R300, ki so ga poimenovali Radeon 9700. Kot verjetno že veste, novi Radeon s precejšjno lahkoto pomete z vso konkurenco ...

Preberi cel članek »

Register Combiners vs. Pixel Shaders

Register Combiners vs. Pixel Shaders

Tokrat bo članek nekoliko bolj tehnične narave. Namen tega članka pa je prikazati kontrast med register combinerji (na primeru NVidine implementacije) in pixel shaderji v DirectX 8.x oziroma klasičnim SetTextureStageState mehanizmom v DirectXu. Za vse, ki se bodo ustrašili, ...

Preberi cel članek »