Nella seconda parte avevano concluso la serie delle “trasformazioni” confinando gli oggetti all’interno di un “CUBO” i cui valori X, Y, Z possono variare rispettivamente tra (-1; 1), (-1, 1) e (0; 1) per le D3D e tra (-1; 1), per tutte e tre le coordinate, per le OpenGL. In questo nuovo spazio che, in letteratura, viene definito, talvolta, CANONICAL VIEW VOLUME (CVV), non esiste una “visione prospettica” ma l’illusione della prospettiva è data dalla deformazione degli oggetti in esso contenuti.
Per i non addetti ai lavori, è doveroso soffermarsi un attimo su questo apparente passo indietro rispetto al VIEW SPACE ed al CLIP SPACE, in cui esisteva una vera e propria prospettiva. La motivazione di questa trasformazione è da ricercarsi nel fatto che l’immagine che deve essere rappresentata ha 2 DIMENSIONI soltanto, forma rettangolare e deve creare l’illusione di svilupparsi lungo una inesistente terza dimensione.
In questo nuovo ambiente, detto SCREEN SPACE, avvengono due cose importanti:
– si passa dalle 4 dimensioni dello spazio omogeneo (x, y, z, w) a due sole dimensioni ((x, y) attraverso una normalizzazione rispetto a w e ad una operazione che somiglia vagamente a quanto avviene durante una TAC, che analizzeremo più in dettaglio tra breve)
– lo spazio tridimensionale delle figure geometriche è sostituito da uno spazio bidimensionale in cui si opera in termini di pixel. Per ottenere ciò, si “taglia a fette” il CVV, con una serie di piani ideali, perpendicolari all’asse Z. Ogni piano sarà individuato da m*n punti (x, y) e ad ogni punto corrisponderà anche un valore z che è quello comune a tutto il piano)..
Ogni singolo piano, dunque, conterrà una sezione di un oggetto presente sulla scena composta da una serie di poligoni e, per ciascun punto o pixel di questa sezione, si avrà un valore di x e uno di y determinati dalla sua posizione sul piano ed uno di z che è lo stesso del piano su cui giace la sezione.
In questo modo, per ogni punto di un determinato poligono componente il frame, si avranno una coppia di coordinate (x, y) che ne individuano la posizione nello SCREEN SPACE ed un valore z che ne fornisce la distanza dall’osservatore.
Ovviamente la procedura sarà ripetuta per tutti i poligoni che compongono il frame. In questo modo è possibile confrontare le reciproche posizioni di questi poligoni rispetto all’osservatore e la loro eventuale copertura ad opera di altri poligoni presenti nel frame.
Siamo giunti alla fine delle elaborazioni di tipo geomentrico, allo stadio che nello schema di pipeline classica riportato in basso, è indicato come screen mapping
Siamo, dunque, pronti per le operazioni di rendering………….. O forse no. Si sarebbe pronti se non ci fosse da risolvere un piccolo problema: le operazioni di clipping viste nei precedenti articoli, aiutavano a rimuovere le superfici che si trovano fuori dalla visuale dell’osservatore e quelle nascoste dietro altre superfici dello stesso solido; ci sono, però, molte altre superfici nascoste che si rischia di renderizzare con notevole spreco di risorse: tutte quelle nascoste dietro superfici facenti parte di altri oggetti presenti nell’immagine.
Ci sono vari algoritmi di Hidden Surface Removal (HSR) i più comuni dei quali fanno uso di uno o più z-buffer, ovvero dei buffer che contengono tabelle recanti i valori Z individuati con il procedimento sopra indicato.
Una scansione front to back, se si parte dal piano più prossimo all’osservatore, o back to front se si parte da quello più lontano, permette di ricavare i valori z da immagazzinare nello z-buffer. Si confronta, a parità di valori x e y (quindi per poligoni che si intersecano sullo stesso pixel), i corrispondenti valori z; se, ad esempio, ho scelto di marcare con valori più grandi i piani più distanti dall’osservatore, allora, per gli stessi valori di X e Y (ad esempio x=260 e y=320), se ho un valore Z=9 ed un valore z=5 il primo sarà rimosso e non verrà renderizzano.
Quest’ultima affermazione fa presupporre, correttamente, che un algoritmo di HSR sia preceduto da una analisi preventiva dei valori contenuti nello z-buffer (quello che si definisce in gergo un early z test). In effetti è così e anche questo test può essere eseguito con differenti modalità.
Alcune delle più comuni prevedono, ad esempio, un early z test a più livelli, in modo da eliminare gran parte delle superfici nascoste senza dover arrivare ad analizzare la situazione alla display resolution.
In pratica, si prende l’immagine 2D nelle sue dimensioni reali (la display resolution appunto), la si divide in tessere (ad esempio di 4×4 o 8×8 pixel) e, per ciascuna tessera, si sceglie un valore PIVOT (il maggiore se si è optato per un algoritmo che attribuisca valori crescenti alla Z ma n mano che ci si allontana dall’osservatore). In tal modo, si effettua una sorta di downsampling e si costruisce una matrice di valori pivot, ciascuno rappresentativo di un preciso gruppo di pixel alla display resolution.
Quando si incontra una nuova tessera si confrontano i suoi valori z più piccoli con il valore pivot contenuto nella matrice, ottenuto dai pixel che presentano quegli stessi valori di x e y; se i nuovi valori sono maggiori del valore pivot, la tessera è immediatamente scartata per intero; se qualcuno di questi è inferiore al valore pivot, allora si passa all’analisi del livello superiore (ad esempio una tessera 2×2, come in figura); qualora perdurasse l’incertezza su alcuni valori, si passa, infine, all’analisi a livello di display resolution. In figura, un esempio di HIERARCHICAL Z BUFFER A 3 LIVELLI , sul modello di quello utilizzato dall’R3x0 dove il numero 8 rappresenta il valore PIVOT.
Questo procedimento ha vantaggi e svantaggi: il vantaggio principale è che molti pixel sono scartati già al primo livello e questo comporta un notevole risparmio di banda passante e di calcoli. Lo svantaggio principale è che questo procedimento, a causa delle ripetute iterazioni, potrebbe indurre degli stalli nella pipeline. In effetti, questo problema ha fatto si che fino all’adozione delle DX9 non si arrivava a fare early z test alla display resolution e questo aumentava il numero di poligoni che venivano renderizzati per errore. Con l’adozione di shader sempre più lunghi e l’aumentare delle potenze di calcolo delle GPU questo inconveniente ha avuto un impatto sempre più trascurabile sulle prestazioni.
Un altro inconveniente è dovuto alla necessità di avere più buffer e, soprattutto, buffer molto capienti all’interno del chip (soprattutto se si utilizza la display resolution). Questo problema è stato definitivamente risolto grazie all’adozione di buffer sempre più capienti on chip ed alla possibilità di salvare i valori relativi allo z-buffer, qualora fosse necessario, all’interno del frame buffer.
In questa breve disamina ho volutamente trascurato il fatto che uno hierarchical z-test a più livelli fornisce buoni risultati a livello di “coerenza spaziale”, ovvero su immagini statiche ma non risolve il problema della “coerenza temporale”, ovvero non dà informazioni circa la possibilità di copertura di poligoni che qualche frame prima erano visibili, ad esempio.
Per risolvere questo problema si affianca ad un algoritmo di tipo hierarchical che fa uso dello z-buffer, altri algoritmi volti a “decidere” quali poligoni risulteranno coperti e quali visibili nei frame successivi.
Questa lacuna è dovuta al fatto che lo scopo di questi articoli non è quello di scrivere un tutorial sulla creazione di un’immagine 3D ma solo di introdurne i concetti base, in maniera da rendere più comprensibile, per i non addetti e per chi conosce poco o per nulla i chip grafici, uno schema a blocchi di un chip grafico
o una pipeline di rendering con i relativi blocchi e le operazioni in essi svolte.
L’esposizione, seppure poco formale e rigorosa di questi concetti, può aiutare a comprendere, altri concetti, probabilmente di maggior interesse, come, ad esempio, cosa è il MSAA box filter, come viene applicato e perché è incompatibile, ad esempio, in DX9, con algoritmi di deferred rendering.