Seminarium z Grafiki: Hardware Occlusion Culling - linki i luźne uwagi Michalisa
Podstawy
ARB_occlusion_query spec (W szczególności "Usage Examples" na końcu.)
GPU Gems 1, "Chapter 29. Efficient Occlusion Culling"
Co to jest occlusion culling? Jeden obiekt zasłania drugi, ale oba są we frustum. Czyli frustum culling nie pomaga. Z-bufor oczywiście rozwiązuje problem zbyt późno.
Techniki occlusion culling o których *nie* będziemy opowiadać:
Zakładamy że nie chcemy dzielić sceny na komórki, nie chcemy robić portali, PVS. Portale (badane w czasie renderowania albo PVS) wymagają żeby scena była stosunkowo indoors, i wymagają interwencji artysty żeby były dobre. Automatyczne generowanie portali jest możliwe, ale trwa długo.
Czyli chcemy uniknąć całego zamieszania z portalami i podziałem modelu na jakieś komórki, pokoje, mając nadzieję że algorytm będzie 1. dobry do dynamicznych scen 2. działał out of the box na dowolnych scenach.
Nie chcemy hierarchical occlusion maps (by Zhang, praca (dissertation.pdf). Krótszy opis np. tutaj albo w "Real-time Rendering" (Moller, Haines) 9.7.6.).
(Bo w HOM trzeba wybrać occluderów, wygenerować view-dependent mapę, Depth Estimation Buffer, testowanie używając tego. Trochę roboty...)
Były i inne pomysły
- hierarchical z buffer
- http://www.cgg.cvut.cz/members/bittner/ http://www.cgg.cvut.cz/members/bittner/publications/cgi98-copy.pdf http://www.cgg.cvut.cz/members/bittner/abstracts.html#cgi98
Demo that naive method is really bad. Use this code:
glBeginQueryARB(GL_SAMPLES_PASSED_ARB, Shape.OcclusionQueryId); OcclusionBoxStateBegin; glDrawBox3dSimple(Shape.BoundingBox); OcclusionBoxStateEnd; glEndQueryARB(GL_SAMPLES_PASSED_ARB); glGetQueryObjectuivARB(Shape.OcclusionQueryId, GL_QUERY_RESULT_ARB, @SampleCount); if SampleCount > 0 then DoRenderShape;
E.g. on gate (extreme example, occlusion query is useless there, and with naive version it even hurts). But also on regular_labirynth with chinchilla (where occl query could help a lot): even there, naive version is simply worse than brute force.
Dobra metoda: zawsze rób coś pomiędzy query a rendering. Np. pytaj o query dopiero w *następnej* ramce.
Ponadto, sortuj obiekty opaque (od najbliższego do najdalszego(czyli na odwrót niż do obiekty transparent do blending)), żeby było dużo zasłaniania.
Właściwie, dla wielu scen to już koniec, pozostało dodać szczegóły implementacyjne, ale już jest całkiem fajnie i szybko.
Uwaga na zmiany stanów:
Zoptymalizowałem zmiany glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE), glDepthMask(GL_FALSE), zmienną InsideOcclusionBoxState. Idea: mamy bardzo wiele shapes które używają tego stanu. Efekt: FPS z 43 do 110 !!! (bzwgen, with sorting, view building 1).
Generalnie, rendering bbox trzeba super-zoptymalizować, inaczej zyski z occlusion culling będa dużo mniejsze.
Demo:
City view from bzwgen: for building 1 view:
- without occlusion query, 70 fps (852 shapes inside frustum)
- with occl query but no sorting, 91 fps (125 shapes visible)
- with occl query and sorting, 110 fps (only 2 shapes visible!!)
Widać przy okazji że sortowanie się zdecydowanie opłaca.
gate: bad case, occlusion culling has no chance to help.
regular_labirynth without chinchilla.
regular_labirynth with chinchilla: enormous speedup.Tak jak napisali w pracy: technika ma zastosowanie tylko jeśli unikamy renderowania obiektów które same mają dużo trójkątów / skomplikowane teksturowanie, czyli duże obciążenie ne geometry/fragment.
Notka: tak jak zauważyli w pracy, mamy czasami migotanie pomiędzy bbox/visible (kiedy bbox jest visible ale obiekt nie), to powoduje że fps się nieustannie "chwieje", ale w praktyce nie jest problemem.
Jak to się ma do innych technik które eliminują część obiektów? Np. w moim engine mam frustum culling, podział na opaque / transparent (dla shadow volumes trzeba je renderować inaczej, obiekty transparent nie mogą być shadow receiverem), no i np. dla env mapping nie można wyświetlać obiektu z którego robimy env mapę.
Na szczęście, przy prostym podejściu problemu po prostu nie ma:
Only when the shape passes all other tests (like only transparent/only opaque, or frustum culling) then the result from previous occlusion query is used and new query is done. So when object is culled by other means, it doesn't hurt us.
Lag 1 frame: jest jednak problematyczny, niekiedy można go dostrzec. Przykład na regular_labirynth (wychylanie się zza korytarza, przy wyłączonym lighting lepiej widać). Chociaż view3dscene akurat ma gorzej, bo nie robi AutoRedisplay. Typowa gra ma AutoRedisplay, i artefakty znikają szybciej, ale widać że są.
Coherent Hierarchical Culling
GPU Gems 2, "Chapter 6. Hardware Occlusion Queries Made Useful"
Chcemy wykorzystać strukturę drzewiastą do odpytywania, aby móc odrzucać całe gałęzie drzewa pojedynczym query.
Ponadto, nie chcemy mieć lag 1 frame. Więc połączymy traversing drzewa (u mnie — 8-kowego, ale technika działa z każdym drzewem (kd, bsp, bvh...)) z odpytywaniem. Unikniemy problemów z latency odpytywania po prostu robiąc renderowanie przez ten czas.
Jak wygląda równoległość query i traversing? Pokaż wyniki z
Writeln('-------------------------- start'); Writeln('got query, stall: ', TraversalStack.Count = 0); Writeln('traversing node');
Czyli query rzeczywiście napływają w czasie pracy traversing, więc cała ta zabawa ma sens.
Notki: tak, TravsersingStack musi być stackiem, nie queue, to ma sens żeby zachować porządek front-to-back. Ich pomysł z priority queue też wydaje się sensowny.
Note: we don't need to have exactly one shape per leaf, more than one shape and even shapes duplicated in leaves also work without problems. However, bad octrees with lots of duplication may hurt us.
Jakieś wnioski? Na Radeon X1600 (chantal) czasami hierarchical jest super, a czasami bez hierarchical (proste occlusion query per shape) jest lepiej. Natomiast np. na GeForce FX 5200 (kocury) (dużo starsze GPU!) hierarchical jest zawsze super.
Chociaż ilości widocznych shapes i sprawdzonych boxów) wskazują prawie zawsze na hierarchical jako na zwycięzcę (ma nie tylko mało obiektów widocznych, ale ma też mało testów z boxami dla culling), ale mimo to bywa bardziej wolny.
Problemy specyficzne pod fglrx (chantal):
Na fglrx widać też że GPU się "krztusi" podczas hierarch occl culling: FPS maleją z ramki na ramkę, nawet podczas renderowania dokładnie tego samego z takim samym rezultatem.
Demo krztuszenia z hierarchical: bzwgen city view after building 1, view 2 also.
To "krztuszenie" jest tylko na fglrx (to samo GPU pod Mac OS Xem nie ma juz tych problemow), jak zwykle fglrx nie grzeszy jakoscia... Tym niemniej, pod Mac OS Xem, nie krztusi sie ale tez nie zyskujemy zawrotnie duzo FPS. W zasadzie, jest lepiej od bez oq, ale gorzej od prostej metody z oq. Jak widac, renderowanie boxow z occl query jest istotnym narzutem na fragment stage. Byc moze nie ma na Radeonie na chantal wsparcia specjalnego dla sytuacji gdy glColor/DepthMask są wylaczone (czytalem ze nowsze karty NVidii zdecydowanie takie wsparcie maja).
Na NVidia GeForce FX 5200 (kocury) tego oczywiście nie ma.
Staram sie nie wyciagac z tego ogolnych wnioskow, wartosciowe byloby wiecej testow na roznych GPU. (Zamiast na starej NVidii i nieznacznie nowszym Radeonie).
Gdzie jest ogólny problem z hierarchical? Profilowanie nie wykazało niczego kosztownego po stronie CPU. Moja teoria to że obciążenie na fragment stage jest duże bo boxy węzłów drzewa są duże.
Po dodaniu
Writeln('Rendering box ', Box3dToNiceStr(Box), ' ', Box3dArea(Box, 0):10:2);
widać że renderujemy mnóstwo Boxów o dużym area przy hierarchical, dużo większe (~ponad 10 razy) są powierzchnie węzłów do hierarchical oq niż do oq per shape. Demo: Można obejrzeć te boxy, wystarczy nie wyłączać glColor/DepthMask.
Na GeForce FX 5200 (kocury) czasy:
- regular_labirynth z default (perfidny) view: bez oq: 1 fps oq per shape: 30 fps hierarchical oq: 85 fps - bzwgen1_final (after passing through building 1 bez oq: 40 fps oq per shape: 60 fps hierarchical oq: 40 fps (hm, nie tak super, ale się nie krztusi)
Czasy na chantal: demo.
Trochę optymalizacji zrobionych:
Kiedy liść jest widoczny, renderuj jego faktyczną geometrię jednocześnie robiąc occlusion query (zamiast używać boxa), oczywiste.
VISIBILITY_KEEP_FRAMES ("Conservative Visibility Testing"), ale chyba marnuje więcej niż pomaga w moich testowych scenach.
RenderLeafNodeVolume - to już moja specyfika (i problem) że w liściach mam wiele shapes i box liścia niekoniecznie jest optymalny. Ale może jest edukacyjne, 1 wersja powoduje dużo visible shapes (Node.Box), 2 wersja powoduje dużo boxes (for each shape render box), 3 wersja najlepsza (for each shape sumuj box, render box once).
Możliwość ustawienia VisibilityThreshold > 0, chociaż stosunkowo useless w moich testach.