You are on page 1of 5

Algoritmul HLBVH mai simplu si mai rapid utilizand structura de

data de tip coada.



Abstract
HLBVH (Hierachical Linear Bounding Volume Hierarchies) este un algoritm dezvoltat
recent iar el demonstreaza fezabilitatea de reconstructie a indicelui spatial necesar
pentru o raza de urmarire in timp real, chiar si in prezenta a milioane de triunghiuri
care sunt complet dinamice. In aceasta lucrare este prezentata o varianta mai simpla
si mai rapida a algoritmului HLBVH, unde acesta foloseste structura de data de tip
coada. Noul algoritm este mult mai rapid si elimina necesitatea de stocare temporara
a datelor geometirce folosite la calcule intermediare. Noul algoritm dezvoltat este de
10 ori mai rapid pe un GPU decat pe un CPU.
Introducere
De peste 20 de ani, ray tracing (trasarea unei raze) a fost considerata o tehnologie
de randare a unei lini. Acest ray tracing a fost considerata ca o bariera deoarece era
greu de utilizat si era costisitor de implementat. Aceasta bariera a inceput sa scada in
ultimul deceniu, cand noul val de cercetare grafica a inceput exploatarea si
constructia de structuri de accelerare, la costuri de implementare scazute. Acestea
au fost implementate astfel: arhitecturi seriale [Wachter and Keller 2006; Wald et al.
2007a], arhitecturi paralele [Popov et al. 2006; Shevtsov et al. 2007; Wald 2007;
Lauterbach et al. 2009; Zhou et al. 2008]. Evolutiile recente in procesarea paralela
permit in domeniul randarilor in timp real, realizarea lor in cateva milisecunde, chiar si
in prezenta a milioane de triunghiuri pe deplin dinamice.
In aceasta lucrare este extinsa activitatea lui Pantaleoni si Luebke prin introducerea
unei noi variante a algoritmului (HLBVH), care este multa mai simpla, mai rapida si
mai usor de generalizat. Pe langa simplificarea foarte mare a alogritmului original,
noua versiune a acestui algoritm este mult mai rapida, eliminand depozitarea
temporara a datelor prelucrate, acest lucru reducand semnificativ timpul de executie
al acestuia. O alta imbunatatire adusa acestui algoritm este paralerizarea
suprafafetei euristice (SAH). Acest lucru ne perimite obtinerea unui factor de accelare
de 10 ori. Dincolo de aceasta accelerare si de paralelirizare, algoritmul poate fi rulat
doar pe GPU, eliminand astfel copiile de memorie dintre GPU si CPU care sunt mari
mancatoare de timp.
Ca si algoritmul HLBVH initial, noul alogirtm este implementat in CUDA. Pe o placa
viode GPU NVIDIA GTX 480, timpul necesar pentru rularea acestui algoritm este de
10,5 ms, pentru un model cu peste 1,75 milioane de poligoane.
Istoric
LBVH si HLBVH: baza muncii acestui articol este ca si pentru versiunea anterioara a
HLBVH, ideea originala implementata de Lauterbach care a furnizat un algoritm
foarte straniu, constructiei BVH. Principiu este foarte simplu: in masura 3D a unei
scene este discretizata folosinduse n biti per dimensiune, si fiecare punct este atribuit
pentru a coordona o linie de-a lungul unei curbe, din spatiul Morton de ordin n (care
poate fi calculat prin intercalarea cifrelor binare din coordonatele discretizate).
Primitivele sunt apoi sortate in functie de codul Morton a centrului de greutate pe
care-l detin. In cele din urma, ierarhia este construita de catre grupuri de primitive
formate din acelas cod de biti 3n.
HLBVH s-a imbunatatit pe algoritmul de baza in doua moduri: RTS, acesta a oferit un
algoritm de constructie rapida si aplica o strategie compresare-sortare-decompresare
pentru a exploata coeranta spatiala si temporala la intrare. In al doilea rand a
introdus un constructor hibrid de inalta calitate, in care partea de sus a ierarhiei este
construita folosind o suprafata euristica (SAH).
Ingo Wald [Wald 2010] raporteaza un constructor paralel de tip SAH BVH optimizat
pentru o arhitectura prototip de tip multi-core numita MIC. In mod similar cu ceea ce
facem noi, el construieste un planificator personalizat bazat pe task-uri executate prin
stuctura de date de tip coada, punand in aplicare modelul de lumina-greutate,
evitand cheltuielile de resurse hardware ridicate. Pentru a atinge viteza maxima, el
realizeaza o cuantificare semnificativa a casetei de incadrare, pentru a minimiza
traficul de memorie si de accesare a datelor dintr-o structura de matrice. Mai mult
decat atat, pentru a efectua pasul binning el foloseste o cantitate substantiala de
stocare locala, prin care mentine statistici bin duplicate per-thread si evita utilizarea
frecventa de instructiuni atomice de pe un set comun, iar toate acestea necesita o
etapa suplimentara de fuziune paralela. Toate acestea par a fi inutile pe arhitectura
utilizata de noi , cel mai probabil din cauza numarului mult mai mare de fire hardware
concurente acceptate. Rezultatele sunt raportate numai pentru scene destul de mici,
iar viteza de realizare este semnificativ mai mica (4-5 ori) decat ofera constructorul
hibrid SAH.
Griduri uniforme: Kalojanov si Slusallek [2009] au introdus algoritmi in timp real
pentru constructia de grid-uri uniforme. In timp relativ rapid, timpul de constructie
pentru aceasta metoda este mult mai mare decat pentru HLBVH, mai ales datorita
faptului ca partitionarea spatiala necesita dublarea de obiect, o etapa de amplificare
a datelor, care se traduce in latimea de banda mai mare. In plus, indicii spatiali
rezultati duc la o performanta mai mica, in timpul ray tracing, deoarece acesta nu
permite salturi eficiente peste spatiile goale. Varianatele ierarhice [Kolajanov el al.
2011] imbunatatesc in timp constructia si performanta randari, dar ele raman in
general mai lente.
Prezentare generala a algoritmului
Ca si in cazul algoritmului HLBVH original dezvoltat de [Pantaleoni si Luebke 2010],
algoritmii pot fi folositi pentru a crea atat un LBVH standard cat si un SAH hibrid de o
calitate superioara. Cu toate acestea, ca standard LBVH implica un subset de lucru
mecanic necesar pentru crearea unei variante de inalta calitate. Similar cu varianta
initiala a LBVH [Lauterbach et al. 2009], algoritmul nostru incepe sortarea de
primitive de-a lungul unei curbe Morton de 30 de biti incadrata intr-o scena. Spre
deosebire de [Pantaleoni si Luebke 2010] care foloseau compresia-sortarea-
decompresia pentru a accelera sortarea, aici se va efectua aceasta etapa a
algoritmului bazandu-se pe o forta bruta, dar mai eficienta. Cu toate acestea, in urma
observatiilor lui Pantaleoni si Luebke asupra codurilor Morton care definesc o grila
ierarhica, unde fiecare cod de 3n biti ce contine un voxel unic intr-o grila regulata cu
intrari 2n pe fiecare parte. Acesta este din nou un exemplu de algoritm de compresie
si codare, si poate fi implementat cu o singura operatie de compactare. Dupa ce sunt
identificate clusterele, am partitionat toate primitivele in interiorul fiecarui grup
folosind LBVH iar apoi am creat un arbore de nivel superior. Impartirea clusterelor se
face cu un constructor SAH, similar ca cel descris de Wald [2007]. Atat impartirea
spatiala divizata cat si constructorul SAH se bazeaza pe un sistem de tip task-coada,
care este asezat peste nodurile individoale ale ierarhiilor de iesire. In urmatoarele
sectiuni sunt descrise principalele componente ale acestui sistem in detaliu.
Sarcina Cozii
Algoritmul original LBVH efectueaza partitionarea in latime iar apoi creaza nodurile
de nivel ierarhic, incepand de la radacina. Ca fiecare nod de intrare ar putea avea la
iesire fie 0 sau 2 noduri, fiecare trece efectuand o suma x pentru a calcula pozitia
urmatoarelor noduri. HLBVH imbunatateste aceasta operatie, prin efectuarea unei
traversari in latime, aceasta fiind facuta partial, in care mai multe niveluri sunt
prelucrate in acelasi timp, emitand un intreg arbore pentru fiecare nod de intrare.
Dezavantajul acestei abordari este ca procesarea fiecarui nivel sau set de niveluri
necesita mai multe lansari din kernel separate, care este o operatie mare ca si
latenta. Vom face o abordare cu totul diferita, banzandu-se pe cozi, pe niste task-uri
simple, in cazul in care un task individual corespunde la procesarea unui singur nod.
La momentul executiei, fiecare deviere (unitatea SIMT fizica de lucru a NVIDIA GPU)
continua prelucrarea seturilor de sarcini pentru a procesa date de la intrarea cozi, iar
fiecare sa contine un task pe thread, folosind o singura memorie globala prin care se
actualizeaza capul cozii. Dup ce fiecare fir este ascuns si comutat de numarul de
iesire sarcinile pe care le va genera, toate firele ascunse participante la o suma mare
care si ea este ascunsa sa comuta pe diferenta de sarcina lor de la iesire in raport cu
baza comuna care este ascunsa. n cele din urm, primul fir ascuns efectueaz o
singur adaugare in memoria globala atomica prin care se calculeaza adresa de
baz din coada de ieire. Prin utilizarea unei singure cozi pentru fiecare nivel, putem
efectua toate operatiile in interiorul unui singur apel al nucleului, iar in acelasi timp
produce un arbore pe latime la iesire.
Ierarhia de emisie a divizari de mijloc
Inca o data, ne abatem de la abordarile anterioare folosite pentru a efectua o divizare
de mijloc in algoritmi LBVH si HLBVH care in timp genereaza primitivele de
partitionare exacte. Ne amintim ca fiecare nod corespunde la o serie consecutiva de
primitive sortate in functie de codurile lor Morton, si ca divizarea unui nod necesita
gasirea primului element din sir, al carui cod difera de elementul precedent. In cazul
in care a facut alegerea punctului de divizare a planului pentru nodul parinte. Din
pacate, localizarea nodului parinte mai intai necesita o pre calculare a hartii intre
primitive si intervalele continute, care la randul lor necesita o alta operatie de suma.
Vom evita acest mecanism complex de revenire la ordinea standard care ar putea fi
folosit pe un dispozitiv serial: mapam fiecare nod la un sigur fir de executie, si lasam
fiecare fir de executie sa-si gaseasca singur planul de separare. Cu toate acestea,
mai simpla este o bucla prin care intreaga gama de primite din noduri observa ca
este posibil sa se reformuleze problema ca o simpla cautare binara. De fapt, in cazul
in care nodul este situat la nivelul L, stim ca codurile Morton vor fi mai mari cu un bit
pe set. Daca vom gasi primul bit p care este mai mare sau egal ca l, prin care primul
si ultimul cod Morton din gama nodului difera, atunci este suficient sa se efectueze o
cautare binara pentru a localiza primul cod Morton care contine un bit 1 la p.
Raspunsul acestui proces este eficient datorita faptului ca, pentru un nod care
contine N primitive, care gaseste planul de divizare prin atingerea numai a O (log(N))
celule de memorie. Abordarea originala a atins si a prelucrat intregul set de coduri N
Morton.
Marele rafinament al frunzei: mediul de divizare ar putea fi uneori esuat, ceea ce
duce la nivele mari de frunze. Atunci cand este detectat un astfel de esec, este usor
sa se imparta aceste nivele in obiecte-mediana.
Calculul casetei de incadrare: dupa ce topologia algoritmului BVH a fost calculata,
vom rula o procedura simpla de jos in sus pentru a calcula caseta de incadrare a
fiecarui nod din arbore. Procesul este realizat simplu, prin faptul ca BVH este stocat
pe latime mai intai. Vom utiliza un singur nucleu pentru lansare pentru fiecare nivel
din arbore si pe fiecare nod din nivel.
Parallel Binned SAH Builder
Asa cum s-a discutat anterior, vom rula un algoritm arbore SAH, optimizat pentru
clustere grosiere, definit pe primi 3 biti 3m din curba noastra Morton, cu m aflat tipic
intre 5 si 7. Inainte de-a trece la descrierea detaliata a algoritmului , observam ca
algoritmii nostri pot rula intr-un volum de memorie marginita: daca este procesat de
N clustere, ne trebuie sa prealocam spatiu doar pentru 2Nc- un nod.
La acest pas vom trata un cluster cu prioritate, ce agregheaza o caseta de incadrare
ca pe o primitiva. Inca o data, vom despartii calculul de task-uri si le vom organiza
intr-o singura coada de intrare si o singura coada de iesire. Fiecare activitate ii
corespunde unui nod care trebuie sa fie divizat, si este descris de 3 campuri, cutia
nodului de incadrare, de numarul de clustere din interiorul nodului si de id-ul nodului.
Alte doua campuri sunt calculate on-the-fly, iar cel mai bun plan de divizare este id-
ul primului copil al task-ului divizat. Apoi vom stoca aceste campuri intr-o structura de
tip matrice, unde se mentin retelele separate fiind indexate de id-ul task-ului. In plus,
vom tine intr-o matrice de tip cluster id-ul de divizare, care creaza pentru fiecare nod
curent cate o harta, din care fac parte operatiunile de divizare.
Bucla incepe prin atribuirea la toate clusterele a nodurilor root, formand sarcina de
divizare 0. Apoi pentru fiecare iteratie din bucla se vor parcurge urmatori 3 pasi:
binning: este un pas analog ce este descris de Wald, unde fiecare nod din caseta de
incadrare este divizat in M, deobicei 8 containere , de forma si dimensiune
egala.Initial un bit stocheaza o caseta de incadrare goala si un numar. Vom acumula
fiecare cluster de casete de incadrare intr-un grup ce contine centrul de greutate si
incrementeaza automat numarul de clustere. Aceasta procedura se executa in
paralel pe cluster: fiecare fir se uita la un singur cluster si acumuleaza cate o caseta
de incadrare in corespondenta cu sarcina de divizare, folosind minimul sau maximul
atomic.
evaluarea SAH: pentru fiecare sarcina de divizare in coada de intrare, vom evalua
suprafata metrica pentru toate planurile de divizare, intre fiecare dimensiune uniform
distribuita vom selecta cea mai buna valoare. Daca sarcina de divizare contine un
singur cluster, ne vom opri subdivizarea, in caz contrar, vom crea doua task-uri de
divizare cu casete de incadrare si la stanga si la dreapta care sa determine ruptura
SAH.
distributia clusterelor: maparea dintre clustere si sarcina de divizare este
actualizata, maparea fiecarui cluster la una dintre cele doua iesiri ale task-ului de
divizare este generata de catre proprietarul anterior. In scopul de-a determina noua
sarcina de divizare dupa id, este suficient sa comparam i-th cluster bin id cu
valoarea retinuta de la zona de divizare cea mai buna care ii corespunde task-ului de
divizare:
int old_id = cluster_split_id[i];
int bin_id = cluster_bin_id[i];
int split_id = queue[in].best_split[ old_id ];
int new_id = queue[in].new_task[ old_id ];
cluster_split_id[i] =
new_id + (bin_id < split_id ? 0 : 1);
In concluzie, retineti ca exista o anumita flexibilitate in ordinea executarii algoritmului.
De exemplu retehnologizarea poate fi efectuata separat pentru fazele de pe nivelul
inferior si de pe cel superior cu o mare precizie.

You might also like