• Non ci sono risultati.

La tecnica del displacement mapping è stata esaminata nel par. 2.2.2 e corrisponde ad una alterazione della geometria dell'oggetto a tempo di rendering, sulla base di un'immagine monocromatica detta height map (o displacement map). Nella nuova

CAPITOLO 4. REALIZZAZIONE DELLE TECNICHE PRESENTATE 119 VR3Lib tale tecnica è supportata nativamente e la geometria viene modicata nello stadio di geometry processing.

Adarsi ad un geometry shader è la scelta più ovvia usando la versione 3.3 del sistema graco OpenGL. Si opera dunque eettuando un tassellamento di ogni triangolo all'interno dello stadio di geometry processing. Il tassellamento può essere di vario tipo, nel nostro caso la procedura è molto semplice e prende il nome di tas- sellamento a suddivisione lineare: si utilizza un parametro intero T F (Tessellation Factor) che indica in quante parti vanno divisi i tre spigoli di ogni triangolo e si uni- scono i punti generati con linee parallele agli spigoli del triangolo (generando dunque nuovi triangoli). Da un punto di vista pratico da ogni triangolo si costruiscono T F diverse triangle strip15(fasce di triangoli) parallele ad uno degli spigoli. A partire da

un singolo triangolo si producono T F2 triangoli nello stadio di geometry processing;

vedi g. 4.1 (valida per T F = 3, 9 triangoli risultanti dal tassellamento).

Figura 4.1: Tassellamento a suddivisione lineare con T F = 3

I vertici risultanti saranno i 3 vertici del triangolo iniziale più diversi vertici generati grazie all'algoritmo di tassellamento, questi vengono spostati lungo una normale calcolata localmente di una quantità data da:

BI + SC · height

15 Una triangle strip è un tipo di primitiva OpenGL che specica un certo numero di triangoli organizzati in una fascia fornendo i primi tre vertici (per il primo triangolo) e poi un singolo vertice addizionale per ogni triangolo aggiunto alla fascia.

dove BI (BIas) e SC (SCale) sono ulteriori parametri che dipendono dall'oggetto e indicano come è stata calcolata la height map (e dunque come alterare la posizione dei vertici in accordo alla stessa), height invece è il valore letto dalla height map (un numero reale compreso tra 0 ed 1 che indica l'intensità dell'immagine in un certo punto).

Ogni striscia viene generata separatamente e dunque alcuni vertici emessi (ap- partenenti a diverse triangle strips) coincideranno. Il numero di vertici emessi in totale dal geometry shader (compresi quelli coincidenti) è dato da:

T F · (T F + 2)

Quest'ultima informazione è importante perchè lo stadio di geometry proces- sing ha un limite massimo al numero totale di componenti (variabili varying) emes- se per tutta la geometria generata. Sapendo allora quante componenti vengono emesse per ogni vertice possiamo calcolare il massimo valore per T F . Quando si utilizza un T F superiore al valore massimo per un particolare oggetto non si veri- ca nessun errore, ma la geometria emessa è incompleta e l'oggetto presenterà dei poligoni mancanti una volta visualizzato a schermo. Il numero massimo di com- ponenti totali di cui stiamo parlando dipende dall'implementazione di OpenGL: possiamo ricavarne il valore Nmax richiedendolo al server GL tramite l'identica-

tore GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS. Esistono anche altri vincoli da rispettare nel geometry shading, ma questo risulta quello più stretto per quanto riguarda la geometria emessa.

Il numero di componenti emesse per vertice cambia a seconda del tipo di oggetto. Chiamando questo valore n, la seguente relazione risulta valida (per come sono stati scritti gli shader e realizzate le altre tecniche presentate):

n = (

9 if not dif f use texture mapped 11 if dif f use texture mapped

per cui il massimo valore di T F per un particolare oggetto sarà dato da:

T Fmax = max T F : n · T F · (T F + 2) ≤ Nmax

Quando si fa displacement mapping i vertici vengono spostati lungo le normali calcolate a partire dalla geometria. Questo signica che se l'oggetto è solido e vi sono diversi smoothing groups l'eetto ottenuto potrebbe non essere quello desiderato in quanto i poligoni generati potrebbero interesecarsi dopo l'applicazione della height

CAPITOLO 4. REALIZZAZIONE DELLE TECNICHE PRESENTATE 121 map. In generale quando si fa displacement mapping conviene inserire tutti i trian- goli in un unico smoothing group allo scopo di evitare questi problemi. Le normali di cui sopra, utilizzate per eettuare il displacement mapping, non vengono poi più considerate nello shading: quando si fa displacement mapping è infatti obbligatorio fornire anche una normal map contenente le normali nali da usare per i calcoli di illuminazione oppure una light map che specichi direttamente come l'oggetto con la geometria nale deve essere illuminato.

Il geometry shader dovrà quindi sempre ricevere le normali (anche nel caso di light mapping), e dovrà inoltre ricevere le coordinate per l'accesso alla height map. Quan- do si fa displacement mapping il vertex shader di base assume quindi la seguente forma:

# version 330 core // GLSL version 3.30 , core OpenGL profile in vec3 vertex ; // vertex position in obj coordinates in vec3 normal ; // normal in obj coordinates

in vec2 diffCoord ; // diffuse texture coordinates in vec2 lightCoord ; // light map coordinates in vec2 normalCoord ; // normal map coordinates in vec2 dispCoord ; // displacement map coordinates smooth out vec3 verPositionObj ;

smooth out vec3 verNormalObj ; smooth out vec2 verDiffTexCoord ;

smooth out vec2 verLightNormalMapCoord ; smooth out vec2 verDispMapCoord ;

void main ( void ) {

verPositionObj = vertex ; verNormalObj = normal ; verDispMapCoord = dispCoord ; ...

}

dove notiamo che:

• La posizione post-proiezione dei vertici (gl_Position) non viene impostata in quanto questa operazione avviene all'interno del geometry shader (il dato gl_Position serve fondamentalmente prima di arrivare alla rasterizzazione dei poligoni e dunque non è necessario ssarne il valore prima di arrivare allo stadio di geometry processing). Dato che non si deve scrivere su gl_Position, non è più necessario ricevere la matrice di trasformazione transformMatrix come variabile uniform.

• La porzione mancante della funzione main() viene utilizzata in modo analogo a quanto descritto nelle sezioni precedenti in dipendenza dal particolare tipo di shading che si vuole eettuare per l'oggetto.

Adesso esaminiamo la struttura del geometry shader che si occupa di eettuare il tassellamento e di alterare la geometria. Assumiamo di non avere una diuse texture e di calcolare i parametri di illuminazione nello stadio di fragment shading sulla base delle normali memorizzate in una normal map; in tal caso al vertex shader di cui sopra bisognerà aggiungere:

...

void main ( void ) { ...

verLightNormalMapCoord = normalCoord ; }

Il geometry shader avrà invece la seguente struttura:

# version 330 core // GLSL version 3.30 , core OpenGL profile // triangles are received

layout ( triangles ) in;

// triangle strips are produced

layout ( triangle_strip , max_vertices = MAX_OUTPUT_VERTICES ) out ; uniform mat4 transformMatrix ; // proj * view * model matrix

uniform int dispTF ; // tessellation factor uniform float dispBI ; // bias

uniform float dispSC ; // scale

uniform sampler2D dispSampler ; // height map sampler

uniform mat4 dispMatrix ; // displacement map coordinates transformation smooth in vec3 verPositionObj [3]; // vertex positions

smooth in vec3 verNormalObj [3]; // vertex normals ( geometry - based ) smooth in vec2 verLightNormalMapCoord [3]; // normal map coordinates smooth in vec2 verDispMapCoord [3]; // displacement map coordinates smooth out vec2 lightNormalMapCoord ; // normal map coordinates smooth out vec3 fragPositionObj ; // resulting vertex positions void main ( void ) {

int i, j, k; float disp ; vec2 dispCoord ;

CAPITOLO 4. REALIZZAZIONE DELLE TECNICHE PRESENTATE 123

vec3 norm ;

vec3 v01 = ( verPositionObj [1] - verPositionObj [0]) / dispTF ; vec3 v12 = ( verPositionObj [2] - verPositionObj [1]) / dispTF ; vec2 delta_uv01_map =

( verLightNormalMapCoord [1] - verLightNormalMapCoord [0]) / dispTF ; vec2 delta_uv12_map =

( verLightNormalMapCoord [2] - verLightNormalMapCoord [1]) / dispTF ; vec2 delta_uv01_disp = ( verDispMapCoord [1] - verDispMapCoord [0]) / dispTF ; vec2 delta_uv12_disp = ( verDispMapCoord [2] - verDispMapCoord [1]) / dispTF ; vec3 delta_norm01 = ( verNormalObj [1] - verNormalObj [0]) / dispTF ;

vec3 delta_norm12 = ( verNormalObj [2] - verNormalObj [1]) / dispTF ; vec3 bv0 ;

vec3 bv1 ;

for (i = 0; i <= dispTF -1; i ++) { // for each strip bv0 = verPositionObj [0] + v01 *i;

bv1 = verPositionObj [0] + v01 *(i +1);

// emit the first vertex of the first triangle lightNormalMapCoord = verLightNormalMapCoord [0] +

delta_uv01_map *(i +1) + delta_uv12_map *(i +1); dispCoord = verDispMapCoord [0] +

delta_uv01_disp *(i +1) + delta_uv12_disp *(i +1); dispCoord = ( dispMatrix * vec4 ( dispCoord ,0.0 ,1.0)). st; norm = normalize ( verNormalObj [0] +

delta_norm01 *(i +1) + delta_norm12 *(i +1)); disp = texture ( dispSampler , dispCoord ).r;

fragPositionObj = bv1 + v12 *(i +1)+ norm *( dispBI + disp * dispSC ); gl_Position = transformMatrix * vec4 ( fragPositionObj ,1.0); EmitVertex ();

// emit the second vertex of the first triangle lightNormalMapCoord = verLightNormalMapCoord [0] +

delta_uv01_map *i + delta_uv12_map *i; dispCoord = verDispMapCoord [0] +

delta_uv01_disp *i + delta_uv12_disp *i;

dispCoord = ( dispMatrix * vec4 ( dispCoord ,0.0 ,1.0)). st; norm = normalize ( verNormalObj [0] +

delta_norm01 *i + delta_norm12 *i); disp = texture ( dispSampler , dispCoord ).r;

fragPositionObj = bv0 + v12 *i+ norm *( dispBI + disp * dispSC ); gl_Position = transformMatrix * vec4 ( fragPositionObj ,1.0); EmitVertex ();

// emit the third vertex of the first triangle lightNormalMapCoord = verLightNormalMapCoord [0] +

delta_uv01_map *(i +1) + delta_uv12_map *i; dispCoord = verDispMapCoord [0] +

delta_uv01_disp *(i +1) + delta_uv12_disp *i; dispCoord = ( dispMatrix * vec4 ( dispCoord ,0.0 ,1.0)). st; norm = normalize ( verNormalObj [0] +

delta_norm01 *(i +1) + delta_norm12 *i); disp = texture ( dispSampler , dispCoord ).r;

fragPositionObj = bv1 + v12 *i+ norm *( dispBI + disp * dispSC ); gl_Position = transformMatrix * vec4 ( fragPositionObj ,1.0); EmitVertex ();

// emit more vertices building the strip for (j = 2*i; j >= 1 ; j --) {

if(j%2 != 0) { k = (j -1)/2;

lightNormalMapCoord = verLightNormalMapCoord [0] + delta_uv01_map *(i +1) + delta_uv12_map *k; dispCoord = verDispMapCoord [0] +

delta_uv01_disp *(i +1) + delta_uv12_disp *k; dispCoord = ( dispMatrix * vec4 ( dispCoord ,0.0 ,1.0)). st; norm = normalize ( verNormalObj [0] +

delta_norm01 *(i +1) + delta_norm12 *k); disp = texture ( dispSampler , dispCoord ).r;

fragPositionObj = bv1 + v12 *k+ norm *( dispBI + disp * dispSC ); gl_Position = transformMatrix * vec4 ( fragPositionObj ,1.0); EmitVertex (); } else { k = j/2 -1; lightNormalMapCoord = verLightNormalMapCoord [0] + delta_uv01_map *i + delta_uv12_map *k; dispCoord = verDispMapCoord [0] + delta_uv01_disp *i + delta_uv12_disp *k;

dispCoord = ( dispMatrix * vec4 ( dispCoord ,0.0 ,1.0)). st; norm = normalize ( verNormalObj [0] +

delta_norm01 *i + delta_norm12 *k); disp = texture ( dispSampler , dispCoord ).r;

fragPositionObj = bv0 + v12 *k+ norm *( dispBI + disp * dispSC ); gl_Position = transformMatrix * vec4 ( fragPositionObj ,1.0); EmitVertex ();

} }

EndPrimitive (); // finalize the triangle strip }

}

dove notiamo che:

• Le funzioni built-in nel linguaggio GLSL EmitVertex() e EndPrimitive() hanno lo scopo, rispettivamente, di emettere un vertice per la primitiva cor- rente e di nalizzare una primitiva (per iniziare ad emetterne una nuova). Nel nostro caso le primitive emesse sono triangle strips.

• La costante MAX_OUTPUT_VERTICES identica il numero massimo di vertici che possono essere emessi dal geometry shader (dipende dall'implementazione di OpenGL e dal particolare tipo di shading che stiamo facendo). Nel nostro caso non abbiamo diuse texture mapping e dunque questa costante avrà il valore:

CAPITOLO 4. REALIZZAZIONE DELLE TECNICHE PRESENTATE 125

MAX_OUTPUT_VERTICES = Nmax n



= Nmax 9



in accordo con quanto detto sopra.

Nel par. 2.2.2 abbiamo anche discusso di un'ottimizzazione all'algoritmo che prevede di eettuare il tassellamento in modo adattivo sulla base del punto di vista dell'osservatore. Purtroppo, lavorando col tassellamento a suddivisione lineare visto sopra, un'operazione del genere comporta che l'utente è spesso in grado di percepire le modiche alla geometria sulla base del suo punto di vista. Per realizzare in modo ecace l'ottimizzazione descritta nel par. 2.2.2 bisogna usare algoritmi di tassellamento più complessi, ma non entriamo nei dettagli accontentandoci di questa soluzione indipendente dal punto di vista.

Visualizzando oggetti con la tecnica del displacement mapping realizzata come sopra può accadere che siano visibili delle cuciture sui punti di contatto tra i trian- goli della mesh originale. Questo deriva dal fatto che i valori prelevati dalla height map per un dato vertice della mesh originale possono essere molteplici e in diverse posizioni della stessa, inoltre si esegue un ltraggio lineare sulla texture. Se i va- lori prelevati non sono sempre esattamente gli stessi per un dato vertice, avremo un'alterazione diversa delle posizione di questo vertice per i diversi poligoni di cui fa parte e di conseguenza la creazione di cuciture. Naturalmente questo fenomeno può essere evitato con una particolare costruzione della height map e delle coordinate per l'accesso ad essa (ad opera del programma usato per modellare l'oggetto), ma non entriamo nei dettagli.