Un'applicazione interessante degli shader riguarda l'estrazione del contorno (silohuette) da una mesh di poligoni. Intuitivamente, il contorno è definito come l'insieme degli spigoli le cui facce adiacenti sono una rivolta verso il punto di vista e l'altra rivolta nel verso opposto.
Lavorando nel volume canonico, dopo le trasformazioni di vista e di proiezione, la regola si semplifica. Una faccia è rivolta lontano dall'osservatore se è rivolta nella direzione in cui la profondità aumenta, cioè la componente z della normale è positiva. Al contrario, se la componente z è negativa la faccia è rivolta verso l'osservatore.
Ciò permette di affermare che se la normale di una faccia ha segno diverso da quella della faccia adiacente, lo spigolo tra le due è parte della silohuette.
In questa immagine è spiegato il ragionamento. Lo spigolo tra le due facce a sinistra non è parte della silohuette, perché le proiezioni lungo z (in nero) delle normali (in rosso) hanno lo stesso segno. Al contrario, lo spigolo a destra è parte della silohuette.
Il vertex shader si limita a calcolare le coordinate nel volume canonico e a passare avanti il colore.
#version 120 varying vec3 position; void main() { vec4 newpos = gl_ModelViewProjectionMatrix * gl_Vertex; position = newpos.xyz / newpos.w; gl_FrontColor = gl_Color; gl_BackColor = vec4(0.0,0.0,0.0,0.0); }
Il fragment shader è ancora più semplice, perché deve solo applicare il colore. Siccome si stanno disegnando delle linee, non c'è nessuna illuminazione.
#version 120 void main() { gl_FragColor = gl_Color; }
L'elaborazione è eseguita tutta nel geometry shader. Esso riceve in ingresso un triangolo con informazioni di adiacenza (GL_TRIANGLES_ADJACENCY_EXT, vedere sezione sul Geometry Shader) e produce in uscita i lati del triangolo (con GL_LINE_STRIP) che soddisfano la condizione di appartenere alla silohuette.
Oltre a quanto detto in precedenza, è stata applicata un'ottimizzazione. Per evitare che ciascuno spigolo della silohuette sia elaborato due volte, uno per una faccia e uno per l'altra, il geometry shader si interrompe subito se la faccia non è orientata verso l'osservatore. Ciò ha effetto anche se tutti gli spigoli della faccia non fanno parte della silohuette, ma questo non ha importanza, perché non sarebbe stata visualizzata comunque.
#version 120 #extension GL_EXT_geometry_shader4: enable varying in vec3 position[6]; // FaceNormal.txt vec3 ComputeNormal(in vec3 v1,in vec3 v2,in vec3 v3); // emette il vertice che ha indice "index" void Emit(in int index) { gl_FrontColor = gl_FrontColorIn[index]; gl_BackColor = gl_BackColorIn[index]; gl_Position = vec4(position[index],1.0); EmitVertex(); } void main() { // calcolo della z della normale alla faccia float fz = ComputeNormal(position[2],position[0],position[4]).z; // ottimizzazione if (fz < 0.0) return; // calcolo della z delle normali alle facce adiacenti float z021 = ComputeNormal(position[0],position[2],position[1]).z; float z432 = ComputeNormal(position[4],position[3],position[2]).z; float z540 = ComputeNormal(position[5],position[4],position[0]).z; // sappiamo già, per l'ottimizzazione, che la z della normale alla faccia corrente è positiva // basta che quella adiacente sia negativa if (z021 < 0.0) { Emit(2); Emit(0); EndPrimitive(); } if (z432 < 0.0) { Emit(2); Emit(4); EndPrimitive(); } if (z540 < 0.0) { Emit(0); Emit(4); EndPrimitive(); } }
#define SHADER_FOLDER "shaders/silohuette/" #define USE_VERTEX_SHADER true #define USE_FRAGMENT_SHADER true #define USE_GEOMETRY_SHADER true #define USE_NORMAL_SHADER true #define USE_VERTEX_LIGHT_SHADER false #define USE_FRAGMENT_LIGHT_SHADER false #define USE_TESSEVAL_LIGHT_SHADER false #define USE_TESSELLATION_SHADERS false #define GEOMETRY_INPUT_TYPE GL_TRIANGLES_ADJACENCY_EXT #define GEOMETRY_OUTPUT_TYPE GL_LINE_STRIP #define GEOMETRY_MAX_VERTICES 6 // massimo 6 vertici (tre lati) prodotti per triangolo #define ENABLE_BACK_FACE_CULLING true #define LOAD_NORMALS false // le normali sono calcolate internamente #define TIMER_MS 0
Risultato:
Come verifica, si può provare a visualizzare anche il poligono all'interno (dopo aver riattivato il caricamento delle normali):
#define LOAD_NORMALS true ... #define DRAW_WITHOUT_SHADER true
Copyright © 2011 Riccardo Monica
Eccetto ove espressamente indicato altrimenti, il contenuto di questa pagina è disponibile secondo la licenza Creative Commons Attribuzione - Condividi allo stesso modo 3.0.