I geometry shader sono diventati parte dello standard OpenGL a partire da OpenGL 3.2 (con GLSL 3.30), ma possono essere usati già a partire da OpenGL 2.1 (GLSL 1.20) nelle schede video che supportano l'estensione GL_EXT_geometry_shader4 o GL_ARB_geometry_shader4.
Un'istanza di un geometry shader riceve in ingresso una primitiva e produce in uscita un qualunque numero di primitive, che possono essere di tipo diverso di quelle ricevute in ingresso. Tuttavia, un program che contiene geometry shader deve essere specializzato per ricevere in ingresso un unico tipo di primitiva e tutte le primitive prodotte devono essere dello stesso tipo. Inoltre, più istanze non possono mai collaborare a produrre la stessa primitiva.
L'installazione di un geometry shader non sostituisce nessuna parte della Fixed Function Pipeline.
Un geometry shader può svolgere tutte le funzioni di un vertex shader, più molte altre. Ciononostante, in genere il geometry shader è meno efficiente per quelle funzioni, e comunque è obbligatorio inserire almeno un vertex shader in ogni program che contiene un geometry shader.
È necessario specificare il tipo di primitive di input e di output di un geometry shader, quando esso viene creato. In versioni di GLSL più recenti, ciò avviene attraverso opportune direttive del compilatore all'interno del codice dello shader. Nella versione 1.20, invece, il tipo deve essere impostato appena prima del linking del program che contiene il geometry shader, tramite alcune chiamate a funzioni OpenGL.
Per questo, esiste la funzione:
void glProgramParameteriEXT(GLuint program, GLenum pname, GLint param);
program è l'id del program che si vuole modificare. pname è il nome del parametro da modificare e può essere:
Le primitive di output di un geometry shader possono essere soltanto di tre tipi:
GL_POINTS punto GL_LINE_STRIP linea spezzata GL_TRIANGLE_STRIP triangoli ciascuno con un lato in comune al precedente
Le possibili primitive di input sono le seguenti, invece:
GL_POINTS punto GL_LINES segmento GL_TRIANGLES triangolo GL_LINES_ADJACENCY_EXT segmento con informazioni di adiacenza GL_TRIANGLES_ADJACENCY_EXT triangolo con informazioni di adiacenza
Per prima cosa, si nota che primitive diverse da punti, linee e triangoli (come quadrilateri e poligoni) non sono supportate dai geometry shader, e devono essere scomposte prima di poter essere inviate alla pipeline che usa geometry shader.
Sono comparsi invece altri due tipi di primitiva, GL_LINES_ADJACENCY_EXT e GL_TRIANGLES_ADJACENCY_EXT. Questi tipi di primitiva possono essere usati per definire primitive come quelli tradizionali (ad esempio possono diventare il parametro di glBegin), ma hanno significato solo se è in uso un geometry shader.
Questi tipi servono per estendere GL_LINES e GL_TRIANGLES ed includere informazioni sulle primitive adiacenti a quella corrente.
Con GL_LINES_ADJACENCY_EXT, un segmento viene definito ogni 4 vertici. Due vertici servono per il segmento e gli altri due per ciascuno dei due segmenti che hanno un vertice in comune con esso. L'ordine di inserimento dei vertici può essere il seguente, anche se si vedrà poi che non è particolarmente rilevante:
In rosso è segnato il segmento, mentre in verde i segmenti adiacenti. Si nota che questo definisce un solo segmento, quello dal punto 2 al punto 3, e OpenGL non assume o verifica l'effettiva esistenza di segmenti adiacenti, né associa questo segmento agli altri.
Il segmento successivo è trattato come una primitiva completamente separata da questa. Perciò, allo stesso modo, anche il segmento adiacente deve essere definito con 4 vertici, di cui 3 sono in comune con questo (ad esempio, il 2, il 3, il 4 e il 5).
Con GL_TRIANGLES_ADJACENCY_EXT, ciascun triangolo è definito con 6 vertici. Tre sono i vertici del triangolo e tre sono i vertici che i triangoli adiacenti non hanno in comune con il triangolo corrente. Di nuovo, questo può essere l'ordine di inserimento dei vertici:
I vertici 1, 3 e 5 definiscono il triangolo, mentre i vertici 2, 4 e 6 i triangoli adiacenti.
Una ulteriore impostazione per i geometry shader riguarda il numero massimo di vertici che una singola istanza può produrre. La GPU deve allocare un buffer per questi vertici, perciò deve sapere in anticipo quanti sono.
void glProgramParameteriEXT(GLuint program, GLenum pname, GLint param);
Come prima, program è l'id del program che si vuole modificare. pname in questo caso deve essere uguale a GL_GEOMETRY_VERTICES_OUT_EXT. param è il numero massimo di vertici che possono essere prodotti nel caso peggiore.
Se l'istanza del geometry shader produce linee o triangoli, il numero di vertici è uguale al numero di vertici totali usati per comporre quelle primitive.
Esiste anche un limite massimo di vertici che dipende dall'architettura, per il geometry shader. Questo numero è di solito molto elevato (centinaia), ma è bene interrogare il driver della scheda video per conoscerlo. Per fare ciò si usa la funzione:
void glGetIntegerv(GLenum pname, GLint * param);
con pname uguale a GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT. param è il puntatore all'intero in cui deve essere scritto il numero.
In ogni caso, è sconsigliato perché molto inefficiente emettere più di una ventina di vertici dal geometry shader.
Un geometry shader riceve in ingresso una primitiva i cui vertici sono già stati elaborati dal vertex shader. Esso riceve in ingresso un array di vertici, ciascuno con le proprietà esattamente come sono state prodotte dal vertex shader. Questo include tutte le proprietà predefinite:
vec4 gl_FrontColorIn[]; // da gl_FrontColor vec4 gl_BackColorIn[]; // da gl_BackColor vec4 gl_PositionIn[]; // da gl_Position vec4 gl_TexCoordIn[][]; // da gl_TexCoord[]
Il numero di elementi di questi array è il numero di vertici della primitiva (3, per un triangolo). Essi sono ordinati secondo la definizione originale del programma OpenGL. Si nota che la variabile gl_TexCoord, che era già un array, è diventata un array di array.
Inoltre, il geometry shader riceve tutte le variabili varying provenienti dal vertex shader. Come già accennato nella sezione sulla comunicazione, queste variabili non possono più scavalcare il geometry shader per raggiungere il fragment shader.
Il geometry shader deve dichiarare le variabili varying in ingresso come variabili globali, aggiungendo la parola chiave in.
varying in vec3 varyvar[3];
Tutte le variabili di ingresso sono considerate costanti all'interno del geometry shader.
Anche se ciascun vertex shader ha prodotto un solo valore per questa variabile, il geometry shader riceve un vettore di lunghezza pari al numero di vertici della primitiva di input. Il vettore è inizializzato, in ordine, con ciascuno dei valori della variabile prodotti dalle varie istanze dei vertex shader.
Risulta a questo punto evidente perché la sequenza con cui i vertici sono inviati alla pipeline, all'interno della stessa primitiva, non può essere definita a priori. Il geometry shader deve produrre i vertici nell'ordine corretto, ma può interpretare l'input in qualunque ordine, basta che ci sia la stessa convenzione di ordinamento tra programma OpenGL e geometry shader.
In ultima analisi, il tipo di primitiva di input definisce solo il numero di vertici che sono raggruppati insieme per essere elaborati dallo stesso geometry shader:
GL_POINTS un vertice GL_LINES due vertici GL_TRIANGLES tre vertici GL_LINES_ADJACENCY_EXT quattro vertici GL_TRIANGLES_ADJACENCY_EXT sei vertici
Anche se il geometry shader dovrebbe produrre una primitiva, tale primitiva viene costruita un vertice alla volta. Sono predefinite le solite variabili globali per un singolo vertice:
vec4 gl_FrontColor; vec4 gl_BackColor; vec4 gl_Position; vec4 gl_TexCoord[];
Il geometry shader può definire anche variabili varying personalizzate, ad uso del successivo fragment shader. Per distinguerle da quelle provenienti dal vertex shader, si aggiunge la parola chiave out:
varying out vec3 gvaryvar;
Tutte queste variabili possono essere lette e scritte ovunque nel codice del geometry shader. In qualunque momento può essere chiamata la funzione predefinita:
void EmitVertex();
Questa funzione legge lo stato attuale di tutte le variabili di uscita e produce un vertice con quei valori. Dopodiché, le variabili possono subito essere modificate per l'emissione del vertice successivo.
Un'altra funzione:
void EndPrimitive();
conclude la primitiva corrente. Se lo shader sta producendo punti, questo non ha nessun effetto. Se sta producendo una linea spezzata, termina la linea corrente e ne comincia una nuova a partire dal prossimo vertice emesso. Se sta producendo una triangle strip, ne inizia un'altra.
Se EndPrimitive non viene chiamata al termine dello shader, la primitiva corrente viene conclusa automaticamente, se sono stati emessi abbastanza vertici, o scartata se non sono sufficienti.
Per fare in modo che certo valore scavalchi il geometry shader, si definisce una variabile di input dal vertex shader e poi si copia sempre il valore in una variabile di output verso il fragment shader. Ovviamente, la variabile di uscita deve avere un nome diverso di quella di ingresso, perciò è necessaria una piccola modifica al vertex shader o al fragment shader.
Questo è un esempio di un geometry shader specializzato per triangoli che si limita a passare avanti i valori provenienti dal vertex shader. È stata aggiunta una direttiva del compilatore per abilitare l'estensione GL_EXT_geometry_shader4.
#version 120 #extension GL_EXT_geometry_shader4: enable void main() { for (int i = 0; i < 3; i++) { gl_FrontColor = gl_FrontColorIn[i]; gl_BackColor = gl_BackColorIn[i]; gl_Position = gl_PositionIn[i]; EmitVertex(); } EndPrimitive(); }
Il contenuto del listato sovrastante è rilasciato nel pubblico dominio.
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.