Monitoare - modul de functionare si redare a
imaginii
Informatii generale
2.1 Vedere stereoscopica
Oamenii
si multe animale au doi ochi care privesc (aproximativ) in aceeasi directie.
Fiecare din ochii nostri vede lumea dintr-un alt punct de vedere, de aceea
imaginile obtinute cu ambii ochi sunt usor diferite. In creier, aceste 2
imagini sunt combinate intr-o singura imagine tri-dimensionala (model mental)
(vezi figura 2.1).
Figura 2.1 Vedere stereoscopica [1]
Acest
tip de sistem de vedere este numit vedere binoculara
(sau stereoscopica). In natura este comuna la multe
animale pradatoare, dar de asemenea si printre primate care necesita sa
parcurga medii tri-dimensionale complexe. Este una din metodele folosite de oameni
pentru a percepe adancimea si de a estima distantele.
Un
alt tip de sistem de vedere este vederea monoculara,
care este comuna printre multe din animalele vanate
in natura. Multe dintre ele au ochii de ambele parti ale capului pentru a avea
un camp de vedere mai larg. Aceasta reduce sansa ca un pradator sa le prinda.
Imaginea primita de fiecare ochi este procesata
separat in creier.
2.2 Perceperea adancimii
Vederea
stereoscopica este mecanismul principal de percepere
a adancimii, dar nu este singurul. Exista si alte
mecanisme [3]:
Paralaxa de miscare: Can iti misti capul dintr-o parte in alta, obiectele care sunt apropiate tie se misca
relativ mai repede decat obiectele care sunt mai indepartate.
Interpunere: Un obiect care blocheaza un alt obiect se afla in fata acelui obiect.
Lumina si umbra: Umbrele ofera informatii despre forma
tri-dimensionala a unui obiect. Daca se cunoaste pozitia sursei de lumina umbra
poate de asemenea sa ofere informatii despre pozitia obiectului in sine.
Marimea
relativa: Obiectele de aceeasi marime
aflate la distante diferite sunt percepute de catre ochi ca avand marimi
diferite. Aceasta relatie marime-distanta ofera informatii despre distanta
obiectelor a caror marime este cunoscuta. Intr-o
imagine plana acest effect poate fi redat prin utilizarea proiectiei de
perspectiva.
Neclaritate data de distanta: Obiectele indepartate apar neclare.
Toate
aceste mecanisme sunt folosite impreuna de catre creierul nostru pentru a
percepe adancimea.
2.3 Monitoare 3D
Cand
privesc o imagine traditionala, ambii ochi vad aceeasi imagine. Desi aceasta
imagine plana poate contine mai toate indicatiile despre profunzime, ii
lipseste cea mai importanta: efectul stereoscopic. Acest efect poate fi redat
prin oferirea unei imagini diferite ambilor ochi. Un monitor care reuseste
acest lucru se numeste monitor
stereoscopic.
2.3.1 Disparitatea ecranului
Diferenta
dintre cele doua imagini de pe ecran este
interpretata de catre creier ca fiind adancime (vezi figura 2.2. si 2.3).
Figura
2.2. Adancimea perceputa in spatele ecranului
Figura
2.3. Adancimea perceputa in fata ecranului
Distanta
dintre punctele de imagine stanga si dreapta corespunzatoare este numita disparitatea ecranului, si poate fi masurata in
milimetri sau pixeli. Poate fi clasificata in trei categorii:
Disparitate
pozitiva - obiectele apar in spatele ecranului (figura 2.2)
Disparitate zero -
obiectele apar in planul ecranului
Disparitate
negativa - obiectele apar in fata ecranului (Figura 2.3)
Disparitatea limitativa a ecranului
Disparitatea
folosita la un monitor stereoscopic trebuie sa fie restrictionata doar la
disparitatea orizontala. Disparitatea verticala cauzeaza incordarea ochilor
[15].
Folosind valori mari
(pozitive si negative) ale disparitatii la un monitor 3D cauzeaza discomfort de
vedere. Aceasta are de-a face, printre altele, cu relatia acomodare/convergenta
[5]. Creierul nu poate contopi cele doua imagini daca ele sunt prea diferite
una de cealalta. Lucrul acesta nu va fi discutat mai departe in aceasta
lucrare. Noi folosim doar rezultatele care mentioneaza ca disparitatea trebuie
sa fie limitata la un anumit interval care este comfortabil vederii. Disparitatea maxima depinde de tipul de ecran 3D.
2.3.2. Adancimea perceputa
Adancimea
perceputa P este in functie de
distanta de vedere z, separarea
ochilor e si de disparitatea
ecranului d (vezi figura 2.4.)
Figura 2.4 Adancimea perceputa este data de
urmatoarea funtie: [4]
(2.1)
Distanta
dintre ochi este de 64 milimetri in medie pentru adulti [5]. Sa presupunem ca
distanta de vedere este de 1 metru. Aceasta are ca
rezultat graficul din figura 2.5. Pentru valorile lui d care tind spre zero, raportul e/d
va deveni ∞, facand ca P sa
devina zero. Pentru valorile lui d
care sunt apropiate de e, adancimea
perceputa va tinde catre infinit (care cauzeaza discomfort de vedere).
2.3.3 Separarea imaginii
Pe
un monitor stereoscopic cele doua imagini necesare pentru ambii ochi sunt
afisate intr-o anumita masura "simultan". Tot ceea ce este
nevoie mai departe este sa se asigure ca fiecare
imagine sa ajunga la ochiul corect. De obicei lucrul acesta se realizeaza
oferindu-i privitorului un tip de ochelari care realizeaza separarea ambelor
imagini. Lucrul acesta este necomfortabil si este unul dintre motivele pentru succesul limitat al
monitoarelor stereoscopice pentru consumatori [3, 5].
Figura
2.5. Adancimea perceputa in spatele ecranului in functie de disparitatea
ecranului (pentru o distanta de vedere de 1 metru si o distanta dintre ochi de
64 milimetri).
2.4 Monitoare Philips 3D
Philips
Research a dezvoltat cateva monitoare autostereoscopice
[9]. Aceste monitoare nu necesita ochelari speciali si pot fi privite simultan
de cativa privitori. Un monitor 3D este de fapt un
monitor LCD plan cu un strat de lentile cilindrice (lenticulare, vezi ficura
2.6) amplasate in fata ecranului.
Figura 2.6: Placa cu lentile
(elipsa este marita) [9].
Fiecare
pixel in LCD consta din trei sub-pixeli: rosu, verde si albastru. Intr-un
monitor normal fara lentile, toti sub-pixelii emit lumina in toate directiile.
Intr-un monitor cu lentile lumina fiecarui sub-pixel este
directionata intr-o singura directie si poate fi vazuta dintr-un unghi limitat
de vedere din fata monitorului.
Datorita
lentilelor din fata monitorului, fiecare ochi vede un set diferit de sub-pixeli
pe monitor. Acesta poate fi folosit pentru a prezenta fiecarui ochi o imagine
diferita. Lucrul acesta face ca vederea stereoscopica sa ofere fiecarui ochi o
imagine a aceleiasi scene dintr-un punt de vedere putin diferit.
2.4.1 Imagini multiple
Imaginea
care este vizibila dintr-un anumit unghi de vedere
in fata monitorului se numeste vedere.
Numarul minim de vederi pe care un monitor stereoscopic trebuie sa-l ofere este doua (una pentru fiecare ochi). Monitoarele create de Philips prezinta in mod characteristic mai mult de doua
vederi.
Monitorul
3D (exemplu) folosit in continuare in prezentul raport este
un LCD de 20 inch cu o rezolutie nativa de 1600 x 1200 (UXGA). Este
un ecran cu noua vederi, ceea ce inseamna ca poate arata noua imagini diferite
din unghiuri diferite de vedere. Intervalul de adancime perceput este
de aproximativ 10 cm in fata ecranului si 15 cm in spatele ecranului. Folosind
disparitati mari ale ecranului pentru
a crea o o profunzime mai mare face ca imaginea sa fie neclara, dar aceasta
depinde de asemenea de continut (luminozitate, exactitate etc), asa deci aceste
numere sunt doar o sugestie. Retineti ca toate proprietatile tehnice mentionate
in prezenta lucrare (numere, etc) sunt specifice acestui tip de ecran. Exista
alte ecrane cu rezolutii, numar de vederi diferite, etc.
Avand
un monitor 3D cu mai mult de doua vederi prezinta cateva avantaje, si
bineinteles si unele dezavantaje, dupa cum vom vedea in continuare.
Avantaje
In
primul rand, privitorul are o libertatea de vedere mai mare. Cand priveste un
monitor cu doar doua vederi (fara ochelari), utilizatorul trebuie sa stea cu mare
precizie in pozitia corecta in fata ecranului, altfel efectul stereoscopic se
va pierde. Pentru a rezolva aceasta problema se foloseste dispozitivul de
urmarire a pozitiei capului, dar aceasta functioneaza doar pentru o singura
persoana, si prin urmare nu este considerata o
tehnologie potrivita pentru piata consumatorilor [3]. Avand vederi multiple
face ca efectul 3D sa fie vizibil dintr-un unghi de vedere mai mare si astfel
permite mai multor persoane sa vada ecranul simultan.
Al doilea avantaj in a avea mai multe vederi este
acela ca utilizatorul poate sa experimenteze paralaxa de miscare, de exemplu
"sa privesti obiectele din jur", prin miscarea orizontala a capului.
Dezavantaje
Un
mare dezavantaj de a avea vederi multiple este
rezolutia scazuta pentru fiecare vedere. Rezolutia originala a ecranului este divizata la numarul de observatori, de aceea un monitor
2D cu rezolutie mare este necesar pentru a crea un
monitor cu imagini multiple cu rezolutie mica.
Un
alt dezavantaj al primei generatii de monitoare 3D este
acela ca ele pot fi folosite doar pentru a afisa un continut 3D. Un continut 2D
normal arata aceptabil doar la o rezolutie mica. Aceasta problema a fost
rezolvata prin dezvoltarea de lentile manevrabile astfel incat monitorul poate
fi inca folosit ca un monitor 2D obisnuit. Facand lentilele manevrabile pe
fiecare regiune, va fi posibil chiar sa se afiseze continutul 2D si 3D in
acelasi timp.
Zone de vedere
Figura
2.7 prezinta cum cele 9 vederi sunt afisate pe ecran. Fiecare vedere este vizibila dintr-un unghi specific (in mod characteristic
4 grade) din fata ecranului. Cele 9 vederi creeaza un "evantai" de 36 de grade.
Evantaiul "primar" este vizibil de la -18 pana la
+18 grade (0 grade fiind perpendicular pe ecran). La stanga si la dreapta
acestuia este repetat acelasi evantai de 9 vederi.
Dintr-o pozitie din fata ecranului doar 1/9 din sub-pixeli sunt vizibili
(pentru fiecare ochi). Datorita lentilelor sub-pixelii sunt mariti si intreg
ecranul este umplut tot timpul.
Figura 2.7 Vederi multiple (se
prezinta un monitor cu 7 vederi in loc de 9) [9].
Suprapunere
si dublarea imaginii
Vederile
adiacente nu sunt complet separate: exista o anumita acoperire intre doua
vederi adiacente care se numeste suprapunere.
Suprapunearea face ca fiecare ochi sa nu vada exact o vedere, dar si ceva din
cele 2 vederi adiacente. Este cauzata de optica
lentilelor care permite vederilor sa migreze usor de la una la cealalta. Un
mare dezavantaj al suprapunerii este dublarea imaginii, care are loc atunci cand
doua vederi adiacente difera prea mult una de cealalta. Este
mai dificil pentru creier sa uneasca intr-o singura imagine cele doua imagini
primite de catre ochi.
2.4.2 Dispunerea sub-pixelilor
Figura
2.8 arata cu exactitate cum sunt plasate lentilele pe ecran. O suprafata mica a
ecranului este marita si sub-pixelii specifici sunt
vizibili. Trei lentile sunt vizibile in fata ecranului. Lentilele nu sunt
aliniate exact vertical, ci sunt inclinate la un unghi mic. Aceasta din doua
motive majore: cauzeaza o mai buna distributie intre rezolutia orizontala si
cea verticala a fiecarei vederi si reduce trasaturile Moir [7].
In
functie de locatia orizontala a unui sub-pixel in spatele lentilei, lumina
acelui sub-pixel este directionata intr-un anumit
unghi. Acest unghi este diferit de cel al
sub-pixelilor invecinati.
Numarul
din fiecare sub-pixel (precum este prezentat in
figura 2.8) este numarul vederii. Un sub-pixel este vizibil doar din vederea sa asociata. Daca privim
ecranul dintr-un anumit unghi (cu un singur ochi) vedem de exemplu toti
sub-pixelii, de exemplu, cu vederea numarul 5. Acesti sub-pixeli formeaza un
gratar rugos in imaginea totala asa cum este
prezentat in figura 2.9.
Pentru
a prezenta o imagine in vederea 5 pe ecran toti sub-pixelii cu acest numar
trebuie sa fie incarcati cu informatii despre imagine (culorile). Acest proces este descris in sectiunea urmatoare.
Sub-pixelii
160012003
pe ecran sunt distribuiti intre cele 9 vederi. Fiecare vedere consta
din 160012003/9 sub-pixeli. Aceasta corespunde unei rezolutii efective de
533x400. Atat rezolutia orizontala cat si cea verticala sunt impartite la 3.
Rezolutia perceputa a monitorului este cumva mai
mare datorita efectului de suprapunere intre imaginile adiacente si deoarece
privitorul vede doua vederi in acelasi timp (cu doi ochi).
Figura
2.8 Fiecare sub-pixel are un numar de vedere [9].
Figura 2.9. Sub-pixelii vizibili pentru vederea 5 (accentuata) formeaza un
gratar.
2.4.3 Pregatirea imaginii pentru monitorul 3D
Procesul
de luare a noua imagini de intrare (una pentru fiecare vedere) si combinarea
lor in asa maniera incat ele pot fi prezentate pe un monitor 3D se numeste interpolare.
Datele
introduse pentru procesul de interpolare sunt cele 9 imagini introduse, iar
rezultatul este o imagine rezultata. Deoarece
rezolutia efectiva per imagine este 533x400,
imaginile introduse ar trebui sa aiba aproximativ aceasta rezolutie, sau si mai
bine: o rezolutie mai mare. Aceasta face ca imaginea rezultata sa fie mai
intensa. Rezolutia imaginii rezultate este
intotdeauna 1600x1200.
Un
algoritm de interpolare foarte simplu este
urmatorul: trecerea peste imaginea rezultata si pentru fiecare sub-pixel se
creeaza o mostra a imaginii introduse corespunzatoare. Procesul este
vizualizat in figura 2.11 si pseudocodul este
prezentat in figura 2.10. Se apeleaza la mostra din pseudocod cand este necesara o valuare de culoare din imaginea introdusa. O
implementare simpla este prezentata, unde se
ajusteaza h si v la numarul intreg cel mai apropiat. Aceasta produce un rezultat
cu o calitate rezonabila. O versiune mai buna ar folosi luarea de noi probe si
filtrarea acestora pentru a obtine o imagine rezultata de o calitate mai mare.
Interpolare
(byte Introdus [9] [wIn] [hIn] [3],
byte Rezultat [wOut] [hOut] [3] )
Proba
(int Nr vedere, float h, float v, int s)
Figura 2.10.
Pseudocod pentru interpolarea a noua imagini introduse intr-o singura imagine
rezultata.
Cand procesul este
complet, imaginea rezultata contine cele 9 imagini introduse intr-un format
interpolat. Cand aceasta imagine este prezentata
intr-un monitor 2D normal toate vederile vor putea fi vizibile in acelasi timp.
Arata ca o imagine cetoasa ciudata dupa cum este
prezentat in figura 2.12. Cand imaginea este afisata
pe un ecran 3D fiecare ochi ajunge sa vada doar o singura imagine.
Imagini 3D
Pentru a afisa o imagine 3D pe
ecran primele 9 imagini trebuie sa fie formate dintr-o scena folosind pozitii
diferite ale camerei de filmat. Apoi aceste 9 imagini individuale trebuie sa
fie combinate (intercalate/interpolate) intr-o singura imagine care este trimisa la monitor.
Imaginea care este
afisata in fiecare vedere trebuie sa fie o imagine a scenei facuta dintr-o
anumita pozitie a camerei de filmat. Vederile sunt numerotate de la 1 la 9.
Vederea 1 trebuie sa contina imaginea primita de la pozitia camerei din extrema
dreapta iar Vederea 9 din extrema stanga. Miscarea camerei trebuie sa fie
orizontala pentru a simula deplasarea dintre ochii umani.
Cand un privitor se uita la
ecran ochiul lui stang ar putea, de exemplu, sa vada vederea 3 iar cel drept sa
vada vederea 4. Aceasta pereche de vederi este o stereo-pereche corecta. O problema poate
aparea daca utilizatorul este la marginea dintre
doua "evantaie". De exemplu cand ochiul sau stang vede vederea 9 si ochiul
drept vede vederea 1. Aceasta creeaza un
efect 3D incorect care poate fi incomod de privit. Utilizatorii pot invata sa
se indeparteze de la o astfel de pozitie de vedere.
Figura 2.11: Informatia despre culoare pentru un
pixel in imaginea rezultata vine din trei imagini introduse (trei vederi)
Figura 2.12: Exemplu de imagine
rezultata interpolata. (Un peste este prezentat in
fata unui fundal).
Grafica
3D pe calculator
In grafica 3D pe calculator un
model tri-dimensional al unei scene este storat
intr-un calculator cu scopul de a efectua calcule si de a reda imagini. In
aceast model tri-dimensional o camera poate fi pozitionata si o fotografie
virtuala creata. Aceasta creeaza imaginea 2D a scenei 3D. Pentru a crea o
animatie sau un film cateva imagini (numite cadre) sunt create si expuse
succesiv.
Un numar de librarii software
sunt disponibile pentru a ajuta programatorii sa creeze aplicatii de grafica pe
calculator. O astfel de librarie consta
in fond din functii care efectueaza functii grafice. Functiile tipice includ
specificarea componentelor scenei (puncte, linii, poligoane), aplicarea transformarii
si selectarea vederilor dintr-o scena. Definitia tuturor functiilor intr-o
librarie se numeste Application Programming Interface
API.
OpenGL si Direct3D sunt doua API-uri
cunoscute de grafica pe calculator. Majoritatea cardurilor grafice ofera
accelerarea hardware pentru aceste API-uri, care inlesneste redarea in timp
real a scenelor 3D complexe. Implementarea librariilor grafice este
efectuata partial in software si (posibil, nu neaparat) partial in hardware.
Intregul sistem se numeste redarea
pipeline-ului. Consta in stagii diferite care efectueaza operatii pe date.
Incepe prin formele geometrice de baza generate de catre aplicatie si se
termina cu desenarea pixelilor pe ecran.
2.5.1 Grafica pe calculator pentru monitoarele 3D
Aplicatiile 3D "normale" (sau
jocurile) creeaza doar o singura vedere pentru fiecare cadru dintr-o secventa.
Pentru a folosi o astfel de aplicatie pe un monitor 3D nu una, ci noua vederi
sunt necesare pentru a fi create si combinate intr-o singura imagine care sa
fie potrivita pentru monitorul 3D. O solutie ar fi implementarea suportului
pentru monitorul 3D in fiecare aplicatie in sine. Aceasta ar necesita
schimbarea fiecarei aplicatii individuale, care de asemenea nu este
nici de dorit si nici posibila.
O abordare alternativae este implementarea suportului in grafica API. Aceasta va
face posibil ca toate programele existente sa foloseasca functionalitatea
monitorului 3D. A fost aleasa aceasta
abordare. In prezent, doar aplicatiile OpenGL sunt suportate prin intermediul
asa numitului OpenGL wrapper.
Inainte de a explica aceasta
abordare, sunt necesare cateva cunostinte ale altor doua subiecte: dubla
stocare (memorare) si nuantatori procedurali. Acestea sunt explicate in
urmatoarele doua sectiuni. In urmatorul capitol OpenGL wrapper-ul este discutat.
2.5.2 Dubla stocare (memorare)
Momeria unui card grafic care
contine pixelii care sunt expusi in prezent pe ecran se numeste framebuffer
(memorie temporara de cadre). Framebuffer-ul este de
fapt o colectie de buffers (zone de memorii temporare): buffer de culoare (fata si spate) z-buffer, buffer sablon, etc.
Majoritatea aplicatiilor
grafice interactive folosesc o tehnica numita dubla stocare. Aceasta este
o metoda in care se folosesc doua framebuffer-uri (de culoare): un front buffer
de culoare vizibil si un back buffer de culoare invizil. In timp ce front
buffer-ul este afisat, urmatorul cadru este redat in back buffer. Cand noul cadru este
terminat, cele doua zone de memorie sunt inter-schimbate. Folosirea de dubla
stocare face posibil ca privitorul sa vada o imagine perfecta tot timpul.
Grafica API, inclusiv OpenGL, ofera suport pentru dubla stocare. Cand o
aplicatie a redat un cadru complet, trebuie sa faca apel la functia care shimba
cele doua zone de memorie. In continuarea acestui raport, vom numi aceasta
functie swapbuffer.
2.5.3 Shader programabile
In trecut, componentele grafice
obisnuiau sa aiba o functie fixa care sa redea pipeline-ul. Aceasta s-a
schimbat recent cu aparitia Unitatilor
de Procesare Grafica Programabile (Graphics Processing Units
- GPU). Acestea permit catorva stagii ale redarii pipeline-ului sa fie
programabil. Un program care functioneaza pe un GPU se numeste shader. Toate
tipurile de efecte frumoase pot fi redate cu shader iar jocurile recente le folosesc in
proportii mari.
O aplicatie grafica trebuie sa
activeze un program shader cand vrea sa-l foloseasca (si sa-l dezactiveze dupa
aceea). Cand un program shader este activ, el
inlocuieste o parte din functionalitatea fixa a pipline-ului.
Exista doua tipuri de shader-e:
shader de varf (vertex shader) si pixel shader. Numele lor corespunde cu
pozitia lor in pipeline (si astfel tipul de date pe care ele opereaza). Pixel
shader sunt uneori numite si shaders de fragment. In restul acestui document
noi vom folosi numele de pixel shader. Figura 2.13 prezinta un model de redare
a pipeline-ului si locul ambelor tipuri de shaders. Sagetile reprezinta
curgerea datelor.
Figura 2.13
Vertex si pixel shader-i in redarea pipeline-ului [8].
Procesoarele programabile (de
varf si de pixel) in interiorul GPU lucreaza pe un curent de date. Fiecare
processor citeste un element din curentul de date de intrare, executa programul
shader care opereaza pe aceste data, si scrie un element in curentul de date
rezultat. Un program shader este executat o data
pentru fiecare element din curentul de date. Nu sunt cunostinte despre
elementele precedente sau urmatoare. Aceasta asigura ca programele shader sa
fie usor executate in paralel pe hardware. Shaders de varf pot personaliza
geometria pipeline-ului. Shaders de pixel lucreaza mai tarziu in pipeline si
pot controla cum este determinata culoarea finala a
pixelului.
Shader-e de varf
Varfurile trimise de catre
aplicatie sunt procesate de catre shader-ul de varf. Fiecare vertex (varf) are
o coordonata (x, y, z, w) si cateva atribute. Exemple de atribute de varf sunt
culoarea, vectorul normal si coordonatele de structura. Un vertex shader poate
sa modifice doar pozitia atributelor unui varf, nu poate sa creeze sau sa
stearga varfuri.
Rasterizer (Dispozitiv care converteste imaginea
vector in format bitmap)
Rezultatul unui vertex shader
ajunge la ansamblul de fond si apoi la raster. Aceste parti ale canalului de
prelucrare a datelor (pipeline) nu sunt programabile. Ansamblul de fond
construieste triunghiuri din varfuri. Rasterul imparte aceste triunghiuri in
pixeli. In timpul acestei operatii, el de asemenea interpoleaza (in mod linear)
atributele fiecarui vertex.
Pixel shader
Rezultatul rasterului (pixeli
cu atributele asociate interpolate) formeaza datele de intrare pentru pixel
shader. Pixel shader poate folosi aceste date de intrare pentru a determina
coluarea pixelului. Rezultatul pixel shader-ului (culoarea) este
apoi trimisa la unitatea de stocare test (framebuffer) pentru a actualiza
(posibil) un pixel real in framebuffer.
2.5.4. Limbaje de programare pentru shader
Un program shader poate fi
scris intr-un limbaj de ansamblu specific pentru un anumit tip de GPU si
grafica API sau intr-un libaj de nivel inalt general. Aceste limbaje inalte includ
C pentru Grafica de la Nvidia (Cg), High-Level Shading Languange de la
Microsoft (HLSL) si OpenGL Shading Language (GLSL). Folosind un limbaj de nivel
inalt are avantajele obisnuite de portabilitate, productivitate crescuta a
programatorului, refolosire usoara a codului, etc. In softul nostru Cg este folosit, prin urmare este
discutat in mai multe detalii mai jos.
Cg
Cg este dezvoltat de Nvidia dar nu este
specific pentru GPU-ul lor. Este un limbaj cu scop
general concentrat pe hardware. Codul
Cg arata aproape ca si codul C, cu
aceeasi sintaxa pentru declaratii, solicitare de functie, si majoritatea
tipurilor de date. Un program Cg este portabil de la
o generatie de hardware la alta, de la
un sistem de operare la altul, si grafica API [11].
Deoarece capabilitatile GPU
cresc rapid, exista diferente majore intre generatii diferite de grafica
hardware. Cg expune aceste diferente de hardware prin intermediul profilelor de limbaj. Un profil
specifica subsetul limbajului Cg complet care este
suportat de un anumit procesor. Exista profile diferite pentru procesoare de
vertex si de pixel, pentru clasa hardware DirectX 8 si 9, etc.
Cg are suport pentru tipuri de
date scalare (precum float) si pentru tipurile vector si matrice (precum
float3, float4x4). Texturile sunt prezentate cu tipul special de sampler.
Limitari
Programele shader au cateva
limitari care sunt cauzate de hardware-ul pe care opereaza. Lungimea lor
(numarul de instructiuni) este limitat.
Indicatoarele nu sunt suportate. Exista mai multe limitari, dar acestea depind
de profilul shader-ului folosit.
Tipuri de input (date introduce)
Un program shader Cg poate avea
2 tipuri de input-ui: variabile si uniforme. Primul tip de input variaza cu
fiecare executie a shader-ului. Ca exemple sunt pozitia, vectorul normal si
coordonatele de structura. Al doilea
tip de input ramane acelasi pentru mai multe executari ale shader-ului. Aceasta
valoare este setata de aplicatie si ramane aceeasi
pana cand aplicatia seteaza o alta
valoare. Ca exemple sunt reflectivitatea si culoarea luminii.
Capitolul 3
Redari multiple
Pentru a folosi o aplicatie
grafica pe un monitor 3D sunt necesare 9 vederi de la fiecare cadru (in loc de
una). Aceasta
este in prezent (la inceputul
acestui proiect) realizata print folosirea unui OpenGL wrapper pentru a reda
fiecare cadru de noua ori si apoi sa foloseasca un pixel shader pentru a face
intercalarea.
Sistemul curent este
descris in acest capitol. Unele din dezavantajele sale au dus la insarcinarea
mea, care este descrisa in capitolul urmator.
3.1 OpenGL wrapper
OpenGL este o librarie de functii care poate fi folosita de
catre o aplicatie pentru a executa operatii de grafica. Pe o platforma Windows
codul tuturor acestor functii se gaseste in interiorul unei librarii de legatura
dinamica (Dynamic Link Library - DLL). O posibila modalitate de a
extinde functionalitatea OpenGL-ului este de a crea
un wrapper DLL. Un OpenGL DLL wrapper a fost dezvoltat de Philips Research [6].
Acest proces wrapper
functioneaza dupa cum urmeaza: opengl32.dll-ul original este
redenumit in wopengl32.dll. Un nou opengl32.dll ("wrapper"-ul DLL) este creat si care exporta aceeasi interfata OpenGL.
Aplicatiile care folosesc in mod normal opengl32.dll-ul original folosesc acum
in mod automat wrapper DLL-ul, deoarece are acelasi nume de fisier.
Comportamentul de baza al wrapper DLL-ului este sa inainteze
apeluri de functie catre DLL-ul original Dar
este de asemenea posibil sa
execute tot felul de functii inainte sau dupa apelul la functia originala.
Aceasta ne da posibilitatea sa extindem functionalitatea OpenGL-ului fara ca
aplicatia sa realizeze acest lucru. In figura 3.1 in stanga o aplicatie
apeleaza functiile OpenGL in opengl32.dll-ul normal. In dreapta o aplicatie
apeleaza functiile OpenGL in sau "prin" wrapper.
Pe alte platforme (de exemplu Linux) sau pe
alte API-uri grafice (de exemplu Direct3D) o abordare asemenatoare de librarie
wrapper este posibila.
3.2 Redare in format "tile"
Wrapper-ul ofera
functionalitatea care permite aplicatiilor OpenGL normale sa foloseasca
monitorul 3D. Aceasta functioneaza dupa cum urmeaza:
Wrapper-ul intercepteaza toate
functiile OpenGL care se transmit catre ecran. Apoi, in loc sa apeleze functia
de desenare o data, aceasta este apelata de noua ori. Inainte de fiecare
apelare, camera este re-pozitionata in scena. De
asemenea, se seteaza un viewport intainte de fiecare apelare.
Un viewport determina OpenGL-ul
sa se redea doar catre o parte specifica a ecranului, in loc de intreg ecranul.
Viewport-urile sunt setate dupa cum este prezentat
in figura 3.2. Numerele indica numerele de vedere. Numarul 1 este
vederea din extrema stanga si numarul noua este
vederea din extrema dreapta.
Figura 3.1: Wrapper-ul OpenGL DLL este
un strat intre aplicatie si OpenGL-ul insusi. (Sageata "redare scena"
reprezinta toate apelurile de functie OpenGL necesare pentru a reda un cadru
complet al unei scenei).
|
|
exrema dreapta
|
|
centru
|
|
extrema stanga
|
|
|
Figura 3.2: 9 Vederi in
format "tile"
Cand aplicatia termina de redat un cadru, ea
apeleaza functia SwapBuffers. La momentul respectiv, back buffer-ul contine
cele noua vederi redate in format tile (de placa). Fiecare vedere are o
rezolutie mica (1/9). Placile sunt un pas intermediar si nu sunt afisate
(datorita efectului de dubla memorare). Functia SwapBuffers
in wrapper DLL este extinsa cu functionalitatea de a
combina cele 9 vederi tile intr-o imagine intercalata potrivita pentru
monitorul 3D. Ea foloseste un pixel shader pentru aceasta functie. Cand interpolarea
este gata, este apelata functia
originala de SwapBuffers astfel incat imaginea intercalata este
afisata pe ecran.
3.3 Interpolare cu pixel shader
Dupa ce cele 9 vederi au fost redate in format
tile, este de datoria pixel shader-ului sa le
intercaleze.
Un pixel shader este
executat o data pentru fiecare pixel (x, y) in imaginea de iesire si functia sa
este de a calcula culoarea. Un pixel consta din trei
sub-pixeli si culoarea fiecarui sub-pixel trebuie sa derive dintr-o vedere
diferita (vezi figura 2.11).
Un tabel este
construit in prealabil care contine pentru fiecare pozitie (x, y) din imaginea
rezultata cele trei pozitii necesare din imaginea de intrare sub forma tile.
Acest tabel are o marime de 22MB .
Tabelul este storat ca sase structuri pe cardul grafic. Toate
cele sase structuri trebuie sa fie citite o data din fiecare cadru. Aceasta
necesita o latime de banda de memorie de 22MB pentru fiecare cadru.
Shaderul executa sase aporturi de structura si
aceasta rezulta in sase valori. Aceste sase valori reprezinta trei pozitii (x,
y). Apoi imaginea celor noua vederi "tiled" este etalonata
la aceste trei pozitii si cele trei colori rezultate sunt combinate pentru a
forma culoarea de iesire a pixelului. Codul Cg
pentru pixel shader se gaseste in apendixul A. 9.
Interpolarea trebuie sa aiba loc pe cardul grafic
insusi: transferarea imaginii redate la memoria principala si executarea interpolarii
pe CPU este prea inceata datorita latimii de banda asimetrica a Portului de
Grafica Accelerata (Accelerated Graphics
Port - AGP). Primirea de date de la el
este mult mai inceata decat trimiterea de date catre el. (Aceasta
problema va fi redusa cand cardurile grafice cu o interfata PCI Express vor
deveni obisnuite .
Problema latimii de banda nu este singurul motiv
pentru utilizarea graficii hardware, ea permite de asemenea folosirea de
filtere de textura hardware pentru re-etalonare. Realizarea re-etalonare pe CPU
va fi mult mai scumpa. Poate cel mai mare avantaj de folosire de grafica
hardware este paralelismul acesteia: CPU nu poate sa
faca fata acesteia.
Urmatorii pasi sunt realizati pentru a face pixel
shader-ul sa opereze:
- Salveaza
starea OpenGL curenta
- Copiaza
continutul memoriei video intr-o textura. Lucrul acesta este
necesar deoarece pixel shader-ul are nevoie de acces ca sa citeasca
imaginea din memoria video, lucru care in mod normal nu este
posibil.
- Seteaza o
proiectie ortografica.
- Activeaza
(bind) pixel shader-ul
- Deseneaza un dreptunghi
care umple complet ecranul. Datorita proiectiei ortografice pixel
shader-ul se executa o data pentru fiecare pixel din dreptunghi, care are
loc o data pentru fiecare pixel din ecran.
- Dezactiveaza
(unbind) pixel shader-ul si restoreaza starea OpenGL.
Un mare avantaj al redarii multiple este calitatea rezultatului. Nu exista
artefacte care sa rezulte din descoperiri sau transparente, lucru care va
deveni clar in sectiunea 5.3.
Poate ca cel mai mare dezavantaj al redarii
multiple este acela ca efectul de adancime nu este usor de ajustat. Valoarea adancimii percepute poate fi
controlata prin variarea distantei dintre cele 9 camere virtuale. Dar aceasta
nu ofera nici un control asupra cum intervalul de adancime din scena produce
adancimea perceputa pe monitor. Aceasta
este intr-adevar o mare problema:
de exemplu, nu este posibil sa obtinem un efect de
adancime bun atat pentru obiectele foarte apropiate de camera cat si (in
acelasi timp) pentru obiectele indepartate. In prezent, doar obiectele un pic
indepartate de camera obtin un efect de adancime bun. Obiectele foarte
apropiate de camera (de exemplu un pistol la primul tragator) are o disparitate
de ecran prea mare si cauzeaza discomfort de vedere. Aceasta este
"rezolvata" in prezent prin dezactivarea redarii pistolului in joc.
Un alt mare dezavantaj este
performanta neproductiva. Redand totul de 9 ori este
un factor mult mai incet decat redarea unei singure vederi. Timpul necesar
creste liniar cu numarul de poligoane (si cu numarul de vederi). Redarea
multipla creste incarcatura primelor stagii ale fluxlui de procesare (unde sunt
procesate varfurile). Creste de asemenea si incarcatura pe CPU. Jocurile
recente folosesc din ce in ce mai multe poligoane iar monitoarele viitoare cu
vederi multiple pot sa aiba mai multe vederi. Aceste dezvoltari sunt un
prospect negative pentru redarea multipla. De asemenea pixel shade-ul care face
interpolarea este incet: trebuie sa citeasca o textura
mare pentru fiecare cadru. O alta
implementare poate folosi o latime de banda mai mica pentru fiecare cadru cu pretul
a mai multor calcule.
Calitatea implementarii curente este
limitata de catre rezolutia imaginii intermediare in format "tiled". Rezolutia
pentru fiecare vedere este de 1/9, dar o rezolutie
cu putin mai mare ar fi mai buna. Acesata se poate realiza in viitor, dar
necesita o schimbare majora.
Implementarea curenta nu este
perfecta: nu toate functiile OpenGL au fost extinse ca sa suporte redarea 9x. Mai este
inca o problema nerezolvata inca: o aplicatie poate sa citeasca pixelii din
memoria video. Unele aplicatii fac lucrul acesta, de exemplu sa creeze o textura
dintr-o imagine care este redata in memoria video.
Aceasta este o problema pentru ca memoria video contine cele 9 vederi mici sub format
"tiled", iar aplicatia asteapta doar o singura vedere normala. Aceasta problema
ar putea fi rezolvata in viitor, prin schimbarea comportamentului unor functii
OpenGL din wrapper (vezi tabelul 7.1.).
Capitolul
Redarea RGBD
Redarea RGBD ia o imagine plus harta ei de
adancime si produce una sau mai multe imagini pentru noi puncte de vedere.
Redarea RGBD se bazeaza pe mutarea orizontala a pixelilor in care valoarea
deplasarii depinde de adancimea pixelului.
In acest capitol teoria din spatele redarii RGBD este explicata si algoritmul existent este
discutat pe scurt. Schimbarile si adaugirile necesare pentru a implementa
redarea RGBD pe un card grafic vor fi discutate in capitolele urmatoare.
5.1 Teorie
5.1.1 Lucrare
inrudita
O lucrare care este
inrudita cu problema noastra vine din campul de redare bazata pe imagine (image
based rendering IBR). Idea generala a IBR este sa
foloseasca ca date de intrare una sau mai multe imagini existente pentru
redarea unei noi imagini. Este o abordare a redarii
cu totul alta fata de redarea
obisnuita bazata pe geometrie.
Idea de baza in a folosi o imagine 2D si harta ei
de adancime pentru a genera o vedere virtuala este
descrisa in [1.5]. Aceasta abordare este folosita in
algoritmul offline existent si este descris in acest
capitol.
Aceeasi abordare va fi de asemenea folosita in
algoritmul nostru de redare RGBD care va functiona pe GPU.
5.1.2 Deplasarea
pixelilor in functie de adancime
Punctual
de plecare este o imagine si harta ei de adancime. Harta
de adancime detine, pentru fiecare pixel in parte, informatia despre distanta
de la camera pana la obiectul vizibil in acel pixel. Aceasta distanta este normalizata astfel incat 0 inseamna punctul cel mai
indepartat de camera si 1 inseamna punctual cel mai apropiat de camera. Aceasta
ne permite sa modelam imaginea ca un set de etaloane proiectate pe un teren.
Figura 5.1a prezinta o linie orizontala a imaginii modelate ca un teren.
Lungimile sagetilor corespund cu informatiile de adancime ale etaloanelor.
Generarea unei alte vederi poate fi realizata
prin repozitionarea camerei si apoi proiectarea etaloanelor in teren inapoi
peste planul imaginii (vezi figura 5.1b). In acest fel fiecare pixel de pe
linia orizontala a imaginii de intrare este mapat
intr-o noua pozitie in imaginea rezultata (pe aceeasi linie). Valoarea cu care
fiecare pixel este deplasat orizontal este
in functie de disparitatea sa si de valoarea miscarii camerei, vezi ecuatia
5.1:
deplasare orizontala = nr vedere x (disparitate -
deplasament) x factor de amplificare (5.1)
Figura 5.1 (a) Linia orizontala a imaginii
modelate precum un teren cu adancime. b) Etaloane proiectate inapoi pe planul
imaginii pentru o pozitie diferita a camerei.
Numerele vederilor folosite in aceasta ecuatie
sunt in intervalul [-4.+4], unde 0 este vederea
centrala. Disparitatea
este normalizata ([0.1]).
Disparitatea nu este acelasi lucru cu adancimea, dar
pentru moment putem ignora diferenta dintre ele. Disparitatea poate fi
calculata din adancime, ceea ce va fi explicat in capitolul urmator. Deplasamentul este
un parametru care controleaza tipul de disparitate de ecran in imaginea
rezultata: poate fi folosit de exemplu astfel incat imaginea rezultata sa
foloseasca doar adancimea din fata ecranului, sau de exemplu 40% in fata
ecranului si 60% in spatele ecranului, etc. Factorul de amplificare este un parametru care controleaza valoarea disparitatii
ecranului si astfel valoarea adancimii percepute. Un factor de amplificare 0
inseamna adancime 0, si cu cat factorul de amplificare este
mai mare, cu atat adancimea este mai mare. Lucrul acesta poate fi vizualizat in figura
5.1 prin considerarea factorului de amplificare ca fiind valoarea deplasarii
camerei. Atat deplasamentul cat si factorul de amplificare sunt acommodate
normal astfel incat imaginea 3D rezultata sa arate "bine".
5.1.3
Acoperiri si descoperiri
Pixelii de intrare se deplaseaza la o noua
pozitie in imaginea rezultata. Densitatea etaloanelor rezultate in imaginea
rezultata nu este uniforma. De aceea este
necesara o procedura de re-etalonare. In timpul deplasarii, unii pixeli sunt micsorati
sau mariti iau unii sunt chiar acoperiti de altii. Lucrul acesta se poate vedea
infigura 5.2. Se prezinta o casa cu un copac in fata ei. Partea de jos a
figurii arata "vederea de sus" a canalului de adancime al liniei de scanare.
Partea din stanga a figurii arata imaginea originala, jumatatea dreapta
prezinta imaginea asa cum este vazuta din pozitia
schimbata a camerei. Pe masura ce camera este deplasata
catre dreapta, atat casa cat si copacul apar sa se "deplaseze" catre stanga.
Totusi, intrucat copacul este mai aproape de camera
decat casa, copacul se deplaseaza mai mult decat casa. Copacul apare sa se deplaseze
catre stanga fata de casa (paralax de miscare). Acoperirea are loc atunci cand
copacul acopera o parte din casa care era vizibila din pozitia originala a
camerei. Descoperirea are loc cand o parte din casa care nu era vizibila din
pozitia originala a camerei devine vizibila.
Manipularea
acoperirii
Acoperirea poate fi detectata mergand prin linia
de scanarea a imaginii de intrare intr-o ordine specifica bazata pe deplasarea
camerei, vezi figura 5.3. O deplasare a camerei catre dreapa dicteaza o
transversare de la dreapta la stanga a pixelilor de intrare. O miscare a
camerei catre stanga dicteaza o transversare de la stanga la dreapta a
pixelilor de intrare. In aceasta figura camera este deplasata
catre dreapta. Aceasta inseamna ca pixelii de intrare sunt procesati in ordinea
a, b, c. Fiecare pixel este deplasat in noua sa pozitie
in baza adancimii sale. Un spatiu variabil este
introdus sa mentina valoarea-x minima (sau maxima) a pixelilor proiectati in
imaginea rezultata.
Figura 5.2: Acoperiri si descoperiri
Figura 5.3: Detectarea acoperirilor prin
mentinerea spatiului in domeniul de iesire. Pixelul c este
acoperit in imaginea de iesire, deoarece el nu mareste spatiul.
Cand un pixel deplasat nu scade (sau creste)
valoarea spatiului, el trebuie sa fie acoperit de oricare din pixelii deplasati
anterior. Prin urmare acest pixel nu trebuie sa fie vizibil in imaginea
rezultata. In figura, aceasta este si situatia
pixelului c: el nu mareste valoarea spatiului, deci este
acoperit (in acest caz de pixelul b).
Manipularea
descoperirii
Descoperirile pot fi detectate prin masurarea
distantei dintre doi pixeli in domeniul de iesire. Cu cat distanta aceasta este mai mare, cu atat mai mult se poate vedea dintr-un
obiect ce n-a fost vizibil in imaginea originala introdusa. Ar trebui notat
totusi, ca nu este posibil sa detectezi cu
exactitate diferenta dintre o marire normala a unui obiect si o descoperire
reala. In figura 5.2 o descoperire are loc la separarea mare dintre doua
pixeluri in imaginea rezultata. Descoperirile pot fi manipulate repetand fundalul.
"Fundalul" este pixelul cu valoarea adancimii cea
mai mare. Repetand fundalul se obtin rezultate mai bune decat repetarea prim
planului deoarece in lumea reala, privind in jurul unui obiect se obtin mai
multe detalii ale fundalului din spatele obiectului.
5.2
Implementare
5.2.1 Redare
RGBD avansata
Un algoritm de redare RGBD a fost deja dezvoltat
de catre Philips. El foloseste doua imagini pentru
fiecare cadru ca date de intrare: o imagine de culoare si una de adancime. Rezultatul este
o imagine interpolata pregatita pentru monitorul cu vederi multiple. Algoritmul
creeaza imediat imaginea rezultata interpolata fara sa mai treaca prin pasul
intermediar al celor noua imagini sub format "tiled".
Se numeste de asemenea algoritm "avansat": el
trece (in mod repetat) peste imaginea introdusa si pentru fiecare pixel
introdus, sunt actualizati zero sau mai multi (sub) pixeluri rezultati, vezi
figura 5.4. Bucla externa a algoritmului creeaza cumva confuzie, deoarece
aceasta trece peste imaginea rezultata. Dar lucrul acesta nu schimba natura
avansata a algoritmului.
pentru
fiecare linie din imaginea rezultata:
pentru
fiecare vedere [-4.+4]:
daca
vederea < 0
pentru fiecare pixel introdus pe o linie (de la dreapta la stanga)
calculeaza noua pozitie in imaginea rezultata
daca pixelul nu este acoperit
actualizeaza sub-pixelul
(pixelii) corespunzatori in imaginea rezultata
altfel
pentru fiecare pixel introdus pe o linie (de la stanga la dreapta)
calculeaza noua pozitie in
imaginea rezultata
daca pixelul nu este acoperit
actualizeaza
sub-pixelul (pixelii) corespunzatori in imaginea rezultata.
Figura 5.4. Pseudocod pentru redare RGBD avansata
Algoritmul produce rezultate de calitate inalta, dar
nu in timp real.
5.3 Avandaje
si dezavantaje
Redarea RGBD are unele avantaje si dezavantaje in
comparatie cu redarea multipla. Multe din aceste diferente sunt fundamentale.
Vom discuta pe scurt pe cele mai importante.
Avantajele redarii RGBD in comparatie cu redarea
multipla
- Un control mai
bun al valorii adancimii
Cand se foloseste redarea multipla singura
modalitate de a controla valoarea adancimii in imaginea rezultata este prin schimbarea valorii translatiei camerei (iar
aceasta nu ofera control asupra distributiei adancimii in scena). Redarea RGBD
face ca acest lucru sa fie complet controlabil prin procesarea hartii de
adancime (vezi sectiunea 6.4.4).
Cand nu exista restrictii la implementare, se
asteapta ca redarea RGBD sa fie mai rapida decat redarea multipla. Timpul
necesar pentru redarea RGBD este (aproape complet) independent
de scena, pe cand timpul necesar redarii multiple este
dependent de scena in mod considerabil.
- Redarea RGBD
are mult mai multe avantaje (pentru Philips in
general), dar acestea nu sunt intr-adevar relevante pentru noi.
De exemplu redarea multipla este
posibila doar cand modelul 3D al scenei este
disponibil. In majoritatea cazurilor, nu se intampla acest lucru, de exemplu cu
video 2D. Singura optiune atunci este crearea de harti
de adancime si folosirea redarii RGBD. Hartile de adancime pot fi estimate din
imagini video 2D prin algoritmi speciali.
De asemenea, rezultatul redarii multiple
(imaginea interpolata) este afisata dependent. Rezultatul
intermediar (imaginea in format "tiled") este de
asemenea afisat dependent: numar fix de vederi, valoare fixa de translatie a camerei.
Formatul RGBD, pe de alta parte, nu este
afisat dependent. Lucrul acesta il face sa fie foarte potrivit continutului 3D.
O harta de adancime poate fi storata o data cu
imaginea 2D la cost mic: folosind compresarea video se ia aproximativ 20% din
marimea imaginii 2D. Algoritmii speciali de compresare pot sa reduca aceasta
valoare chiar mai mult.
Formatul RGBD este
invers compatibil cu monitoarele 2D (ignora pur si simplu adancimea). Trebuie
retinut faptul ca o imagine cu 9 vederi in format "tiled" este
de asemenea invers compatibila: le ignora pur si simplu pe toate, cu exceptia
vederii centrale.
Dezavantajele redarii RGBD in comparatie cu
redarea multipla:
- Descoperirile
sunt umplute cu un fundal "gaussed" in loc cu ceea ce este
intr-adevar vizibil
- Efectele de
transparenta pot cauza probleme
Problema transparentei este
faptul ca culoarea pixelului este o combinatie
dintre culoarea fundalului si culoarea obiectului transparent insusi. Deci
pixelul ar trebui sa aiba de fapt doua valori ale adancimii, ceea ce nu este cazul. Numai una dintre ele este
disponibila in harta de adancime. In baza acestui lucru, pixelul este
remapat la o noua locatie. Lucrul acesta nu este
corect: in mod ideal, culoarea fundalului si culoarea obiectului transparent ar
trebui sa fie mapate in doua noi locatii diferite. Acelasi lucru se aplica si
cu efectele atmosferice, cum ar fi ceata.
- Vederea
efectelor dependente poate crea probleme
O ipoteza ridicata de redarea RGBD este
aceea ca o suprafata arata la fel, independent de unghiul de vedere. Nu se
intampla tot timpul asa. Luminarea depinde de unghiul de vedere. Acesta nu va
fi redat corect in vederile virtuale folosind redarea RGBD, care va scadea
calitatea.
Capitolul 6
De la z-buffer
la adancimea perceputa
Vrem sa implementam redarea RGBD pe un card
grafic si sa folosim informatia in z-biffer ca "informatia despre adancime"
necesara in procesul de redare. In acest capitol vom discuta teoria turutor
pasilor necesari de parcurs de la informatia din memoria-z la adancimea
perceputa pe ecran (vezi figura 6.1). Implementarea acestor pasi este
discutata in urmatorul capitol (in sectiunea 7.3).
|
|
z-buffer à . à adancime perceputa
|
|
Figura 6.1: De la z-buffer la adancimea perceputa
6.1 Z-buffering
Z-buffering este o
metoda folosita in mod curent de indepartare a suprafetei ascunse. Foloseste un
buffer 2D care storeaza "adancimea" obiectului vizibil la fiecare pixel. Cand
incepe redarea unui cadru, z-buffer-ul este
initializat complet pana la infinit. In timpul redarii unui obiect, culoarea unui
pixel este actualizata doar daca adancimea
obiectului este mai mica decat adancimea storata in z-buffer.
Aceasta inseamna ca un obiect este vizibil doar daca
este mai aproape de camera decat un obiect care a
fost deja redat, ca si in lumea reala.
Z-buffering este practic folosita de toate
jocurile 3D.
Disparitatea
normalizata la adancimea perceputa
z-buffer
à à
disparitatea ecranului à adancimea perceputa
|
|
Adancimea perceputa este adancimea pe care un privitor o experimenteaza cand
priveste monitorul. Este cauzata de disparitatea
ecranului asa cum a fost explicat in sectiunea 2.3.2 (vezi figura 6.2).
Figura 6.2: Disparitatea ecranului cauzeaza
adancimea perceputa.
Relatia dintre disparitatea ecranului si
adancimea perceputa a fost trasata in figura 2.5. Asa cum se observa, aceasta
functie nu este liniara pentru domeniul pentru care
a fost trasata. In sectiunea 2.3.1 am discutat ca disparitatea maxima a ecranului
(pozitiva sau nevativa) trebuie sa fie limitata (vezi sectiunea 2.3.1).
Pentru intervalul de disparitate a ecranului
folosit de monitoarele noastre, reiese ca relatia dintre disparitatea ecranului
si adancimea perceputa este in mod remarcabil liniara,
asa cum poate fi vazut in figura 6.3. Aceasta este
aceeasi funtie precum cea din figura 2.5, numai ca domeniul este
mai mic. Valorile disparitatii ecranului din afara acestui interval mic nu sunt
folosite in mod normal la monitorul nostru (pentru ca ele cauzeaza discomfort
de vedere).
Figura 6.3: Adancimea perceputa in spatele
ecranului in functie de disparitatea ecranului (pentru o distanta de vedere de
1 metru si o separare a ochilor de 64 milimetri).
In sectiunea 5.12 noi am discutat formula
folosita de redarea RGBD pentru a calcula valoarea deplasarii orizontale a unui
pixel. Formula necesita disparitatea normalizata ca date de intrare. Definitia
disparitatii este data in [2]. Valoarea exacta a
disparitatii nu este importanta deoarece
disparitatea ecranului este limitata. Prin urmare,
disparitatea poate fi normalizata. Formula deplasarii transforma disparitatea
normalizata in disparitatea ecranului folosind numarul vederii, factorul de
amplificare si deplasamentul (vezi figura 6.4). La randul ei, disparitatea
ecranului cauzeaza disparitatea perceputa pe monitor.
à à harta disparitatii normalizate à RGBD (redarea folosind factorul de
amplificare si deplasament) à disparitatea ecranului à adancime perceputa
Figura 6.4. Disparitatea ecranului este
rezultatul redarii RGBD folosind ca date de intrare o harta a disparitatii
normalizate.
6.3. De la
adancime normalizata la disparitate normalizata
Disparitatea normalizata necesara pentru redarea
RGBD poate fi calculata din adancime. Adancimea este
definita ca distanta de la planul camerei la un obiect intr-o scena. Marimea
exacta a adancimii nu este importanta deoarece intervalul
de adancime al scenei trebuie sa fie ajustat oricum pentru a se incadra in intervalul
adancimii percepute a monitorului. Aceasta inseamna ca harta adancimii poate sa
contina adancimile normalizate astfel incat 0 reprezinta adancimea cea mai scazuta
iar 1 reprezinta adancimea cea mai mare (de exemplu).
Disparitatea normalizata poate fi calculata din
adancimea normalizata folosind inversul graficului prezentat in figura 6.3.
Graficul trebuie sa fie normalizat (si inversat) in acest scop. Pentru intervalul
de disparitate (a ecranului) folosit la monitoarele noastre functia
reprezentata in aceasta figura este aproape liniara.
Aceasta inseamna ca noi putem folosi harta adancimii normalizata (fara
conversie) ca harta de disparitate normalizata pentru redarea RGBD (vezi figura
6.5). Adancimile din harta adancimii sunt schitate (aproape) in mod liniar la
adancimile percepute pe monitor, ceea ce si dorim. Retineti totusi ca pentru
monitoare cu un interval al disparitatii ecranului mare, pasul conversiei de la
adancimea normalizata la disparitatea normalizata nu mai poate fi sarit.
à à harta adancimii normalizate à (redarea RGBD cu factor de amplificare si
deplasament) àdisparitatea
ecranului à adancime perceputa
Figura 6.5. O harta a adancimii normalizate poate
fi folosita fara convestie ca harta a disparitatii normalizate deoarece relatia
dintre adancime si disparitate este aproape liniara
pentru monitorul nostru.
6.4 De la
Z-buffer la adancime normalizata
6.4.1 Z-buffer
Cand un cadru este
redat complet, imaginea este disponibila in
framebuffer-ul color (vezi figura 6.6 pentru un exemplu de joc Quake). Un
produs secundar al redarii este "z-map" (harta-z)
care este disponibila in z-buffer. Ea contine valoarea-z a obiectului vizibil la
fiecare pixel. Valorile storate in z-buffer-ul unui card grafic sunt rezultatul
unei serii de calcule in pipeline-ul grafic ( conducta de executie grafica) (vezi
figura 6.9).
Z-buffer-ul contine valorile-z ale spatiului
fereastra de la capatul secventei de transformare. Noi suntem interesati in
valorile-z ale spatiului ochiului. In spatial ochi camera este localizata in
origine si este directionata in jos pe axa z negative. In acest spatiu
coordonata z reprezinta distanta de la un punct la planul camerei (zeye = 0). Aceste coordinate zeye pot fi folosite ca harta
de adancime (de vreme ce ele reprezinta distanta de la planul camerei).
Z-bufferul are de obicei o precizie de 16 sau 24
bits per pixel. Figura 6.7 prezinta cei mai importanti opt bits al hartii-z ca
o imagine in scala gri. Daca s-ar folosi aceasta harta-z direct ca harta de
adancime pentru redarea RGBD, atunci ar rezulta o imagine 3D unde doar
obiectele foarte aproape de privitor vor obtine un efect de adancime bun iar
toate celelalte obiecte vor sfarsi undeva in spate. In exemplul nostru arma ar
folosi o mare parte a intervalului de adancime perceputa disponibila si toate
celelalte obiectele care sunt un pic mai indepartate vor fi in ultima partea a
intervalului de adacime perceputa. De aceea, harta-z nu poate fi folosita
direct ca harta de adancime. O etapa de conversie este
necesara pentru a transforma harta-z intr-o harta de adancime (dupa cum este prezentat in figura 6.8).
6.4.2
Calculare avansata
Trebuie sa inversam calculele facute in secventa
de transformare pentru a merge de la coordinate-z spatiu fereastra inapoi la
coordonate-z spatiu ochi. Dar mai intai
haideti sa aruncam o privire la calculele avansate (care au loc in pipeline-ul
de redare).
Matricea
proiectiei
Coordonatele ochilui (xeye, yeye,zeye,
weye) sunt
multiplicate cu matricea de proiectie pentru a obtine coordonatele clip (xc, yc, zc,
wc).
Matricea de proiectie poate fi orice, dar pentru
jocuri si multe alte aplicatii, este intotdeauna
matricea de proiectie de perspectiva. Cand o aplicatie OpenGL specifica o
proiectie de perspectiva ea trebuie sa specifice (printre altele) valorile
planurilor apropiate si departate (n
si f).
Figura 6.6 Framebuffer-ul color
Figura 6.7 Z-buffer (cei mai importanti 8 bits
sunt in imagine de scala de gri).
Partea din scena care este (in mod potential)
vizibila este intre zeye = - n
we si zeye = -
fwe.
Valorile rezultate ale zc si wc
sunt prin urmare definite prin ecuatiile [14]:
(6.1)
wc
= - zeye (6.2)
Divizare de
perspectiva
Valorile coordonatelor clip (xc, yc, zc ) sunt impartite la
valoarea coordonatei clip wc,
care rezulta in coordonate de aparat normalizate. Acest pas este
cunoscut sub numele de divizare de perspectiva. Valoarea coordonatei clip wc reprezinta distanta de la
planul camerei. Pe masura ce aceasta distanta creste valoarea 1/wc se apropie de 0. Prin
urmare si xc/wc si
yc/wc se
apropie de 0, cauzand primitivele redate
sa devina mai mici pe ecran. In acest mod pipeline-ul grafic simuleaza o vedere
de perspectiva.
Valoarea coordonatei clip zc este de asemenea divizata
la wc, precum xc si yc. Aceasta are acelasi rezultat ca si pentru xc si yc: cu cat zc
este mai mare (cu cat este mai indepartat de camera) cu atat zndc se micsoreaza.
Figura 6.8. Harta de adancime (calculata din
valorile z-buffer-ului folosind functia Z - D si storata ca imagine 8-bit de scala
de gri).
Figura 6.9.
Secventa de trasformare a vertex-ului [14]
Cauzeaza o precizie mai mare in jurul planului
apropiat si mai putina precizie in jurul planului indepartat (acest efect se
vede foarte clar in figura 6.7).
Valoarea rezultata a lui zndc este:
(6.3)
(6.4)
Coordonatele aparatului normalizate sunt in
intervalul [-1.+1]. zndc =
-1 corespunde planului apropiat si zndc
corespunde planului indepartat.
Transformarea portului
de vedere
Transformarea portului de vedere ajusteaza
intervalul coordonatelur apartului normalizate la [0..1], care are ca rezultat
coordonatele fereastra:
(6.5)
Coordonatele spatiului fereastra zwin sunt storate in z-buffer.
Zwin = 0 corespunde
planului apropiat si zwin
= 1 corespunde planului departat. (hardware grafice storeaza de fapt numere
intregi nesemnate de 16 sau 24 bits, dar OpenGL se sustrage de la aceasta. Deci
pentru noi o valoare din z-buffer este doar un numar
real in intervalul [0..1]).
6.4.3
Calcularea inversa
Sectiunea anterioara a prezentat cum pipeline-ul
de redare transforma zeye (adancimea de la planul camerei) in zwin (valorile storate in
z-buffer). Tot ceea ce ne trebuie noua este o formula care sa faca reversul: sa
transforme zwin in zeye. Aceasta formula poate
fi astfel folosita in softul nostru pentru a crea harta de adancime pentru
redarea RGBD folosind infomatia din z-buffer.
Calculele prezentate in sectiunea precedenta pot
fi inversate. Aceasta duce la urmatoarea ecuatie:
(6.6)
Aceasta ecuatie asuma un default de glDepthRange
(interval de adacime gl) de [0..1], ceea ce este si
cazul. Daca nu, este nevoie de o formula mai extinsa. Functia glDepthRange
afecteaza schitarea de la zndc la zwin, dar nu este folosita mai niciodata. Deoarece noi nu suntem
interesati in valorile zeye exacte, rezultatul poate fi normalizat
intre 0 (planul camerei) si f (indepartat):
(6.7)
Cu constantele a si b bazate pe valorile
lui n si f
(6.9)
Aceasta rezulta in graficul prezentat in figura
6.10. Relatia dintre zwin si zeye normalizata este prezentata pentru doua exemple de relatie de aproape si
de departe. In general, jocurile folosesc o relatie de aprope la indepartat
intre aceste doua.
Valorile lui zeye reprezinta o
distanta liniara de la planul camerei la un obiect vizibil si prin urmare le
vom numi simplu, adancime. Ecuatia 6.7 este
cunoscuta de acum incolo ca si "functia Z la D". Este
functia care transforma valorile z-buffer in valori de adancime. Cand
valorile-z ale spatiului fereastra de la z-buffer sunt procesate cu aceasta
functie, rezulta harta de adancime prezentata in figura 6.8.
6.4.4 Alte date
In aceasta sectiune sunt discutate alte chestiuni
despre conversia Z la D.
Proiectia ortografica
Cand aplicatia grafica foloseste proiectia
ortografica in locul proiectiei de perspectiva, intreaga poveste despre
conversia Z la D nu este necesara. Z-buffer-ul poate
sa fie folosit direct ca harta de adancime de vreme ce relatia dintre zwin
si zeye este liniara.
Figura 6.10 Functia Z la D trasata pentru doua
rapoarte de aproape la indepartat (1:100 in graficul de sus si 1:1000 in cel de
jos).
W-buffering
Cand aplicatia grafica foloseste w-buffering in
loc de z-buffering intreaga poveste despre conversia Z la D este
inutila. W-buffering ofera o reprezentatie liniara a distantei in bufferul de adancime.
Prin urmare w-buffer-ul poate fi folosit direct ca harta de adancime de vreme
ce relatia dintre zwin si zeye este
liniara.
Trasarea adancimii scenei la adancimea perceputa
In general, jocurile traseaza o scena 3D foarte
mare si aceasta scena mare trebuie sa fie compresata in intervalul de adancime
relativ mic al monitorului. Fara a fi discutat maparile in aceasta sectiune,
intregul interval de adancime al scenei este mapat
liniar in intervalul de adancime al monitorului. Aceasta este
cea mai corecta abordare, dar se poate sa nu ofere tot timpul rezultatele cele
mai bune.
Un avantaj al redarii RGBD este
acela ca noi putem controla cu exactitate cum adancimea scenei este
mapata la adancimea perceputa a monitorului. Aceasta poate fi realizata ca un
pas de procesare suplimentar al hartii de adancime inainte de a face redarea
RGBD. Deoarece valorile in harta de adancime sunt normalizate o functie f
: [0..1] --> [0..1] poate fi folosita pentru a pre-procesa harta de
adancime.
à (Z
in D) à harta
de adancime normalizata à (mapare)
à harta
de adancime normalizata à
Figura 6.11 De la z-buffer la adancimea perceputa
Un exemplu simplu si util este functia radacina
patrata, prezentata in figura 6.12. Aceasta functie are efectul ca obiectele
mai apropiate de camera folosesc mai multa adancime perceputa decat obiectele
indepartate de camera (in imaginea 3D rezultata pe monitor). Aceasta se poate
vizualiza in figurile 6.13 si 6. 14.
Figura 6.12 Functia radacina patrata
Figura 6.13. Adancimea scenei
Figura 6.14 Adancimea perceputa
Un alt exemplu util este
functia y = 1.3x transformata in intervalul [0..1]. Aceasta functie este prezentata in figura 6.15. Are efectul ca obiectele mai
indepartate decat o adancime specifica primesc toate aceeasi adancime maxima
perceputa. Toate celelalte obiecte care sunt mai putin indepartate primesc un
efect de adancime marit. Un alt scenariu util pentru aceasta functie este cand aplicatia seteaza planul indepartat la
"indepartat", astfel nemaifolosind de fapt o parte din z- buffer.
Figura 6.15: Exemplu de functie
Trebuie sa se observe faptul ca prin procesul de
normalizare, intervalul de adancime al scenei este
intotdeauna mapat la intervalul de adancime al monitorului, chiar daca oricare
dintre functiile exemplu prezentate in aceasta sectiune sunt folosite sau nu.
Adancimea scenei este intotdeauna compresata sau
expandata pentru a se potrivi in intervalul de adancime al monitorului (cu
exceptia cazului rar in care ele se potrivesc perfect).
Nu vom intra in mai multe detalii despre aceasta
scena deoarece ea nu este specifica redarii RGBD pe
GPU, dar se aplica in general tuturor tipurilor de redari RGBD. Controland cum
adancimea scenei este mapata la adancimea perceputa
a monitorului este discutata mai departe in [16,
17].
Capitolul 7
Redarea RGBD pe GPU
Abordarea noastra de redare RGBD este
implementata pe GPU prin folosirea unui OpenGL wrapper ca si in metoda veche
descrisa in capitolul 3. OpenGL wrapper-ul trebuie sa indeplineasca anumite
sarcini:
- Sa lase
aplicatia (jocul) sa redea un singur cadru. Mai intai wrapper-ul trebuie
sa lase aplicatia sa redea un cadru asa cum ea ar face in mod normal fara
wrapper, cu singura diferenta ca acest cadru trebuie redat la o rezolutie
normala in timp ce monitorul este intr-un mod
de rezolutie inalta. Lucrul acesta este necesar
deoarece datele introduse pentru redarea RGBD trebuie sa fie o imagine cu
o rezolutie normala si deoarece monitorul 3D merge doar cand este intr-un mod cu rezolutie inalta (rezolutia
originala, vezi sectiunea 2.4.1).
- Sa
copieze continutul framebuffer-ului in structuri. Cand aplicatia a
terminat redarea cadrului ea apeleaza functia swapbuffer. Acesta este
semnul pentru wrapper sa copieze frambufer-ul (atat cel color cat si
Z-bufferul) in structuri.
- Sa
efectueze calcularea Z la D. Continutul z-buffer-ului nu poate fi folosit
direct ca harta de adancime pentru redarea RGBD si prin urmare un pas de
conversie este necesar (dupa cum este explicat in capitolul precedent).
- Sa
efectueze redarea RGBD. Si ultimul pas este sa
efectueze redarea RGBD efectiva (pe GPU) avand ca date de intrare harta de
adancime creata in pasul precedent si structura color din pasul al doilea.
Aceste patru functii vor fi discutate in mai
multe detalii in urmatoarele patru sectiuni.
7.1 Redarea vederii centrale
Pentru redarea RGBD este
necesar ca aplicatia sa redea vederea centrala la o rezolutie normala (de
exemplu 800x600), in vreme ce monitorul insusi sa fie intr-un mod cu rezolutie
inalta (de exemplu 1600x1200). Exista doua modalitati de a realiza acest lucru:
- Prin
scalarea portului de vedere
Se lasa jocul sa ruleze la rezolutie inalta si se
ajusteaza portul de vedere in wrapper astfel incat jocul se reda la rezolutie
normala. Vom numi acest proces scalarea portului de vedere.
a) Jocul incepe si schimba monitorul la 1600x1200
b) In mod automat, monitorul incearca de asemenea
sa redea la rezolutia 1600x1200
c) Wrapper-ul ajusteaza portul de vedere astfel
incat jocul se reda la 800x600 (folosind de fapt doar coltul din stanga al
monitorului).
d) Rezultatul final: monitorul la 1600x1200 iar
jocul la 800x600.
- Fara
scalarea portului de vedere
Jocul este lasat sa ruleze la rezolutie normala si se
schimba rezolutia monitorului la rezolutie inalta in wrapper.
a) Wrapper-ul porneste si schimba monitorul la
800x600
b) Wrapper-ul schimba imediat monitorul inapoi la
rezolutia 1600x1200. Monitorul ramane la aceasta rezolutie.
c) Jocul nu observa lucrul acesta si continua sa
redea rezolutia 800x600 automat (folosind de fapt in mod automat doar coltul
din stanga jos al ecranului).
d) Rezultatul final: monitor la 1600x1200 si
jocul se reda la 800x600.
Desi ambele aplicatii realizeaza acelasi lucru,
prima prezinta un mare dezavantaj: nu este complet
transparenta pentru aplicatie. Continutul framebuffer-ului nu este
ceea ce aplicatia asteapta. De exemplu aplicatia se asteapta ca framebuffer-ul
sa contina o imagine cu o rezolutie de 1600x1200, dar datorita schimbarilor
portului de vedere in wrapper, imaginea este doar de
800x600. Aceasta
este o problema deoarece unele
jocuri redau o imagine in framebuffer si o folosesc apoi ca textura. O alta problema are loc cand o aplicatie citeste direct
din/scrie in frambuffer. Aceste probleme pot fi rezolvate prin schimbarea
comportamentului functiilor OpenGL implicate (citirea de la si scrierea in framebuffer
direct, vezi tabelul 7.1), sau folosind a doua abordare.
Exista totusi o noua problema cu prima aplicare
care nu poate fi rezolvata in wrapper: problema "marimii fontului". Un exemplu
poate fi vazut in jocul Quake 3: consola text in joc este
intocmita astfel incat fiecare caracter sa ocupe o marime fixa (in pixeli) pe
ecran. Aceasta inseamna ca atunci cand jocul este
jucat la o rezolutie inalta, mai multe caractere incap intr-o linie. Aceasta
cauzeaza probleme la scalarea portului de vedere: jocul crede ca ruleaza la
1600x1200 si astfel reda multe caractere pe o linie. Dar datorita scalarii
portului de vedere, caracterele sunt micsorate: ele devin
mici si prin urmare dificil de citit. Aceasta nu poate fi rezolvata (precum
celelalte probleme) prin schimbarea comportamentului functiilor OpenGL in
wrapper: jocul este cel care decide cate caractere
sa redea pe o singura linie. Problema nu este
limitata numai la exemplu marimii fontului: tot ceea ce aplicatia reda cu marime fixa (in pixeli pe ecran) sufera de
aceasta problema.
A doua abordare nu sufera de problemele de mai
sus, dar are o alta problema:
schimbarea rezolutiei monitorului in timp ce jocul ruleaza nu este
usor de suportat. Experienta noastra este ca merge
bine pe cardurile Nvidia dar cauzeaza probleme pe cardurile ATI pe care le-am
incercat.
S-au implementat ambele optiuni. S-au identificat
functiile OpenGL care cauzeaza probleme cu prima abordare (vezi tabelul 7.1).
Toate aceste functii logheaza un mesaj ce avertizeaza cand sunt apelate in timp
ce se foloseste scalarea portului de vedere. Ambele solutii au dezavantajele
lor si depinde de situatia exacta (joc, cardul grafic, etc) ce solutie sa se
foloseasca. Nu exista diferente de performanta intre cele doua optiuni.
Functia
|
Descrierea
|
glBitmap
|
traseaza un bitmap
|
glCopyPixels
|
copieaza pixeli in framebuffer
|
glCopyTexImage1D, 2D si 3D
|
copieaza pixeli din framebuffer in imagine cu
textura n-dimensionala
|
glCopyTexSubImage1D, 2D si 3D
|
copieaza o sub-imagine a unei imagini cu textura
n-dimensionala din framebuffer
|
glDrawPixels
|
scrie un bloc de pixeli in framebuffer
|
glReadPixels
|
citeste un bloc de pixeli din framebuffer
|
Tabelul 7.1: Functiile OpenGL care pot cauza
probleme la scalarea portului de vederea.
7.2 De la framebuffer la texturi
Pixel shader-ul nostru are nevoie de acces pentru
citirea continutului framebuffer-ului, deoarece el contine datele care sunt
introduse la redarea RGBD. Lucrul aceasta nu este
posibil in mod direct. Totusi, pixel shader-ul are acces sa citeasca texturile.
In acest fel, prin copierea continutului framebuffer-ului in structuri, pixel
shader-ul poate avea acces la el.
Copierea din framebuffer in textura trebuie sa
aiba loc pe cardul grafic insusi (si nu prin intermediul memoriei sistemului).
Lucrul acesta este efectuat de functia
glCopyTexSubImage2D, ea copieaza datele din framebuffer in textura. In mod
normal doar continutul culorii framebuffer-ului poate fi folosit ca sursa, dar
extensia ARB_depth_texture a OpenGL permite z-buffer-ului sa fie folosit ca
sursa de asemenea.
7.3 Conversia de la Z
la D
In sectiunea 6.4 noi am discutat teoria din
spatele pasului de conversie de la Z la D. In aceasta sectiune se va discuta
implementarea acesteia.
Textura
de adancime intermediara
Conversia de la Z la D trebuie sa aiba loc pe cardul grafic
folosid un pixel shader (precum redarea RGBD insasi). In esenta, avem doua
optiuni pentru a implementa lucrul acesta:
- Cu
structura de adancime
Conversia de la Z la D este realizata
intr-o trecere separata a pixel shader-ului inainte de redarea RGBD. Rezultatul
acestri treceri este o textura de adancime. Pixel
shader-ul de redare RGBD foloseste aceasta textura de adancime ca input. Cand este
nevoie de o valoare de adancime, poate fi luata direct din structura de
adancime. Aceasta optiune este prezentata in
jumatatea de jos a figurii 7.1.
- Fara
structura de adancime
Conversia Z la D este
integrata in pixel shader-ul de redare RGBD. Aceasta inseamna ca se efectueaza
doar o singra trecere a pixel shaderului. Pixel shader-ul de redare RGBD
foloseste structura z-bufferul ca input. Cand este
nevoie de o valoare de adancime, se aduce o valoare-z de la structura z-buffer
si apoi este convetita. Aceasta optiune este prezentata in jumatatea de sus a figurii 7.1.
Ambele optiuni realizeaza acelasi lucru, dar una
din ele foloseste structura de adancime ca pas intermediar iar cealalta nu.
Folosind aceasta structura de adancime intermediara este
o idee buna din doua motive:
Figura 7.1 Doua optiuni: cu sau fara structura de
adancime intermediara.
- Rezolutia
imaginii rezultate interpolate este mai mare
decat rezolutia structurii z-buffer introdusa
- Pixel
shader-ul de redare RGBD are nevoie de mai multe valori de adancipe pentru
fiecare pixel rezultat
Haideti sa explicam acest lucru printr-un
exemplu: sa presupunem ca jocul reda o rezolutie de 800x600 si rezolutia
rezultata (a rezultatului interpolat) este de
1600x1200. Si sa presupunem mai departe ca folosim un pixel shader de redare
RGBD care necesita sa citeasca 10 valori de adancime (pentru fiecare pixel
rezultat). Acum sa ne uitam la latimea de unda de memorie necesara pentru
fiecare cadru pentru citirea structurii, cu sau fara textura de adancime
intermediara:
- Cu
structura de adancime intermediara
In prima trecere, se creeaza structura de
adancime. Sunt cititi 800x600 Z (24 bits) si convertiti (cu o functie de
textura, din 8 bits Z in D) in D si apoi scrisi intr-o structura de adancime (8
bits). Aceasta necesita 800x600x(3+2+1) = 2.8 106 bytes de latime
de unda pentru fiecare cardu. In a doua trecere, se realizeaza redarea RGBD si
10 valori D (8 bits) sunt citite pentru fiecare pixel rezultat. Aceasta
necesita 1600x1200x10=19.2 106 bytes de bandwidth pentru fiecare
cadru. Totalul de latime de unda de memorie necesar este de 22106
bytes pentru fiecare cadru.
- Fara
textura intermediara de adancime
Intr-o singura trecere sunt realizate atat
conversia Z la D cat si redarea RGBD. Pentru fiecare pixel rezultat 10 valori Z
(24bits) sunt citite si convertite (intr-o funtie de textura de 8 bits Z in D).
Aceasta necesita 1600x1200x10x(3+2) = 96106 bytes de latime de
banda pentru fiecare cadru.
In acest exemplu folosind o structura de adancime
intermediara salveaza un factor de 4.4 in latimea de unda de memorie pentru
fiecare cardu (de la 98MB la 22MB). Acest castig de performanta este
cel mai important motiv pentru care noi folosim structura de adancime
intermediara.
7.3.2 Pixel shader
Pentru a executa pixel shader-ul care urmeaza sa
efectueze conversia din Z la D se traseaza un dreptunghi care are marimea
hartii de adancime rezultata. Cand se rasterizeaza acest dreptunghi, se executa
pixel shader-ul pentru fiecare pixel din dreptunghi (care va deveni harta de
adancime rezultata). Sarcina pixel shader-ului este
de a citi o valoare-z din structura z-buffer-ului, sa o converteasca in valoare
de adancime, si sa o redea ca valoare de culoare rezultata. Pseudocodul pentru
toate acestea este prezentata in figura 7.2.
pentru fiecare pixel in harta de adancime:
citeste valoarea-z din structura z-buffer-ului;
converteste valoare-z in valoare de adancime;
culoarea rezultata = valoare adancime; //scrie framebuffer-ului
Figura 7.2 Pseudocod pentru conversia din Z in D.
Bucla din pseudocod poate fi vazuta ca executarea
repetata a unui pixel shader. Codul din interiorul buclei este
echivalentul unui pixel shader. Partea cea mai interesanta a unui pixel shader
este cum reuseste sa faca conversia lui Z la D, iar lucrul acesta va fi
discutat in urmatoarea sectiune.
Cand se efectueaza conversia lui Z la D harta de
adancime rezultata se gaseste in framebuffer (color). Aceasta deoarece
rezultatul unui pixel shader este o culoare care (in
mod normal) ajunge in framebuffer. O functie OpenGL se apeleaza ulterior pentru
a copia continutul framebuffer-ului intr-o textura de scala gri (structura
hartii de adancime). O solutie mai rapida ar fi redarea directa intr-o
structura (fara a trece prin framebuffer). Extensia ARB_render_texture
poate sa ofere aceasta functionalitate. Aceasta ramane de analizat in viitor.
Functia de structura
Functia "din Z la D" (vezi ecuatia 6.7)
converteste valorile-z in valori de adancime. Pixel shader-ul nostru ar trebui
sa foloseasca aceasta functie. Din punct de vedere al calculelor este (prea)
scump sa evaluezi functia Z la D intr-un pixel shader. De aceea functia este
evaluata pe CPU pentru un numar fix de input-uri si rezultatele sunt storate
intr-o structura 1D (o matrice). Structura are o latime de 256 texels de
exemplu si fiecare texel poate fi storat intr-o valoare de 8-bits. Pixel
shader-ul care realizeaza calcularea lui Z la D foloseste valoarea-z (citita
din textura z-buffer-ului) ca adresa de unde se preia structura pentru structura
functie. Datorita filtrarii structurii biliniare realizata de cardurile grafice
rezultatul luat din structura este interpolat in mod
liniar intre doua valori cele mai apropiate. In felul acesta functia de
transformare a lui Z in D este aproximata destul de
exact prin valorile storate in structura.
Codul pixel shader-ului Cg este
prezentat in apendicele A1. Cand se compileaza cu profilul fp30 programul Cg
are 3 instructiuni (din care 2 sunt aduceri de textura). Functionarea acestuia
va fi discutatata in capitolul 8.
Datele Z introduse in funtia de transformare a
lui Z la D trebuie sa aiba o precizie de mai mult de 8 bits. O structura
z-buffer de 8 bits nu are destul de multa precizie pentru a fi folosita pentru
functia de transformare a lui Z in D. Cand se realizeaza acest lucru oricum,
rezulta in artifacte de banda dupa cum este prezentat in figura 7.3. Structura
z-buffer ar trebui sa aiba o precizie mai mare de 8 bits si in general, pe un
card grafic aceasta inseamna 16 sau 24 bits.
Figura 7.3: Harta de adancime (calculata din
valori ale z-buffer-ului de 8 bits) cu artefacte de banda.
Rezultatul functiei Z la D, harta de adancime,
poate fi storata cu o precizie de 8 bits. Aceasta este de ajuns pentru exemplul
nostru de monitor 3D deoarece 256 "nivele de adancime" sunt de ajuns pentru
intervalul de adancime perceput.
Reducerea hartii de adancime
In timpul conversiei lui Z la D rezolutia hartii
de adancime rezultata (in pixeli) poate fi mai mica decat rezolutia (in pixeli)
a z-buffer-ului. Acest lucru poate fi realizat variind marimea dreptunghiului
care este desenat pentru a executa pixel shader-ul.
Aceasta duce la cresterea performantei si scaderea calitatii. Reducerea hartii
de adancime duce la reducerea conversiilor din Z la D, copierea mai rapida din
framebuffer in structura si latime de banda de memorie mai mica in timpul
redarii RGBD. Reducerea hartii de adancime este o
optiune a software-ului nostru care poate fi redata in fisierul de configuratie
(vezi apendicele B.1).
7.3.3 Detectarea matricei de proiectie
Pentru a construi structura functiei de
transformare a lui Z in D pe un CPU sunt necesare valorile pentru planele
apropiate si indepartate. Aceste 2 valori determina forma precisa a functiei
(vezi figura 6.10). Wrapper-ul trebuie sa detecteze aceste doua valori cand
aplicatia seteaza o matrice de proiectie de perspectiva. Lucrul aceasta nu este pe atat de simplu pe cat pare.
Solutia cea mai usoara este
sa se foloseasca o functie fixa, independenta de planurile apropiat si departat
folosite de catre aplicatie. De exemplu: noi putem sa presupunem ca raportul
apropiat:indepartat este de 1:500 si sa folosim
acest raport pentru a calcula functia Z in D. Aceasta functioneaza oricum, desi
rezultatele nu sunt tocmai corecte (deoarece aceasta este
posibil doar cand se cunoaste raportul exact apropiat:indepartat).
Solutia corecta este
sa se detecteze valorile planurilor apropiat si indepartat folosite de catre
aplicatie. O abordare simpla este sa se astepte
apelarea SwapBuffer-ilor si apoi sa se citeasca matricea proiectiei curente.
Aceasta functie nu functioneaza in multe din cazuri: pana cand aplicatia
apeleaza la functia de SwapBuffers matricea de proiectie poate fi diferita de
matricea de proiectie folosita pentru a reda scena. De obicei, jocurile redau
unele suprapuneri 2D peste scena redata folosind o proiectie ortografica. Cand
se seteaza proiectia ortografica, ea inlocuieste matricea de proiectie de
perspectiva. Cand se apeleaza functia de SwapBuffers nu se mai poate oricum sa
se determine care era matricea de proiectie veche. De aceea este
nevoie de o solutie mai buna.
Tot ceea ce face aplicatia OpenGL trece printr-un
wrapper, de asemenea si modificarea matricei de proiectie. Aceasta ne da noua
posibilitatea de a detecta schimbarile facute matricei de proiectie in momentul
cand aceastea au loc. In tabelul 7.2 se prezinta o lista de functii OpenGL care
pot (eventual) schimba matricea de proiectie. In wrapper aceste functii isi
extind functionalitatea ceea ce le permite sa vada cand o matrice de proiectie
de perspectiva este setata sau modificata. Apoi
valorile de apropiere si de departare pot fi determinate din aceasta matrice de
proiectie si functia exacta Z in D poate fi construita. Totusi, exista unele
complicatii: matricea de proiectie se poate schimba pentru fiecare cadru, deci
ea trebuie sa fie detectata din nou pentru fiecare cadru. Situatia se
inrautateste si mai mult: ea se poate chiar schimba cand aplicatia reda un
cadru. O aplicatie poate de exemplu sa redea o parte dintr-o scena (cu o
proiectie de perspectiva), sa schimbe proiectia de perspectiva, si sa redea o
parte diferita a scenei. Aceasta inseamna ca nu este
necesara o singura functie Z in D care sa fie corecta pentru toata scena.
Aceasta totusi, este o problema mai mult teoretica
decat una practiva: wrapper-ul curent detecteaza numai prima matrice de
proiectie de perspectiva folosita pentru fiecare nou cadru, si aceasta este folosita pentru a calcula functia Z la D pentru acel
cadru. Acest lucru se pare ca functioneaza bine pentru moment.
Functia
|
Descrierea
|
glFrustum
glLoadIdentity
glLoadMatrix
glMultMatrix
glPopMatrix
glPushMatrix
|
multiplica matricea curenta printr-o matrice de perspectiva
inlocuieste
matricea curenta cu matricea de identitate
inlocuieste
matricea curenta cu o matrice arbitrara
multiplica
matricea curenta printr-o matrice arbitrara
impinge stack-ul
matricei curente
pop stack-ul
matricei curente
|
Tabelul 7.2 Functii OpenGL care pot shimba
matricea de proiectie
7.3.4 Erori Z-buffer
Daca privim imaginea 2D din figura 6.6 si harta
sa de adancime in figura 6.8 se pot observa 2 lucruri:
- Culoarea,
dar nu si potrivirea adancimii
In unele locuri, un obiect este
vizibil in imaginea color, dar nu si in harta de adancime. De exemplu numerele
din josul ecranului apar in imagine dar nu si in harta de adancime. Valoarea
adancimii in harta de adancime este din fundalul din
spatele obiectului
- Adancime,
dar nu si potrivirea culorii
Un alte locuri, exista o valoare a adancimii in
harta de adancime care nu corespunde cu un obiect din imaginea color. Aceasta
are loc de exemplu la capul mic din josul ecranului. Dreptunghiul care
inconjoara capul apare in harta de adancime dar nu si in imagine.
Aceste doua probleme sunt vizible in harta de
adancime, dar adevarata problema este deja prezenta
in z-buffer (de vreme ce harta de adancime este
tocmai rezultatul unui calcul realizat in z-buffer). O valoarea incorecta a
z-buffer-ului va rezulta intr-o adancime incorecta in harta de adancime.
Prima problema este
rezultatul faptului ca aplicatia dezactiveaza scrierea z-buffer-ului (si
testarea z-buffer-ului) cand se reda suprapunerea 2D. Aceasta are loc in
special din motive de functionare (de vreme ce ea salveaza latimea de unda a
framebuffer-ului). Ea cauzeaza suprapunerea 2D (majoritatea text) sa aiba
valoarea-z a obiectului din spatele acestuia. In imaginea 3D rezultata se pare
ca textul este lipid de fundal, ceea ce este un efect ciudat nedorit.
Cand aplicatia OpenGL incearca sa dezactiveze
scrierea z-buffer-ului wrapper-ul poate sa previna acest lucru si sa-l tina activ. Aceasta rezolva una din probleme: o
valoare-z ajunge sa fie scrisa in z-buffer. Dar valoarea exacta depinde de
valoarea-z arbitrara la care aplicatia reda suprapunerea 2D. Noi avem nevoie de
o valoare-z specifica pentru ca dorim ca suprapunearea 2D in imaginea 3D
rezultata sa aiba o disparitate de ecran zero. Disparitate ecranului zero
inseamna ca imaginea 3D este perceputa ca fiind
intinsa in planul ecranului (vezi sectiunea 2.3.1). La aceasta adancime ecranul
este foarte exact ceea ce face ca textul sa fie mult
mai usor de citit. Prin urmare, suprapunerea 2D necesita sa fie redata cu o
valoare-z care (atunci cand este convertita in
adancime) sa creeze o imagine 3D cu disparitate de ecran 0.
Amintiti-va din ecuatia 5.1 ca o adancime cu
aceeasi valoare ca parametrul deplasamentului cauzeaza o disparitate a
ecranului de zero (pentru toate numerele de vedere). Sa presupunem ca
parametrul de deplasare pentru redarea RGBD este
0.5, atunci adancimea necesara este tot 0.5.
Folosind inversul funtiei de transformare a lui Z in D noi putem calcula
valoarea-z necesara in z-buffer care sa faca suprapunerile 2D din imaginea 3D
rezultata sa apara cu disparitate zero. Sa presupunem ca aceasta valoare este de 0.98. Atunci wrapper-ul are nevoie sa se asigura ca
suprapunerile 2D ajung tot timpul in z-buffer cu valoarea de 0.98, indiferent
de valoarea-z folosita de catre aplicatie. In prezent, aceasta se realizeaza
prin folosirea functiei glDeptRange. Aceasta functie afecteaza schitarea
valorilor-z de la coordonatele aparatului normalizate la coordonatele window. Este apelata atunci cand aplicatia seteaza o proiectie
ortografica, deoarece suprapunerile 2D sunt de obicei trasate cu acest tip de
proiectie.
Cand wrapper-ul schimba valorile-z a obiectelor
redate apare o noua problema: z-testul poate acum preveni redarea unui pixel
deoarece valoarea-z proprie este mai mare decat
valoarea z a pixelului deja desenat. De aceea, z-testul trebuie sa fie
dezactivat.
Figura 7.4: Harta de adancime cu unele din
erorile de z-buffer rezolvate.
Rezultatul acestora este
prezentat in figura 7.4. Textul are o adancime care il face sa aiba o
disparitate de ecran zero dupa redarea RGBD. Textul este
totusi redat folosind un cadran schitat cu textura semi-transparenta si intreg
cadranul are o adancime, nu numai caracterele vizibile din el. Prin urmare, noi
am transformat problema noastra din tipul 1 in tipul 2. A doua problema,
totusi, nu este usor de rezolvat. Aceasta ramane de
realizat in viitor.
Exista o noua problema care poate fi observata in
figura 7.4: parul (din centrul imaginii) este de
asemenea redat folosind o proiectie rectangulara. Datorita lucrarii noastre,
acesta obtine o adancime in harta de adancime. Insa lucrul acesta nu este de dorit: cauzeaza un efect straniu in imaginea 3D
rezultata. Wrapper-ul (in general) nu poate sa rezolve aceasta problema. Acesta
nu poate sa stie cand obiectele trebuie sa aiba adancimea fundalului si care
obiecte trebuie sa apara la adancimea ecranului. Cea mai buna solutie este ca jocul ofera un z-buffer care sa fie pe cat de bun
posibil. Unele tehnici care pot fi folosite de creatorii de jocuri pentru a
ajuta aplicatiile 3D pot fi gasite in [18].
O alta
problema care are ca rezultat harti de adancime incorecte este
folosirea efectelor de translarenta. Jocurile recente folosesc din ce in ce mai
mult aceste efecte (apa, ceata, etc). Un exemplu poate fi vazut in figura 7.5. Este evident ca aceasta harta de adancime ofera un efect 3D
straniu cand este folosita pentru redarea RGBD.
Insa o alta
problema este folosirea structurilor
semi-transparente. De exemplu: o ramura a unui copac poate fi trasata cu o
structura patrata mare. Structura
este o imagine a frunzelor de pe
ramura. Este transparenta in locurile unde nu sunt frunze. Cand un joc
doreste sa redea o ramura, el trebuie doar sa redea un singur patrat cu
structura pe el. Aceasta tehnica (structuri semi-transparente) este
folosita in mod general. Totusi, aceasta rezulta in harti de adancime
incorecte: Intregul patrat ajunge in z-buffer, dar numai ramurile sunt vizibile
in buffer-ul color. Lucrul acesta se poate rezolva in viitor prin a face
z-buffer-ul sa scrie dependent de valoarea alfa a unui fragment.
In general ne putem astepta ca z-buffer-ul sa
devina o sursa de incredere din ce in ce mai mica pentru informatia adancimii
datorita a tot felul de "trucuri de redare".
Figura 7.5. Aceasta informatie din z-buffer nu este folosita intotdeauna ca harta de adancime: apa este vizibila dar in josul paginii apa apare in harta de
adancime. De asemenea muntele din spate lipseste din harta de adancime.
7.5 Redarea RGBD
A patra si ultima si sarcina a wrapper-ului este se realizeze procesul de redare RGBD. Datele de intrare
pentru redarea RGBD sunt textura color-buffer-ului din sectiunea 7.2 si textura
adancimii din sectiunea 7.3.
7.4.1 Redarea RGBD
avansata versus Redarea RBGD inversa
Algoritmul ar trebui sa ruleze pe cardul grafic
insusi deoarece copierea imaginii redate in memoria principala este
prea inceata (dupa cum s-a explicat in sectiunea 3.3) si deoarece CPU este prea incet pentru a efectua redarea RGBD in timp real.
Acesta are un impact mare asupra algoritmului
nostru: proprietatile hardware pot impune unele constrangeri serioase. Probabil
cel mai mare impact este cauzat de faptul ca pixel
shader-ii sunt condusi de rezultate, in loc de datele introduse. Aceasta indica
un algoritm inversat in loc de un algoritm avansat care ar fi probabil
mult mai logic.
pentru fiecare pixel din imaginea rezultata
interpolata:
pentru cei trei sub-pixeli:
Gaseste
pixelii din imaginea introdusa care sa contribuie la
culoarea
acestui sub-pixel
Figura 7.6 Pseudocodul pentru redarea RGBD
inversa
Comparati pseudocodul pentru redarea RGBD
avansata (din figura 5.4) cu pseudocodul pentru redarea RGBD inversa (figura
7.6). Este foarte importanta diferenta dintre
redarea in functie de datele introduse versus redarea in functie de datele
rezultate. Redarea RGBD avansata, in esenta, trece de 9 ori peste imaginea
introdusa. Pentru fiecare pixel din imaginea introdusa sunt actualizati zero
sau mai multi sub-pixeli in imaginea rezultata.
Redarea RGBD inversa este
dotal diferita: ea trece o singura data peste imaginea rezultata. Culoarea
pentru un sub-pixel poate veni de la oricare pixel(i) introdus(i) intr-un
interval orizontal din jurul pixelului rezultat. Intervalul orizontal este limitat deoarece disparitatea maxima a ecranului este limitata. Noi numim acest interval din imaginea
introdusa intervalul de cautare. Toti pixelii introdusi in intervalul de
cautare sunt posibili candidati: ei pot sa contribuie la culoarea sub-pixelului
curent rezultat. Daca ei intr-adevar contribuie sau nu, depinde de adancimea
lor (si de vizibilitatea lor, ei nu trebuie sa fie acoperiti). De aceea, noi
trebuie sa shimbam fiecare pixel introdus ca posibil candidat si sa vedem daca
el contribuie sau nu. Un pixel introdus contribuie daca el se schimba in
apropierea pozitie rezultate si daca nu este
acoperit de catre alti subpixeli.
Distanta dintre dintre pozitia rezultata si noua
pozitie a pixelului introdus determina greutataea sa. Aceasta este
o simplificare, dar pentru moment este destul de
buna. Greutatea totala intotdeauna se insumeaza la unu. Culoarea sub-pixelului
rezultat este actualizata in functie de greutatea si
culoarea pixelului introdus.
Bucla externa din figura 7.6 este
in esenta executarea repetata a pixel shader-ului. Daca eliminam bucla, noul
pseudocod este prezentat in figura 7.7.
Pentru cei trei sub-pixeli:
Pentru fiecare pixel introdus in intervalul de cautare:
Daca
pixelul introdus nu este acoperit:
calculeaza greutatea
culoare sub-pixel + = greutate * culoare introdusa;
Figura 7.7: Pseudocodul pixel shader-ului pentru
redarea RGBD inversa.
7.4.2 Implementare
Dreptunghi de marimea ecranului
Pentru a executa un pixel shader pentru fiecare
pixel din imaginea rezultata trebuie redat un dreptunghi care sa umple complet
ecranul, asa numitul dreptunghi de marimea ecranului. Cand acest dreptunghi este rasterizat, ar trebui ca toti pixelii ecranului sa fie
acoperiti de dreptunghi. De asemenea coordonatele de textura trebuie sa fie
specificate in varfurile dreptunghiului. Cand coordonatele de structura sunt
interpolate de catre rasterizer rezulta in coordonate de structura la fiecare
pixel. Aceste coordonate de structura trebuie sa fie corecte cu exactitate
(deoarece pixel shader-ul le foloseste pentru a calcula numerele vederilor de exemplu).
Cum se poate realiza acest lucru este discutat in
.
Adrese de structura
Pixel shader-ul nostru de redare RGBD are trei
adrese de structura ca date de intrare:
- Rezultat:
pozitia in imaginea rezultata
- RGBD
introdus: pozitia corespunzatoare in imaginea color introdusa
- D
introdus: pozitia corespunzatoare in imaginea de adancime introdusa
Prima coordonata de structura este
folosita de catre pixel shader pentru a determina pozitia pixelului in imaginea
rezultata pentru care trebuie sa calculeze culoarea. A doua coordonata de
structura specifica pozitia corespunzatoare in imaginea color introdusa. De
exemplu, daca pozitia in imaginea rezultata este rezultat
(x, y) = (100, 200) atunci pozitia corespunzatoare in imaginea color
introdusa este input RGBD (x, y) = (50, 100), dat
fiind faptul ca rezolutia culorii introduse este
jumatate din cea rezultata. Acelasi lucru se aplica si pentru coordonata de
structura D introdusa: ea specifica pozitia corespunzatoare in imaginea de
adancime introdusa. Aceasta pozitie poate fi diferita de pozitia color
introdusa deoarece harta de adancime poate fi redusa in timpul conversiei lui Z
in D (vezi pagina 34).
7.4.3 Determinarea numerelor vederii
Primul pas in redarea RGBD folosind pixel
shader-ul este de a determina cele trei numere de
vedere ale pixelului rezultat. Numerele vederii pot fi deduse din modelul
prezentat in figura 2.8 (pentru monitorul nostru luat ca exemplu). In esenta,
exista doua optiuni de a determina numerele de vedere: sa le calculam sau sa le
cautam intr-o structura (sau o combinatie a acestora). Un posibil calcul este prezentat in figura 7.8.
matricea
V =
i
= (3x + 4y) mod 9
VR
= V[i]
VG
= V[(i + 1) mod 9] (7.1)
VB
= V[(i + 2) mod 9]
Figura 7.8 Calcularea celor 3 numere de vedere
(folosind o cautare cu o matrice mica)
Calculul foloseste pozitia pixelului rezultat pe
ecran (x, y) pentru a calcula cele trei numere de vedere VR, VG
si VB. Pozitia pixelului (0, 0) este
pixelul din partea de stanga sus pe ecran. Calculul foloseste o matrice care
contine primele noua numede de vedere in coltul din stanga sus a ecranului. Matricea este
atat de mica, incat ea ar incapea in codul pixel-shader-ului. Totusi, ea nu
poate fi implementata intr-un pixel shader: indexii matricii (sau indicatori)
nu sunt suportati in profilele de pixel shader curente. Noi putem folosi o structura pentru a stora
valorile matricii dar aceasta ar costa trei cautari de structura ceea ce nu se
doreste. Sau am putea adapta calculul astfel incat sa nu mai fie nevoie de
matrice (calculata pe loc), dar aceasta ar creste numarul de calcule, ceea ce
nu se doreste de altfel. O mai buna abordare este de
a scapa de calcule si doar de a folosi cautarea de structura.
Este
posibil sa se creeze o structura mare (cu aceeasi rezolutie ca si rezolutia
rezultata) care sa contina pentru fiecare pixel rezultat numerele de vedere ca
valoare de culoare RGB. Aceasta singura aducere de structura reda cele trei
numere de vedere ca valoare de culoare RGB. Nu mai este
atunci nevoie de calcule, dar in schimb este nevoie
de mai mult bandwidth de memorie.
Putem reduce in mod considerabil marimea
structurii folosind modelul repetarii numerelor. Patratul care se repeta are
3x9 pixeli, ceea ce inseamna ca dupa trei pixeli in directia orizontala,
numerele incep sa se repete. Acelasi lucru este
valabil si pentru cei noua pixeli in directia verticala.
Texturile din OpenGL au nevoie sa aiba o latime
si o inaltime care sa fie multiplu de doi. De vreme ce marimea texturii
numarului de vedere nu este multiplu de doi, acest
lucru este probelmatic. Exista doua extensii OpenGL
care sa indeparteze aceasta limitare:
l
ARB_texture_rectangle
- coordonate de structura dependente de
dimensiune (ne-normalizate): interval [0..w]x[0..h]
- modul REPETA wrap nu este
suportat
l
ARB_texture_non_power_of_two
- coordonate de structura dependente de
dimeniune: interval [0..1]x[0..1]
- mod REPETA wrap suportat
Extensia ARB_texture_rectangle a fost folosita deoarece este
suportata de mai multe carduri grafice. De vreme ce modul repeat wrap nu este suportat, diviziarea modulara a coordonatelor
structurii este realizata in insusi pixel shader
(vezi figura 7.9).
float2
TexCoordViewNrs;
TexCoordViewNrs.x = fmod(Input . TexCoordOutput.
x, 3); // x = [0.5 .. 2.5]
TexCoordViewNrs.y = fmod(Input . TexCoordOutput.
y, 9); // y = [0.5 .. 8.5]
float3 ViewNrs; ViewNrs = texRECT(ViewNrTexture,
TexCoordViewNrs);
ViewNrs = ViewNrs - (5/255.0); // Vederea
centrala este 0 acum
Figura 7.9 Codul pixel shader-ului Cg pentru
determinarea celor trei numere de vedere.
Dupa ce numerele de vedere au fost determinate
din structura, ele sunt toate pozitive. Pentru redarea RGBD nu este
necesar ca vederea centrala sa fie 0. O valoare constanta este
scazuta din numerele de vedere pentru a realiza acest lucru.
7.4.4 Variante de redare RGBD
Cand se cunosc numerele de vedere, urmatorul pas
al pixel shader-ului este sa realizerea redarea
RGBD. S-au dezvoltat trei variante de pixel shader pentru redarea RGBD. Ele
difera in evaluarea informatica si in calitatea rezultatelor pe care le ofera.
- Redarea
RGBD a adancimii rezultate
- Redarea
RGBD a adancimii introduse prin "gasirea celui mai apropiat"
- Redarea
RGBD a adancimii introduse
Redarea RGBD
a adancimii rezultate
Redarea RGBD a adancimii rezultate este
un algoritm ieftin din punct de vedere informational. El se foloseste de o
aproximare pentru a realiza redarea RGBD care creste performantele dar scade
calitatea rezultatului.
Redarea RGBD a adancimii rezultate ia adancimea
la pozitia rezultata curenta si o foloseste pentru a determina care din pixelii
introdusi se muta catre pozitia rezultata.
Pseudocodul pixel shader-ului pentru redarea RGBD
a adancimii rezultate este prezentat in figura 7.10.
determina cele trei numere de vedere;
citeste (pozitia rezultata) adancimea din
structura;
calculeaza trei pozitii in imaginea introdusa
folosind adancimea si numerele de vedere;
realizeaza trei citiri ale structurii in
structura color (culoare1 t/m 3);
culoare rezultata (rosu) = culoare 1 (doar rosu);
culoare rezultata (verde) = culoare 2 (doar
verde);
culoare rezultata (albastru) = culoare 3 (doar
albastru);
//culoarea rezultata este
scrisa in framebuffer
Figura 7.10: Pseudocudul pixel shadeer-ului
pentru redarea RGBD a adancimii rezultate
Deoarece nu se efectueaza nici o cautare in
imaginea de intrare, redarea RGBD a imaginii rezultate este
mult mai ieftina decat "adevarata" redare RGBD (adancime introdusa). Trebuie sa
se citeasca doar o singura valoare a adancimii si sa se realizeze doar o singura
calculare a mutarii. Cu redarea RGBD "adevarata" toate adancimile tuturor pixelilor introdusi in intervalul de
cautare trebuie sa fie citite iar valoarea mutarii trebuie sa fie calculata.
Redarea RGBD a adancimii rezultate foloseste supozitia ca valorile adancimii nu se schimba
foarte mult intr-o regiune orizontala mica. El aproximeaza adancimea unui pixel
introdus prin adancimea pixelului introdus la pozitia rezultata
(corespunzatoare). Deoarece distanta dintre un pixel introdus si pozitia rezultata
nu este mare, aceasta aproximare functioneaza bine,
ceea ce cauzeaza un artifact in imaginea rezultata dupa cum este
prezentat in figura 7.11. Artefactul
este mai mare pentru vederile
externe (vederea centrala nu are nici un artifact).
Figura 7.11: O singura vedere externa creata prin redarea RGBD a
imaginii rezultate. Artefactul din marginea din stanga lansatorului de rachete este clar vizibil. Este rezultatul unei cresteri in harta de
adancime.
Codul pixel shader-ului Cg pentru redarea RGBD a imaginii rezultate este prezentat in apendicele A.2.
Cand este compilat cu profilul fp30 programul Cg are 30
instructiuni (din care 5 sunt aduceri de structura). Functionarea va fi
discutata in capitolul 8.
In esenta, codul prezinta cum o singura valoare de adancime este citita (folosind pozitia rezultata) si cum aceasta
valoare este folosita pentru a calcula pozitia x a
celor trei pixeli introdusi. Vectorul datatype float3 este
folosit pentru a calcula trei rezultate (pentru r, g,b) imediat. Hardware
grafice pot efectua calcule vetorizate (cu pana la 4 componente) fara
pierderi de eficienta in comparatie cu
calculele scalare. Pozitiile x calculate sunt folosite ca adrese de structura
pentru 3 executari de structura in culoarea introdusa.
Redarea RGBD a adancimii introduse prin "gasirea
celui mai apropiat "
Varianta celui de-al doilea pixel shader care a
fost dezvoltata este numita adancime introdusa
gaseste-cel-mai-apropiat. Asa cum specifica si numele, ea se foloseste de
adancimea din pozitia de intrare. Aceasta este
abordarea corecta: un pixel introdus este mutat in
functie de propria adancimea.
A doua parte a numelui este
"gaseste-cel-mai-apropiat". Aceasta se refera la faptul ca algoritmul gaseste
doar un singur pixel (pentru fiecare sub-pixel in rezultat) care deplaseaza pe
cel mai apropiat la pozitia rezultata. Algoritmul este
mai costisitor din punt de vedere informational decat varianta precedenta
(adancime rezultata). Dar el totusi nu efectueaza toate operatiile necesare pentru
algoritmul de redare RGBD "adevarat". Nu se efectueaza nici o tratare a
acoperirii sau filtrarii.
Pseudocodul pixel shader-ului pentru redarea RGBD
a adancimii introduse gaseste-cel-mai-apropiat este
prezentat in figura 7.12.
determina cele trei numere de vedere;
distanta minima = infinit
pentru fiecare pixel introdus (i) in intervalul
de calculare:
citeste adancimea din structura;
penru cei trei sub-pixeli (vectorizati):
calculeaza noua pozitie in functie de adancime;
calculeaza distanta dintre pozitia rezultata si noua pozitia;
daca
distanta < distanta minima:
pixelul cel mai apropiat = i;
distanta minima = distanta;
pentru cei mai apropiati pixeli introdusi:
citeste culoarea din structura;
culoarea sub-pixelului rezultat = culoare; (doar
r, g sau b)
// culoarea rezultata este
scrisa in framebuffer
Figura 7.12 Pseudocodul pixel shader-ului pentru
redarea RGBD a adancimii introduse gaseste-cel-mai-apropiat.
Algoritmul contine o mica bucla: el trece peste o
regiune orizontala mica in imaginea introdusa in jurul pozitiei rezultate.
Pozitia (in imaginea rezultata) a pixelului introdus curent este
calculata in functie de adancimea sa. Acest lucru este
realizat pentru cele trei vederi in acelasi timp (vectorizat). Apoi distanta
dintre pozitia calculata si pozitia rezultata este
masurata. Scopul
este de a gasi pixelul cu cea mai
mica distanta. De aceea algoritmul se numeste "gaseste-pe-cel-mai-apropiat". De
fapt exista trei pixeli apropiati (pentru r, g, b). O data ce acesti pixeli au
fost gasiti, se realizeaza trei aduceri de structura in structura de culoare
introdusa pentru a obtine culorile. Doar o singura componenta de culoare din
fiecare culoare este folosita. Apoi aceste trei
componente sunt combinate in culoarea finala rezultata a pixel shader-ului.
Acest pixel ajunge in imaginea rezultata interpolata.
Algoritmul de manipulare a acoperirilor explicat
in sectiunea 5.1.3 nu este folosit. Aceasta inseamna
ca pixelii care nu sunt vizibili intr-o vedere noua nu sunt indepartati, cum ar
fi trebuit. Algoritmul pentru a manipula acoperirile nu este
folosit deoarece aceasta ne permite noua sa facem doar o singura trecere pentru
pixelii introdusi. Daca se foloseste algoritmul ar insemna ca este
necesar trei treceri peste pixelii introdusi (pentru cele trei numere de
vedere), ceea ce este mult mai scump. De asemenea nu
se realizeaza nici o filtrare: se foloseste doar culoarea pixelului care muta
pe cel mai apropiat in pozitia rezultata.
Aceasta varianta este
mult mai scumpa decat cea a adancimii rezultate expusa mai sus deoarece se
realizeaza o cautare in imaginea introdusa. Latimea intervalului de cautare in
imaginea introdusa este ajustabila. Aceasta inseamna
ca mai multe aduceri de structura sunt necesare in structura de adacime. Un
interval de cautare mai mare inseamna o adancime mai mare in imaginea 3D
rezultata, dar mai inseamna si mai multe aduceri de structura.
De asemena, aceasta varianta este
mai scumpa pentru ca sunt necesare mai multe calculte: trebuie sa se gaseasca
distanta minima. Distanta trebuie sa fie calculata pentru fiecare pixel
candidat introdus in functie de adancimea sa. Cum s-a mai precizat, aceste
calcule au fost vectorizate oricand a fost posibil.
Un cod pentru pixel shader-ul Cg folosit pentru
redarea RGBD a adancimii introduse in baza gasirii celui mai apropiat, este prezentat in apendicele A.3. Cand este
compilat cu profilul fp30, programul Cg are 120 de instructiuni (din care 13
sunt aduceri de structura). Aceste numere sunt pentru un interval de cautare de
9 pixeli introdusi. Buclele nu sunt desfasurate de compilatorul Cg.
Dupa implementarea si testarea acestui pixel
shader, a devenit clar faptul ca acest algoritm nu este
o imbunatatire la versiunea precedenta (adancime rezultata). Shader-ul este incet dar nu produce rezultate mai bune. Artefactele
cauzate de aceasta varianta sunt mult
mai notabile decat artefactele versiunii precedente, in special pentru
imaginile care se misca. Artefactele pot fi observate la margini (in harta de
adancime). Artefactele pot varia rapid cand imaginea introdusa se schimba doar
putin, pe cand artefactele variantei precedente erau mult mai stabile.
Datorita raportului rau calitate/performanta a
acestei variante, ea nu mai este folosita. De
asemenea, in capitolul 8 nu se va mai realiza nico o masuratoare a performantei
pentru aceasta varianta.
7.4.7 Redarea RGBD
a imaginii introduse
Varianta a treia a redarii RGBD se numeste
adancime introdusa. Scopul acestei variante este sa
fie echivalenta cu algoritmul offline deja existent (din sectiunea 5.2.1). Prin
urmare ar trebui sa produca rezultate de mare calitate, ceea ce inseamna:
manipularea acoperirilor si descoperirilor si o filtrare mai eficienta.
Pseudocodul pentru pixel shader-ul de redare RGBD
a adancimii introduse este prezentat in figura 7.13.
Algoritmul consta din trei parti majore: mai itai se citeste (din structura)
adancimea si culoarea tuturor pixelilor introdusi in intervalul de cautare si
se storeaza in doua matrici. Se realizeaza acest lucru deoarece adancimea si
culoarea pixelului introdus sunt necesare de mai multe ori mai tariziu.
Storarea lor intr-o matrice reduce numarul de aduceri de structura necesare.
Indexarea matricei este posibila in acest caz deoarece
buclele nu sunt desfacute de compilatorul Cg.
A doua parte a algorimului foloseste adancimea
pixelilor introdusi pentru a calcula noile lor pozitii. Pentru fiecare pixel
introdus, se calculeaza trei noi pozitii (pentru cele trei numere de vedere ale
sub-pixelilor RGB). Aceste calcule sunt realizate vectorizat din motive de
functionalitate.
Noile pozitii ale pixelilor introdusi sunt
folosite in partea a treia a algoritmului. Aici se verifica daca un pixel nu este cumva acoperit de un alt pixel. Daca nu este,
se calculeaza o asa numita greutate. Greutate defineste cat de mult culoarea
unui pixel introdus contribuie la culoarea sub-pixelului rezultat. Greutatea
poate fi calculata prin folosirea o functie de filtrare. In functie de
greutate, culoarea unui pixel introdus este adaugata
la culoarea sub-pixelului rezultat. A treia parte a algoritmului nu poate fi
vectorizata ca partea precedenta. Deaoarece algoritmul de detectarea a
acoperirilor se bazeaza pe ordinea in care pixelul introdus este
procesat. Si nu este cazul mai niciodata ca cele
trei numere de vedere sa aiba nevoie de aceeasi ordine. In majoritatea
cazurilor, este necesar sa procesam pixelii
introdusi de la stanga la dreapta pentru un numar de vedere si apoi de la
dreapta la stanga pentru un alt numar de vedere.
determina cele trei numere de vedere;
pentru fiecare pixel introdus in intervalul de
cautare:
citeste culoarea din structura in matrice;
citeste adancimea din structura in matrice;
pentru fiecare pixel introdus:
calculeaza trei noi pozitii in functie de adancime (vectorizat)
pentru fiecare sub-pixel rezultat (r, g, b):
culoarea sub-pixelului = 0;
daca numarul de vedere > = 0
pentru fiecare pixel introdus in intervalul de cautare (de la stanga la
dreapta)
daca
pixelul nu este acoperit
calculeaza greutate;
culoare sub-pixel + = greutate * culoare
introdusa;
daca nu
pentru fiecare pixel introdus in intervalul de cautare (de la dreapta la
stanga)
daca pixelul nu este acoperi
calculeaza greutatea;
culoare sub-pixel+ = greutate * culoare
introdusa;
// culoarea rezultata este
scrisa in framebuffer
Figura 7.13. Pseudocodul pixel shder-ului pentru
Redarea RGBD pentru imaginea introdusa
Pixel shader-ul nostru efectueaza redarea RGBD a
adancimii introduse cu manipularea si filtrarea acoperirilor. Nu produce,
totusi, aceeasi calitate ca algoritmul existent deja (offline). De exemplu
algoritmul existent are grija de "schimbarea lentilelor" care ajuta la
producerea unei calitati mai mare a rezulatului. Pixel shader-ul nostru nu ia
aceasta in calcul. De asemenea algoritmul offline poate folosi funtiile de
filtrare definite de utilizator. Pixel shader-ul nostru foloseste doar o cutie
de filtrare din motive de performanta.
Implementarea tuturor acestora ar face pixel
shader-ul atat de mare incat nu ar mai putea fi executat pe carduri grafice.
Totusi, pixel shder-ul ofera o buna indicatie a numarului minim de instructiuni
necesare pentru a realiza redarea RGBD pe un card grafic. O calitate mai mare
ar insemna mai multe instructiuni. Deoarecel pixel shader-ul este
foarte incet, nu este foarte folositor decat pentru
masuratorile de performanta.
Codul pixel shder-ului pentru redarea RGBD a
adancimii introduse este prezentat in apendicele
A.4. Cand este compilat cu profilul fp30 programul
Cg are 708 instructiuni (dinc are 25 sunt aduceri de structura). Aceste numere
sunt pentru un interval de cautare de 11 pixeli introdusi. Buclele nu sunt
desfacute de catre compilatorul Cg.
Performantele vor fi discutate in
capitolul 8.
7.5 Rezultatul YUVD
In loc de a efectua redarea RGBD pe un card
grafic, noi putem de asemenea sa realizam redarea RGBD extern. Cardul grafic va
fi folosit atunci doar pentru a trimite imaginea 2D impreuna cu harta sa de
adancime la exterior (vezi figura 7.14). Philips a
dezvoltat o placa externa de redare RGBD. Wrapper-ul nostru a fost extins cu
suport pentru a putea crea rezultatul necesar pentru placa externa de redare
RGBD.
Figura 7.14. Cand redarea RGBD se face extern,
cardul grafic trimite o imagine 2D impreuna cu harta sa de adancime la
exterior.
Placa hardware
Placa hardware de redare RGBD dezvoltata de Philips are o imagine 2D impreuna cu harta sa de adancime
ca date de intrare si produce o imagine interpolata ca rezultat. Placa are doua
porturi DVI: unul pentru intrare si unul pentru iesire. Portul de intrare este conectat la portul de iesire al placii grafice. Portul
de iesire este conectat la un monitor 3D (portul de
intrare). Aceasta placa hardware dedicata poate efectua o redare RGBD de
calitate inalta la un framerate inalt. Datele trebuie introduse in placa la o
rezolutie fixa si o rata reinnoita (de exemplu: 729x540 la 60Hz), altfel nu va
functiona.
Formatul YUVD
Rezultatul (DVI) a unui card grafic poate sa
redea doar trei canale de culoare (RGB). Noi vom dori sa folosim aceaste canale
de culoare nu numai pentru culoare, dar si pentru adancime. YUVD este
o metoda de impachetare a culorii si a adancimii in aceste canale RGB.
Functioneaza daca se folosesc canalele rosu si verde pentru culori si cel
albastru pentru adancime. Culorile RGB ale imaginii sunt mai intai convertite
in spatii de culoare YUV. Canalul rosu este folosit
pentru a stora Y, canalul verde este folosit
alternativ pentru a stora U sau V, si canalul albastru este
folosit pentru a stora D (adancimea). (vezi figura 7.15). Componentele U si V
au doar jumatate din rezolutia verticala a componentei Y, dar aceasta nu
reprezinta o problema deoarece ochiul uman este mult mai bun la vederea
diferentelor in luminozitate decat in culoare.
Figura 7.15 Informatie despre culoare si adancime storate in canalele
RGB prin folosirea formatului YUVD.
Pixel shder
Wrapper-ul OpenGL creat original pentru a reda RGBD a fost extins cu
suport pentru rezultatul YUVD. Aceasta
face ca jocurile sa poate sa fie jucate in timp real cu calitate ridicata la
monitorul 3D (cu ajutorul placii hardware de redare).
Redarea RGBD a fost implementata ca un algoritm pixel shader cu doua
treceri (vezi figura 7.1) din motivele discutate in sectiunea 7.3.1. Pentru
rezultatul YUVD, totusi, aceste motice nu mai sunt valabile:
- Rezolutia rezultatului YUVD este
(in majoritate cazurilor) la fel ca si rezolutia RGB (si Z) introdus
- Conversia la YUVD necesita doar o singura
valoare Z de intrare pentru fiecare valoare D rezultata.
Figura 7.16. Rezultatul
YUVD este
realizat cu o singura trecere a pixel shder-ului: nu se foloseste structura de
adancime intermediara.
Aceasta inseamna ca rezultatul YUVD poate fi implementat ca algoritm
de pixel shader pentru o singura trecere, fara pasul intermediar de structura
de adancime. Conversia din Z in D poate fi integrata in pixel shader-ul YUVD si
aceasta nu creste numarul de calcule pentru conversia Z in D.
citeste culoare din structura;
converteste Z in D (folosind functia de structura);
culoarea rezultata (rosu) = Y;
daca pixelul rezultat este intr-o pozitie
egala:
culoarea rezultata (verde) = U;
altfel
culoarea rezultata (verde)
= V;
culoarea rezultata (albastru) = D;
Figura 7.17: Pseudocodul pixel shader-ului pentru rezultatul YUVD.
Pseudocodul pixel shader-ului pentru rezultatul
YUVD este prezentat in figura 7.17. Codul pixel
shader-ului Cg pentru rezultatul YUVD este prezentat
in apedicele A.5. Cand
este compilat cu profilul fp30,
programul Cg are 13 instructiuni (din care 3 sunt aduceri de structura).
Performantele for fi discutate in capitolul 8.
Corectie gama in hardware
Toate placile grafice curente suporta corectia
gama in hardware. Aceasta inseamna ca RAMDAC al cardului grafic poate efectua
corectia gama in hardware astfel incat aplicatia grafica nu mai este nevoita sa
faca acest lucru in software. Aceasta nu poate fi aplicata, dar poate interfera
cu rezultatul YUVD. Este foarte important ca
continutul YUVD din framebuffer sa fie trimis exact la placa hardware fara
interferenta corectiei gama in hardware. Aceasta
inseamna ca trebuie oprita. Unele jocuri pot fi configurate sa isi foloseasca
propriul software de corectie gama in locul celei
realizate de hardware. Aceasta
este o solutie mai timpurie. Daca
jocul nu poate fi configurat sa nu foloseasca corectia gama
in hardware, atunci este nevoie de o alta solutie. O solutie posibila este
sa determini wrapper-ul sa dezactiveze corectia gama
in hardware. Atunci inseamna ca corectia gama
trebuie sa fie realizata in alta
parte, fie in pixel shader-ul YUVD sa in placa hardware. Niciuna din aceste
optiuni nu a fost implementata in prezent.
Filtrare morfologica
Cu redarea RGBD noi vrem sa repetam fundalul (si
nu prim-planul) sa umple descoperirile, asa cum a fost explicat in sectiunea
5.1.3. Aceasta
este o problema cu continutul care
a fost anti-aliased. Efectul de anti-aliasing este
acela ca culoarea unui pixel pe o margine a unui obiect este
o combinatie de culoare a obiectului (prim planul) si obiectul din spatele lui
(fundalul). Valoarea adancimii in harta
de adancime are doar o singura valoare: prim plan sau fundal. Pentru a fi
sigure ca algoritmul de redare RGBD repeta culoarea de fundal corecta cand are
loc o descoperire , harta de adancime poate fi pre-procesata cu un filtru
morfologic. Filterul
este ales astfel incat sa
"creasca" obiectele din prim plan. O implementare simpla (si scumpa) este de a inlocui fiecare valoare de adancime in harta de
adancime prin minim 8 valori de adancime invecinate (si valoarea adancimii
insasi, astfel sunt 9 in total). Aceasta implementare se numeste morth 3x3. S-au
dezvoltat alte 2 variante, numite morph 3x1, morph 5. Ele sunt mai putin scumpe
deoarece ele calculeaza minimul prin folosirea valorilor de adancime mai putin
invecinate.
Filtrarea morfologica nu este
folosita de pixel shader-ii de redare RGBD ai nostri pentru ca ei alte
artefacte mult mai severe, dar are sens pentru algoritmul de redare RGBD
offline. Dupa ce au fost comparate calitatea si performanta, s-a decis ca
varianta morph 3x1 are cel mai bun raport calitate/performanta. Folosind o
varianta mult mai scumpa, are un efect aditional foarte mic asupra calitatii.
Codul Cg pentru pixel shader-ul YUVD rezultat cu filtrarea morfologica
integrata este prezentat in apendicii A.6, A.7 si A.8.
Capitolul 8
Masuratori si analize de performanta
De vreme ce sofware-ul nostru va fi folosit pentru a juca jocuri
interactive, performanta este un aspect important.
Noi am masurat si analizat performanta atat a
sofware-ului nostru cat si a aceluia deja existent de "redare multipla".
Rezultatele sunt prezentate in acest capitol.
Se va masura mai intai performanta totala
(framerate-ul obtinut) in doua jocuri. Folosind aceste masuratori noi vom trage
cateva concluzii. Apoi vom arunca o privire mai atenta la diverse aspecte
detaliate care determina aceasta performanta generala.
Se va masura doar performanta sofware-ului
nostru, si nu si calitatea. Masurarea calitatii este
o sarcina dificila si (partial) subiectiva, care nu este
considerata utila in prezent. Calitatea diferitelor redari RGBD a fost deja
descrisa in sectiunea 7.4.4.
8.1 Sisteme
Pentru masuratorile de performanta s-au folosit
doua PC-uri:
l Sistem '5950': Pentium 4 (2.4 Ghz), Nvidia
Geforce FX 5950 ultra (AGP 8x)
l Sistem '6800': Pentium 4 (2.4 Ghz), Nvidia
Geforce FX 6800 ultra (AGP 8x)
Sistemul "6800' are un card grafic de cea mai
noua generatie, cardul grafic din sistemul '5950' este
cu o generatie in urma. Ambele carduri sunt versiunea cea mai rapida a
generatiei lor. Conducand masuratorile pe doua sisteme, ne permite sa analizam
cum scaleaza softare-ul in ceea ce priveste noile carduri grafice.
Ambele sisteme au hard- si sofware aproape
identice, cu exceptia marimii memoriei, dar aceasta nu ne va afecta
masuratorile. Software-ul instalat pe ambele sisteme include urmatoarele:
l Windows XP
l Drivere 66.93 Nvidia ForceWare
l Nvidia Cg Compiler Release 1.3 Beta 2
8.2 Performanta generala
Performanta generala a unei aplicatii grafice poate fi masurata in
cadre/secunda (frames/seconds - fps). Un joc poate fi jucat in timp real daca
numarul de cadre pe secunda ramane mai mare de o anumita valoare minima. In
general, 30-40 de cadre pe secunda este considerat pentru a juca jocurile in
timp real.
8.2.1 Masuratori
O serie de teste au fost realizate pentru a
determina performanta generala in doua jocuri: Quake 3 si Doom 3. Quake 3 este un joc vechi (lansat in 1999) in timp ce Doom 3 este un joc destul de recent (lansat in 2004). In fiecare
joc s-a inregistrat un demo care mai tarziu a fost redat, in timp ce se foloseau
urmatorii wrapper/pixel shader:
fara wrapper: Jocul foloseste fisierul opengl32.dll directl. Rezolutia mentionata
in table este rezolutia la care este
redat jocul
wrapper gol: Jocul foloseste un empty wrapper DLL. Acest wrapper DLL doar trimite functii de apelare la DLL
original, fara a face nimic altceva. Rezolutia mentionata in table este
rezolutia la care este redat jocul
redarea a 9 vederi: jocul foloseste wraper DLL-ul de "redari
multiple", asa cum a fost explicat
in capitolul 3. Jocul
este redat la 533x400 si rezolutia
imaginii interpolate
rezultate este de 1600x1200
RGBD de adancime rezultata: jocul foloseste DLL wrapper-ul nostru cu pixel
shader-ul de redare RGBD a adancimii rezultate (vezi sectiunea 7.5.4). Jocul este
redat la 800x600 si rezolutia imaginii interpolate rezultate este
de 1600x1200. Structura de adancime intermediara nu este
redusa: ea este de asemenea: 800x600.
RGBD de adancime introdusa: Jocul foloseste wrapper DLL-ul cu pixel
shader-ul de redare RGBD a adancimii introduse (vezi sectiunea 7.4.7). Pixel
shader-ul foloseste un interval de cautare de 11 pixeli in imaginea introdusa.
(Efectul intervalului de cautare asupra performantei este
masurat in sectiunea 8.3). Jocul
este redat la 800x600 si rezolutia
imaginii rezultate interpolate este de 1600x1200.
Structura intermediara de adancime nu este redusa:
ea este de asemenea: 800x600.
Rezultat YUVD: Jocul foloseste wrapper-DLL-ul nostru cu pixel
shader-ul rezultatului YUVD (vezi sectiunea 7.5). Jocul este
redat la 720x540 si rezolutia rezultatului YUVD este
de asemenea 720x540.
Rezultatele masuratoriloe sunt prezentate in
tabelul 8.1 si 8.2. Varianta de redare RGBD a adancimii introduse
gaseste-cel-mai-apropiatnu a fost inclusa in masuratori deoarece este
mai inceata decat varianta adancime rezultata, dar produce rezultate mai bune
(vezi sectiunea 7.4.6).
In tabele sunt prezentate doua numere:
cadre/secunda (fps) si timp pe cadru (ms/cadru). Timpul pe cadru este
pur si simplu reciproca framerate-ului (ratei cadrului).
8.2.2 Analize
Ce se poate concluziona din masuratorile de
performanta generala?
l
Jucarea de jocuri
recente pe monitorul 3D este posibil doar cu un
wrapper de redare a adancimii rezultate (33.4 fps) sau cu wrapper-ul de YUVD rezultat in combinatie cu redarea hardware (52.7
fps). Framerate-urile mentionate sunt pentru un card 6800.
test
|
Geforce 5950
|
Geforce 6800
|
fps
|
ms/cadru
|
fps
|
ms/cadru
|
fara wrapper (800x600)
|
|
|
|
|
fara wrapper (1600x1200)
|
|
|
|
|
wrapper gol (1600x1200)
|
|
|
|
|
redare de 9 vederi
|
|
|
|
|
RGBD a adancimii rezultate
|
|
|
|
|
RGBD a adancimii introduse
|
|
|
|
|
YUVD rezultat (720x540)
|
|
|
|
|
Tabelul 8.1: Masuratorile performantei generale pentru jocul Quake 3
test
|
Geforce 5950
|
Geforce 6800
|
fps
|
ms/cadru
|
fps
|
ms/cadru
|
fara wrapper (800x600)
|
|
|
|
|
fara wrapper (1600x1200)
|
|
|
|
|
wrapper gol (1600x1200)
|
|
|
|
|
redare de 9 vederi
|
|
|
|
|
RGBD a adancimii rezultate
|
|
|
|
|
RGBD a adancimii introduse
|
|
|
|
|
YUVD rezultat (720x540)
|
|
|
|
|
Tabelul 8.2: Masuratorile
performantei generala pentru jocul Doom 3
l
Shader-ul RGBD al
adancimii itroduse este foarte incet, chiar si pe un
card 6800 (2.5fps). Un card grafic ar trebui sa fie de cel putin 12 ori mai
rapid pentru a permite acestui shader sa ruleze la o viteza acceptabila (30
fps). Pentru masuratorile noastre de performanta noi am folosit un interval de
cautare mic de 11 pixeli introdusi. Aceasta limiteaza efecturl 3D. Pentru a
produce un output de aceeasi calitate ca cea a algoritmului de redare offline,
nivelul de cautare trebuie sa fie marit. De asemenea pixel shader-ul trebuie sa
fie imbunatatit (sa se ocupe de miscarea lentilei, sa aiba o filtrare mai buna,
etc). Acestea doua combinate ar face pixel shader-ul mult mai incet. De aceea,
noi putem fi siguri ca redarea RGBD a adancimii introduse pe cardul grafic nu
va fi o optiune viabila in viitorul apropiat (presupunand ca modelul de
programare nu se schimba).
l
Shader-ul RGBD al
adancimii introduse ruleaza de 15 ori mai repede (in medie) pe un card grafic
Geforce6800 decat pe o Geforce 5950. Aceasta poate fi explicata (partial) prin
faptul ca 6800 are unitati de pixel shader mai multe si mai rapide. Nu suntem
siguri ca aceasta caracteristica va continua in acelasi raport in viitorul
apropiat.
l
Wrapper-ul YUVD
rezultat are o transmitere de informatie mica (media 4.15 ms/cadru pe un
Geforce 5950 si de 1.25ms/cadru pentru un Geforce6800) in comparatie cu 'fara
wrapper 800x600'.
8.3 Performanta detaliata
In plus fata de masurarea framerate-ului general obtinut, noi dorim sa
stim cat de mult timp ia sarcinilor individuale sa fie realizate. Daca ne uitam
la munca realizata pentru un cadru, o putem imparti in doua parti majore:
timpul necesar aplicarii sa redea vederea centrala si timpul suplimentar
necesar wrapper-ului sa isi execute sarcina. Munca wrapper-ului poate fi
divizata mai departe in doua parti mai mici:
- Timpul de redare al aplicatiei
- Timpul de transmitede de informatie a
wrapper-ului
(a) Salveaza & seteaza statusul
(b) Actualizeaza structurile
(c) Conversia lui Z la D
(d) Redare RGBD
(e) Restoreaza statusul
Pentru YUVD rezultat, impartirea este
cumva diferita: nu exista un pas separat
de conversie Z in D de vreme ce aceasta este
introdusa in pasul YUVD rezultat
- Timpul de redare al aplicatiei
- Timpul de transmitede de informatie a
wrapper-ului
(a) Salveaza & seteaza statusul
(b) Actualizeaza structurile
(c) Rezultat YUVD
(d) Restoreaza statusul
8.3.1 Formula framterate-ului
Framerate-ul obtinut f (in
cadre/secunda) a aplicatiei grafice in combinatie cu wrapper-ul nostru este determinat de urmatoarea formula:
(8.1)
unde a este timpul (in secunde)
necesar pentru aplicatie de a reda un cadru iar w este
timpul de transmitere de date (in secunde) necesar pentru wrapper sa-si faca
treaba. Sa presupunem ca o aplicatie (fare wrapper) obtine un framerate de 50
cadre/secunda. Aceasta inseamna ca a este
0.02secunde (20ms). Daca folosim aceasta aplicatie in combinatie cu un wrapper
care necesita 10ms pentru a-si realiza sarcinile, atunci framerate-ul va scadea
la 33.3 fps.
Formula 8.1 este o simplificare a situatie
reale. Folosind DLL wrapper-ul aceasta nu afecteaza numai timpul w ci si
timpul a necesar de aplicatie pentru a reda un cadru. Aceasta deoarece
transmiterea apelurilor de functie la DLL original dureaza ceva timp. Noi vom
ignora acest efect in calculele noastre, dar acest efect este
vizibil in tabelul 8.1. Exista o diferenta in framerate-ul dintre 'fara wrapper
(1600x1200)' si "wrapper gol" (1600x1200)'.
Noi vrem sa aflam cat de mare este timpul
de transmitere de date w pentru diferite programe wrapper/pixel shader.
Aceasta ne va da posibilitatea de a calcula noul framerate al unei aplicatii
grafice dat fiind un framerate original (fara wrapper).
De asemenea, software-ul nostru are multi parametri (rezolutii
diferite, intervale de cautare, etc). Noi dorim sa stim cum afecteaza acesti
paramentri performanta pasilor individuali prezentati in sectiunea 8.3 si cum
se ridica aceasta.
Parametri
E posibil ca parametrii din tabelul 8.3 sa afecteze performanta
software-ului nostru.
rezolutia de redare a
aplicatiei (#pixeli)
Culoare adancime (bits per pixel)
precizia Z-buffer-ului (bits per pixel)
Rezolutia structurii de adancime (#pixeli)
Rezolutia RGBD sau YUVD rezultata (#pixeli)
Latimea structurii functiei Z in D (#texeli)
|
Na
Bc
Bz
Nd
No
Nf
|
Tabelul 8.3. Parametrii care pot afecta performanta sofware-ului
nostru.
8.3.2 Masuratori
Fiabilitatea
Noi am adaugat un cod in wrapper-ul nostru care
masoara cat de mult timp ia efectuare pasilor prezentati in sectiunea 8.3.
Lucrul acesta nu este la fel de simplu precum pare:
nu este de ajuns sa se masoare timpul pentru a
apelarile functiei OpenGL. Apelarile
OpenGL doar trimit cereri de
redare la buffer. Redarea actuala este realizata mai
tarziu pe placa grafica, in mod nesincronizat. Pentru a obtine rezultate bune
de timp, noi trebuie sa asteptam pana cand apelurile OpenGL au fos executate
(de hardware). Apelul glFinish poate fi folosit in acest sens: el blocheaza
pana cand comenzile anterioare au fost executate. El sincronizeaza CPU cu GPU.
Aceasta inseamna ca codul de timp din wrapper afecteaza masuratorile, ceea ce
este inevitabil.
In urmatoarele 5 sectiuni se vor discuta masuratorile individuale. Noi
vom masura urmatorii pasi:
l
Actualizarea
structurilor
l
Z la D
l
Redare RGBD a
adancimii rezultate
l
Redare RGBD a
adancimii introduse
l
YUVD rezultat
Noi nu vom masura timpii consumati de pasii salveaza si seteasa
statusul si restoreaza statusul deoarece acestia sunt foarte mici.
Actualizarea structurilor
Actualizarea structurilor copiaza framebuffer-ul (atat RGB cat si Z)
in doua structuri. Deoarece aceasta se realizeaza cu o memorie de copiere pe
cardul grafic insusi, timpul pe care acesta il consuma va fi proportional cu
rezolutia redata de aplicatie (Na). Alti doi parametri care
afecteaza performanta sunt adancimea culorii a buffer-ului color (Bc)
si precizia z-buffer-ului (Bz).
Noi am variat valoare Na si am masurat timpul consumat
pentru actualizarea structurilor, (cu Bc = 8 si Bz = 24).
Asa cum era de asteptat, performanta s-a ridicat in mod liniar cu rezolutia.
Aceasta a avut ca rezultat 1.56ns/pixel pe Geforce5950 si 0.69 ns/pixel pe
Geforce6800. Folosind aceste rezultate noi putem calcula viteza la care ambele
carduri grafice pot copia memoria intern.
(8.2)
(8.3)
Aceste rezultate par corecte.
Noi am folosit doar un buffer color de 32-bits si un z-buffer de 24
bits deoarece buffer-ul color de 16-bits si cel Z de 16 bits nu mai sunt
folositi de jocurile moderne.
Din Z la D
Timpul de transformare a lui Z la D va lua un timp proportional cu
rezolutia structurii adancimii rezultate (Nd).
Alti parametri care pot fi variati sunt rezolutia datelor intrate (textura
Z-buffer, Na) si latimea
(in texeli) a structurii functie (Nf).
Noi am variat rezolutia structurii adancimii Nd si am masurat timpul necesar de transformare a lui Z
in D in timp ce am tinut alti parametri constanti (Na = 800x600 si Nf
= 256). Dupa cum s-a asteptat, performanta s-a scalat proportional cu
rezolutia. Aceasta a dus la 0.67ns/pixel pe un Geforce5950 si 0.24ns/pixel pe
un Geforrce6800.
Noi nu am masurat efectul creat de varierea rezolutiei structurii Z Na, dar acesta ar trebui sa
aiba un efect foarte mic. Numarul de aduceri de structura depinde numai de
rezolutia structurii adancimii rezultate: o aducere de structura este realizata
in structura Z si este pentru fiecare pixel in structura adancimii.
Schimband latimea structurii functie Z la D (Nf) nu a avut un impact masurabil asupra rezultatelor.
Redarea RGBD a adancimii rezultate
Performanta redarii RGBD a adancimii rezultate
depinde de urmatorii parametri: rezolutia structurii de culoare (Na), rezolutia structurii de
adancime (Nd), rezolutia
rezultata (No) si
parametrul de amplificare.
De departe, rezolutia rezultata este factorul cel
mai important. Noi am variat rezolutia rezultata No si am masurat performanta in timp ce am pastrat toti
ceilalti parametri constanti (Na
= 800x600 si Nd =
800x600). Dupa cum s-a asteptat, a existat o relatie liniara intre performanta
si rezolutia rezultata. Aceasta a dus la 12.07ns/pixel pe un Geforce5950 si
1.89ns/pixel pe un Geforce6800.
Variind rezolutia structurii adancimii (Nd) ar trebui sa aiba un
efect foarte mic asupra performantei deoarece numarul de executari de structuri
de adancime depinde numai de rezolutia rezultatului: o executare de structura
in structura de adancime este realizata pentru fiecare pixel in imaginea
rezultata. Noi nu am masurat acest efect.
Redarea RGBD a adancimii introduse
Performanta redarii RGBD a adancimii introduse
depinde de urmatorii parametri: structurii de culoare (Na), rezolutia structurii de adancime (Nd), rezolutia rezultata (No) si parametrul de
amplificare. Noi am variat rezolutia rezultata No in timp ce am
pastrat toti ceilalti parametri constanti (Na
= 800x600, Nd = 800x600 si
interval de cautare de 11 pixeli de intrare). Dupa cum s-a asteptat, a existat
o relatie liniara intre timpul necesar si rezolutia rezultata. Aceasta a dus la
3292 ns/pixel pe un Geforce5950 si 207.7 ns/pixel pe un Geforce6800.
Noi am variat de asemenea intervalul de cautare
si am pastrat toti ceilalti parametri constanti. Masuratorile rezultate sunt
prezentate in figura 8.1 si 8.2. Dupa cum se observa in aceste doua grafice,
relatia dintre intervalul de cautare si timpul necesar este ne-liniara. Lucrul
aceasta a fost de asteptat deoarece un interval de cautare mai mare inseamna ca
cache-ul structurii de pe cardul grafic este folosit mai putin eficient.
Figura 8.1: Masuratori RGBD ale adancimii introduse pe un 5950
Figura 8.2: Masuratori RGBD ale adancimii introduse pe un 6800.
Performanta pixel shader-ului YUVD rezultat
depinde in special de rezolutia rezultata (No)
si doar putin de rezolutia de redare a aplicatiei (Na). In mod normal, aceste doua rezolutii sunt una si
aceeasi, sau doar putin diferite. Cand se foloseste pixel shader-ul YUVD
rezultat are sens sa se lase jocul sa redea un cadru la aceeasi rezolutie ca
cea a YUVD rezultat. In acel caz, nu trebuie realizata nici o crestere sau reducere, deci nu se
pierde calitatea. Numai atunci cand jocul nu se poate reda la rezolutia ceruta
de YUVD rezultat, trebuie aleasa o alta rezolutie. De exemplu, atunci cand
jocul nu se poate reda la 720x540, este configurat sa se redea la 640x480 in
schimb.
Pixel shader-ul YUVD rezultat scaleaza automat
aceasta valoare la 720x540.
Exista un numar diferit de variante ale YUVD
output, ele difera numai in tipul de filtrare morfologica pe care ei o executa
z-buffer.
Noi am variat rezolutia rezultata No in timp ce am pastrat toti
ceilalti parametri constanti (Na
= 800x600). Dupa cum s-a asteptat, a existat o relatie liniara intre timpul
necesar si rezolutia rezultata. Rezultatele masuratorii sunt in tabelul 8.4.
8.3.3 Analize
Toate masuratorile detaliate sunt colectionate in tabelul 8.4:
etapa
|
simbol
|
ns/pixel
|
5950
|
6800
|
actualizare structuri
|
Put
|
1.56
|
0.69
|
Z inD
|
Pztod
|
0.67
|
0.24
|
RGBD adancime rezultata
|
Prgbd-od
|
12.07
|
1.89
|
RGBD adancime intrata
|
Prgbd-id
|
3292
|
207.7
|
YUVD
|
Pyuvd
|
7.87
|
1.54
|
YUVD morph 3x1
|
Pyuvd31
|
17.03
|
2.83
|
YUVD morph 5
|
Pyuvd5
|
21.04
|
4.17
|
YUVD morph 3x3
|
Pyuvd33
|
42.39
|
6.87
|
Tabelul 8.4. Masuratori de performanta detaliate
Ce poate fi concluzionat din masuratorile detaliate?
l
Pasul Z la D este
foarte rapid cand este comparat cu pasul de redare RGBD. Optimizarea acestuia
nu poate avea un efect mare.
l
YUVD rezultat cu
morph 3x1 este aproximativ de doua ori mai incet decat YUVD rezultat (fare
filtrare morfologica). Celelalte variante sunt mult mai incete, dar nu ofera o
imbunatatire efectiva a calitatii. De aceea, varianta morph 3x1 este
"recomandata" sa fie folosita.
Masuratorile din tabel pot fi folosite pentru a calcula framerate-ul
pe o aplicatie grafica, dati fiind unii parametri de intrare.
Sa incepem cu cazul cel mai simplu: YUVD rezultat. In acel caz,
framerate-ul f poate fi calculat dupa cum urmeaza:
(8.4)
Put este timpul necesar (in secude) etapei de
copiere in structura (per pixel). Poate fi gasit in sectiunea 8.3.2. Pyuvd este timpul necesar (in
secunde) de YUVD rezultat pentru fiecare pixel rezultat. Poate fi gasit in
tabelul 8.4.
Cu o formula asemanatoare, framerate-ul tuturor
ceilalte wrapper/pixel shader poate fi calculat. Nu vom discuta lucrul acesta
mai departe intrucat nu are efectiv nici o intrebuintare practica.