Z-Compression
e Z-Occlusion culling
Le
tecniche di compressione dello Z-buffer si basano su alcuni
semplici assunti:
1)
Nei moderni chip 3D non viene manipolato lo Z-value come valore
di profondità del punto rispetto all'osservatore ma
viene memorizzato nello Z-Buffer e gestito il suo reciproco
(1/Z). Questo stratagemma ha vari vantaggi (come una risoluzione
maggiore in prossimità del punto di osservazione) tra
cui è particolarmente importante il seguente: è
possibile calcolare il valore 1/Z dei punti interni ad un
triangolo semplice come interpolazione lineare del valore
1/Z dei vertici.
2)
In realtà, "propagandosi" lo Z-value (1/Z)
linearmente sul triangolo, per calcolare il valore di punti
interni al triangolo è sufficiente conoscere lo Z-value
di pochi altri punti vicini.
Partendo
da queste osservazioni possiamo quindi tentare di ottimizzare
la gestione dello Z-buffer maneggiando non tutti i valori
contenuti in una certa porzione dello schermo ma solo alcuni
valori significativi dai quali calcolare tutti gli altri internamente
al chip.
Nella
figura qui a destra abbiamo supposto di suddividere lo Z-buffer
in blocchi di 4x4 pixel. Quando andiamo a disegnare un triangolo
avremo tre situazioni:
1. Il blocco viene completamente coperto dal triangolo
2. il blocco viene coperto parzialmente dal triangolo
3. blocchi non interessati dal triangolo
Nel caso 1, utilizzando le osservazioni
fatte sopra, si potrebbe evitare di calcolare e scrivere nello
Z-buffer tutti i valori di Z ma ci si potrebbe limitare ad
indicare, in una apposita memoria di supporto (volendo anche
interna al chip per blocchi più grandi) che il blocco
contiene dati compressi e memorizzare nello Z-Buffer soltanto
i valori agli estremi del blocco stesso, lasciando che sia
poi il chip a generare i restanti punti nel caso in cui sia
necessario. Nel caso 2 si procede invece come al solito calcolando
lo Z in tutti i punti ed indicando che il blocco contiene
dati in forma completa riportando nella solita memoria di
appoggio il valore Z-value massimo contenuto nel blocco (servirà
poi per lo Z-Occlusion Culling).
La compression che ne deriva è (ATTENZIONE)
relativa alla banda passante necessaria per maneggiare lo
Z-buffer e non relativa alla riduzione di spazio occupato
in memoria per lo Z-buffer, infatti per il blocco 1 molte
delle 16 locazioni di memoria rimarranno semplicemnete vuote.
Del resto a noi interessa di più la banda passsante
dello spazio occupato.
In pratica quando si va a disegnare
un triangolo come quello sopra, normalmente saremo costretti
a scrivere nello Z-buffer 90 Z-value (uno per punto del triangolo
che in questo caso è di 90 punti) mentre adesso i blocchi
contrassegnati 1 possono essere liquidati addirittura con
1 solo valore (tanto abbiamo quelli circostanti). Risultato:
dobbiamo scrivere solo 60 Z-value + 9 Z-value max, complessivamente
una riduzione del 25%.
E' chiaro che i vantaggi sono tanto
maggiori quanto i triangoli sono grandi. Quello rappresentato
è un triangolo abbastanza piccolo, nel caso di triangoli
più grandi si possono ottenere benefici di oltre il
90%.
Adesso che abbiamo disegnato i nostri
primi triangoli è probabile che procedendo con il rendering,
un'altro triangolo vada a sovrapporsi a quelli già
disegnati. Come si procede ?
Ci sono vari casi. Se un blocco di tipo 1 si sovrappone ad
un blocco di tipo 2 si va a confrontare lo Z-value min del
nuovo triangolo con lo Z-value max precedentemente memorizzato
nel blocco, se il primo valore è maggiore del secondo
significa che il nuovo triangolo, in quel blocco, sta completamente
davanti al precedente contenuto. Siccome il nuovo blocco sarà
di tipo 1 avremo liquidato l'intero blocco con 1 load e 1
store di Z-value contro i 16 load - 16 store mediamente necessari
nel caso normale. Se questo test non da esito positivo è
necessatio procedere con la normale procedura punto per punto.
Analogamente se entrambi i blocchi
sono di tipo 1 si confrontano i valori Z-min Z-max per determinare
quale dei due triangoli è visibile. Solo in casi di
compenetrazione è necessario ricorrere allo Z-processing
di tutti e 16 i pixel.
Non vorrei procedere
descrivendovi tutte le casistiche, l'importante è che
abbiate capito il senso della tecnica sia per quanto riguarda
la riduzione della banda sia per lo Z-occlusion culling che
tenta di rimuovere le superfici nascoste.
La cosa interessante è che lo stesso procedimento
si può applicare CONTEMPORANEAMENTE a blocchi più
grandi, ad esempio di 16x16 pixel ossia macro-blocchi di 4x4
blocchi più piccoli. Il vantaggio è quello di
poter applicare i controlli prima ad i blocchi più
grandi e solo in base ai risultati scendere a controllare
i blocchi più piccoli. Se infatti guardate la figura
precedente, l'intera area potrebbe rappresentare un macro-blocco
da 16x16 pixel; se un triangolo coprisse completamente questa
area, potremmo eseguire il controllo di visibilità
in un solo ciclo e in molti casi "sistemare" ben
256 pixel in un solo ciclo di clock.
La tecnica che vi ho
appena illistrato è simile a quella attualmente usata
per l'Hyper-Z di Ati e lo Z-compression e Z-Occlusion Culling
di GF3. Il concetto nuovo è quello della struttura
gerarchica dei blocchi. Nel complesso si sopperisce sia alla
necessità di banda che alla necessità di eliminare
"un pò" di superfici nascoste. Dico un pò
perchè finchè l'ordine dei triangoli sarà
casuale questa tecnica da sola potrà ben poco contro
l'over-drawing.
Adesso introduciamo
un concetto nuovo, non presente nelle schede odierne: il Fast
Z Rendering. Supponiamo cioè che i chip vengano ottimizzati
per renderizzare il SOLO Z-buffer di una scena (si, avete
capito bene, senza texture, nè illuminazione nè
frame-buffer). I principi sopra esposti già permettono
una tale features ma si può fare di più...
Siccome nel rendering
del solo Z-buffer abbiamo tutta la banda a disposizione potremo
caricare gli Z-value a gruppi di 4 pixel (le righe dei nostri
blocchi). Siccome i calcoli del solo Z-buffer si riducono
a semplice interpolazione lineare e semplici confronti per
sapere blocco per blocco o alla peggio pixel per pixel quali
sono gli Z-value maggiori, potremmo dotare ogni pipeline di
rendering di una unità capace di processare 4 Z-value
per ciclo di clock.
In questo modo, e solo
nel calcolo del solo Z-buffer si potrebbero raggiungere delle
velocità spaventose indipendenetemente dal tipo di
blocco elaborato (in pratica si elaborerebbero blocchi di
tipo 1 in un solo ciclo e blocchi di tipo 2 in 1-4 cicli !).
Ma a cosa serve tutta
questa velocità nel calcolo del solo Z-buffer ? Calma
e sangue freddo che adesso arriva il bello...
Sovvertiamo le leggi del rendering
Supponiamo di avere
una scena complessa con sovrapposizione media pari a 4 (4
superfici sovrapposto mediamente di cui solo una visibile).
In questo caso, con le normali tecnich edi rendering dovrei
disegnare tutti i pixel anche delle superfici non visibili.
Con le tecniche in stile Hyper-Z recupererei solo il 20% dell'elaborazione
in eccesso.
Ma proviamo a fare
così: disegnamo con le tecniche di Fast Z-Rendering,
il solo Z-buffer dell'intera scena. Come precedentemente detto,
questo compito verrebbe effettuato a velocità molto
elevate, con un costo medio per pixel inferiore al ciclo di
clock. Adesso che abbiamo lo Z-buffer renderizzato, rirenderizziamo
l'intera scena con le texture. Voi direte: ma a cosa serve?
Semplice: quando andiamo a rirenderizzare la scena, per ogni
triangolo andremo a caricare i valori già presenti
nello Z-buffer (molti saranno in forma compressa) se i valori
del triangolo presente sono gli stessi contenuti in quei punti
nello Z-buffer precedentemente calcolato vuol dire che quel
punto è visibile altrimenti va scartato. Se vi ricordate
che noi abbiamo un sistema per fare confronti di Z su blocchi
gerarchici oppure nel peggiore dei casi su intere linee da
4 pixel avrete capito che in pochi cicli si riesce a scartare
senza renderizzare tutti i triangoli non visibili.
In pratica con un costo
complessivo di circa 2 cicli per pixel visibile si renderizza
l'intera scena anche se essa ha stratificazione media di 4,
5, 6+.
Tuttavia non è
finita qui, siccome per realizzare questa tecnica dobbiamo
calcolare due volte tutta la geometria (in realtà la
prima volta mancano i calcoli di illuminazione) noi non ci
accontentiamo e vogliamo migliorare ulterioemente questa tecnica.
Overview Z-Rendering
Il
Boundary Box (da ora BB) di un oggetto 3D è
tipicamente il parallelepipedo che lo contiene e ne stima
il volume nello spazio. Supponiamo adesso di suddividere un
oggetto nelle sue parti più significative. Ogni parte
avrà il suo BB. Es: stimare il volume di una forma
umana con un parallelepipedo non è molto conveniente,
dobbiamo invece usare un BB per le mani, uno per l'avanbraccio,
uno per la testa e così via, otterremo un questo modo
una stima migliore.
Differenziamo adesso
un BB+ che stima il volume per eccesso (potremmo dire dal
di fuori ossia nessuna parte dell'oggetto originale fuoriesce
dal BB) e un BB- che stima il volume per difetto (potremmo
dire dal di dentro ossia l'oggetto si appoggia sul BB).
I BB devono essere il più possibile vicini all'oggetto
che stimano e possono anche essere più complessi di
un parallelepiepedo l'importante è che ci sia un rapporto
abbastanza alto fra numero di triangoli dell'oggetto originale
e numero di triangoli del BB (diciamo ad esempio un rapporto
20:1). Il BB si particolari oggetti come il terreno o un muro
può coincidere con l'oggetto stesso.
Adesso che abbiamo
calcolato i BB+ e i BB- di tutta la nostra scena andiamo a
renderizzare il solo Z-Buffer della scena utilizzando il BB-
al posto degli oggetti originali. Questo procedimento impiegherà
un tempo molto limitato perchè:
1) i calcoli geometrici sono molto semplici (non ci sono le
luci) e vengono effettuati su una parte molto piccola della
geometria (ricordate il rapporto 1 a 20 ?)
2) la procedura di rendering è molto veloce perchè
essendo i BB semplici e costituiti da grandi triangoli è
molto semplice e veloce renderizzare lo Z-buffer con le tecniche
sopra esposte.
In pratica si può renderizzare questo Overview della
scena (ossia una versione semplififcata della scena) in un
20-esimo del tempo necessario a renderizzare l'intera scena.
Adesso riprocessiamo
l'intera scena questa vola procediamo in maniera diversa:
1) Per ogni oggetto
si proietta il suo BB+, se tutti i triangoli del BB+ non sono
visibili sulla scena calcolata con i BB- (per dire ciò
si utilizza una specie di Z-occlusion culling) allora siamo
sicuri che l'oggetto a cui si riferisce non è visubile
sulla scena e lo scartiamo.
2) Se il discorso di
cui sopra non è applicabile a tutti i triangoli del
BB+ ma qualche triangolo risulta potenzialmente visibile,
allora si procedere renderizzando il solo Z-buffer per l'oggetto
originale. I nuovi valori Z andranno a sovrascrivere quelli
vecchi calcolati sul BB- e lo Z-buffer si raffinerà
progressivamente.
3) Alla fine avrò
lo Z-buffer finale della scena completa.
Questo secondo passaggio
permette di evitare il calcolo geometrico sugli oggetti non
visibili (ed è grazie all'overview buffer che possiamo
dire se un oggetto nel suo complesso è visibile o no)
ed inoltre permette di ottimizzare rispetto ai casi precedenti
il calcolo dello Z-buffer finale.
Una volta ottenuto
lo Z-buffer finale si procede con il rendering completo dei
soli oggetti precedentemente marcati come visibili.
Il risultato finale
è il rendering della scena con un costo geometrico
molto inferiore al normale e un costo medio per pixel visibile
tra 1 e 2 anche con fattori di stratificazione molto elevati.
In soldoni questa tecnica può accelerare le performance
dei chip classici, in caso di geometria complessa e di alta
stratificazione, anche di un fattore 3-4 ... se vi sembra
poco.
Come potete vedere la grafica 3D in
tempo reale è ancora un territorio sconosciuto tutto
da esplorare. Le novità non mancheranno mai anche perchè
da circa 4 anni a questa parte il settore del 3D è
quello che ha sperimentato la maggiore crescita tecnologica.
Maggiore anche della famosa legge di Moore che 'profetizza'
per i processori un raddoppio di potenza ogni 18 mesi mentre
qui siamo su ritmi ben più serrati.
La novità più attesa,
dopo l'uscita dei nuovi modelli ATi e nVidia, è il
futuro Kyro III di cui attualmente si sa poco o nulla. Nel
frattempo torneremo sicuramente a parlare di grafica 3D ma
per lasciarvi in buona compagnia concludiamo questo articolo
con una riflessione:
Sono passati pochi anni da quando guardavamo
con ammirazione film come ToyStory chiedendoci quando mai
avremmo potuto avere tale splendore sui nostri monitor, adesso
che le schede attuali sono capaci di realizzare in tempo reale
animazioni paragonabili a quelle, al cinema gira per le sale
il film Final Fantasy, attuale stato dell'arte nel campo della
grafica di sintesi. Quello che allora ci viene spontaneo chiedersi
è: quando avremo questo ben di Dio sui nostri computer
?. C'e' solo una differenza rispetto a prima ... adesso sappiamo
la risposta: sarà molto, ma molto prima di quanto possiamo
immaginare...
