Home Page Twitter Facebook Feed RSS
NotJustCode
Apri

Geometry Shader 596 Visite) DirectX 11

Il Geometry Shader è la novità più conosciuta e forse più importante introdotta nelle Direct3D10. Questo nuovo tipo di Shader si posiziona prima del Pixel Shader ossia quando le basi della geometria sono già state tracciate dal Vertex, Hull e Domain Shader. Ricordo infatti che il Vertex Shader prende in input i vertici provenienti dal Vertex Buffer dando loro una prima trasformazione e che l’Hull ed il Domain eseguono un aumento degli stessi ed una regola di posizionamento. Al Geometry Shader arrivano geometrie definitive a cui è possibile far eseguire le ultime trasformazioni prima della loro colorazione tramite il Pixel Shader.

Il Geometry Shader lavora quindi non per vertici ma per primitive, che esse siano punti, linee, triangoli o gruppi di triangoli. Questo tipo di Shader fu la grande innovazione delle Direct3D10 per le sue 2 caratteristiche principali:

1. può aumentare o diminuire il numero di primitive nella scena mentre il Vertex Shader ha 1 vertice sia in uscita che in ingresso

2. può lavorare accedendo a più vertici contemporaneamente, mentre il Vertex Shader può leggere solamente 1 vertice

Con Direct3D11 parte delle attività del Geometry sono state spostate nei nuovi Shader, il Domain e l’Hull che sono in grado di aumentare il numero delle primitive della scena in modo molto più ampio, oltre a poter dare delle regole di posizionamento dei vertici. Rimane però il suo utilizzo di manipolazione di primitive e soprattutto per poter salvare il lavoro fatto per future fasi di rendering. Nei prossimi tutorial verrà spiegato come utilizzare il Geometry per salvare intere scene in buffer da utilizzare successivamente, tecnica chiamata di Output Stream che in Direct3D11 è stata notevolmente migliorata. Torniamo ora alla gestione delle primitive tramite Geometry Shader.

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: 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.

Come ultima nota, una novità introdotta con le Direct3D11. Ora potete eseguire più volte il Geometry Shader mettendo come prefisso [instance(n)] dove n è il numero di volte che vorrete eseguire lo Shader (massimo 32). In questo modo il tutto verrà eseguito più volte per ogni primitiva riducendo ulteriormente il numero di chiamate necessarie. Come parametro di ingresso potrete utilizzare una variabile in ingresso alla funzione che restituirà da 0 ad N il numero dell’istanza lanciata.

uint InstanceID : SV_GSInstanceID

L’utilizzo dell’instance richiede lo Shader Model 5.0 e quindi una scheda video DirectX11 compatibile.

 

Vi rimando al tutorial numero 8 della serie (Github o in fondo dal primo tutorial)