Il Geometry Shader è la novità più conosciuta e forse più importante di DirectX10. Questo nuovo tipo di Shader si posiziona tra il Vertex Shader ed il Pixel Shader.
La pipeline di DirectX10 esegue questi step:
- ogni vertice viene passato al Vertex Shader che restituisce il vertice trasformato
- i vertici vengono raggruppati in primitive e passati al Geometry Shader che restituisce la primitiva trasformata
- DirectX10 prende la primitiva e per ogni Pixel contenuta in essa calcola il valore interpolato da passare al Pixel Shader (operazione di Rastering)
- Il Pixel Shader prende il valore dal rastering e restituisce il colore finale
- Il colore viene confrontato con quello già presente ed il risultato viene stampato sul target
Il Vertex Shader ha 2 limiti importanti:
- non può accedere o modificare i dati di vertici diversi da quello su cui si sta lavorando
- non può modificarne il numero: per ogni vertice ne uscirà sempre e solo
Il Geometry Shader invece lavora con gruppi di vertici e può leggere e modificare ognuno di essi. Inoltre, cosa ancor più interessante, può decidere se la primitiva deve essere mandata a video oppure se mandarne di diverse. Teoricamente potreste partendo da 1 solo vertice creare nel Geometry Shader una intera scena.
Una primitiva è una unità minima visualizzabile da DirectX. Queste sono i punti, le linee ed i triangoli. Il Geometry Shader è in grado di gestire queste primitive comprensive delle adiacenze. Le adiacenze sono le primitive direttamente collegate ad una primitiva. Le linee ed i triangoli possono avere adiacenze. Nel primo caso l’adiacenza ad una linea formata da 2 punti A e B sono il punto che forma la precedente linea con il vertice A e quello che forma la successiva con il vertice B. Nel caso del triangolo invece sono i 3 vertici che formano i triangoli con un lato in comune.
Un Geometry Shader è definito in questo modo
[maxVertexcount(32)]
void GS( triangle GS_INPUT input[3], inout TriangleStream outStream )
GS_INPUT è la struttura che ci facciamo restituire dal Vertex Shader contenente tutto ciò che abbiamo elaborato.
Il parametro triangle GS_INPUT input[3] indica che utilizzeremo un array di 3 elementi di tipo GS_INPUT che conterranno un triangolo. L’oggetto TriangleStream è un oggetto speciale del Geometry Shader. Questo è una lista del tipo che gli passiamo come template e sarà a questa che gli passeremo i vertici per formare le nuove primitive. Il tag maxVertexcount prima della funzione specifica il numero massimo di vertici che la funzione può restituire. Se si supera tale limite i vertici non vengono rende rizzati.
Per il primo parametro sono possibili 5 impostazioni parametri
- point : viene utilizzato dal Geometry Shader 1 solo punto alla volta. L’array ha dimensione pari ad 1
- line : viene utilizzata come primitive una linea alla volta. L’array ha dimensione pari ad 2
- triangle : viene utilizzata come primitive il triangolo. L’array ha dimensione pari ad 3
- lineadj : viene utilizzata come primitive una linea con le sue adiacenze o una strip di linee con le adiacenze. L’array ha dimensione pari ad 4
- triangleadj: viene utilizzata come primitive un triangolo con le adiacenze o un triangle strip con adiacenze. L’array ha dimensione pari ad 6
Per poter utilizzare linee, triangoli con adiacenze e tutte le varie configurazioni servirà impostare il Vertex buffer nel modo corretto e passare l’opportuna Topology al device prima del rendering. Qui sotto ci sono le tipologie di primitive supportate in directX10 ed il modo per riempire l’array.
Il second parametro invece può essere di 3 tipi:
- TriangleStream: per creare primitive
- LineStream: per creare linee
- PointStream: per creare punti
Queste classi hanno 2 metodi:
void Append(T)
Permette di aggiungere un vertice alla lista
Void RestartStrip()
Chiude la primitiva. Se osservate l’immagine potete vedere che linee e triangoli possono essere creati con le strip. Una strip utilizza punti della precedente primitiva per chiudere la successiva. Se vedete ad esempio la triangle strip sono sufficienti 5 vertici per chiudere 4 primitive, cosa che nella triangle list sarebbe possibile solo con 12 vertici. Ricordate però, servono almeno 2 vertici per chiudere una linea e 3 per un triangolo. Altra cosa importante è che non potrete usare la TriangleStream per restituire linee o una PointStream per creare triangoli. Un Geometry Shader quindi può restituire solo uno di questi 3 tipi.
Un altro limite da tenere a mente è il buffer dello stream. In DirectX10 si possono passare allo stream massimo 1024 elementi. Ciò significa che se si restituisce una struttura formata da un float4 per la posizione ed un float2 per le coordinate texture occupiamo 6 elementi. Il numero massimo di vertici che possiamo utilizzare è 1024/6 ossia 170. Con strutture più complesse il numero diminuirà sensibilmente.
Ricordate, lo Stream viene svuotato ad ogni chiamata del Geometry Shader. Quindi il limite dei 170 vertici è per ogni primitiva, non totale per tutta la mesh. Il buffer permette quindi ampi margini. Inoltre potrete usare input ed output diversi. Potrete partire ad esempio da primitive di tipo point e restituire primitive di tipo triangle.
Infine non è necessario passare vertici allo Stream. Potrete anche fare un Geometry Shader che non restituisce nulla. Questo può essere molto utile per eliminare primitive che non vogliamo renderizzare.
Per il resto avrete la massima libertà e potrete utilizzare texture e tutte le istruzioni HLSL. A questo punto molti di voi si porranno una domanda: ma se il Geometry Shader permette tutto questo, a cosa serve il Vertex Shader?
In effetti il Geometry Shader può fare tutto ciò che fa il Vertex Shader ma applicato a più vertici contemporaneamente. La ragione dell’esistenza del Vertex Shader è principalmente di ottimizzazione. Un cubo che sarà salvato nel Vertex buffer come triangle strip, oppure che farò uso di indexbuffer (che vedremo nei prossimi tutorial) farà in modo che un vertice appartenga a più di una primitiva. Di conseguenza il Geometry Shader sarà eseguito sullo stesso vertice più di una volta. Il modo migliore di lavorare in DirectX è quello di delegare più operazioni possibili allo Shader precedente. Ricordate che i Pixel Shader sono quelli che vengono eseguiti più volte, seguiti dal Geometry mentre il Vertex Shader è quello eseguito meno volte.
Vi lascio all’esempio. Questo demo costruisce un array di punti contenenti solo la posizione e per ognuno di questi genera un intero cubo texturizzato nel Geometry Shader. In pratica creeremo una intera mesh direttamente dal Geometry Shader.
Demo
I commenti sono disabilitati.