Geometry Instancing 1693 Visite) DirectX 11
Il geometry instancing è una tecnologia che permette di replicare un Vertex Buffer numerose volte in modo da creare copie di un oggetto a video in modo ottimizzato.
Tramite il geometry instancing possiamo caricare un soldato ed in un'unica istruzione di draw, mandarne a video migliaia di copie, ognuna con caratteristiche di posizione, colore o texture differenti l'uno dall'altro.
La cosa più interessante è la totale gestione in memoria video del processo, permettendo delle prestazioni nettamente migliori. Infatti ogni volta che si esegue una istruzione di draw ci sono dati che passano dalla RAM alla scheda video o tra zone diverse di quest'ultima: sarà direttamente la GPU ad operare la moltiplicazione dei vertici e degli indici dell'oggetto.
Il geometry instancing prevede l'utilizzo di 2 vertex buffer: il primo contenente i vertici dell'oggetto, il secondo contenente un array di strutture dati specifiche per ogni copia. Ad esempio se vogliamo rappresentare 1000 oggetti, ognuno con posizione e colore diverso, dovremo creare una struttura contenente posizione e colore e crearne un array di 1000 elementi. La funzionalità geometry instancing non farà altro che fondere ogni vertice con ogni elemento del secondo buffer. Nel vertex shader troveremo una struttura di input che sarà la somma dei 2 buffer. Dal punto di vista dello shader sarà quindi sufficiente prevedere dei dati extra da utilizzare per modificare la posizione ed il colore dell'oggetto. Per prima cosa è necessario creare un buffer per contenere i dati
_instanceBuffer = Buffer.Create(Device, BindFlags.VertexBuffer, data);
Quindi, come già visto, populate il buffer inserendo per ogni clone dell’oggetto un differente valore (ad esempio una struttura che contenga la posizione dell’oggetto rispetto al centro).
Come vedete è simile a quanto usato per il vertex buffer. Per poter utilizzare l'instancing occorre creare un layout appropriato
new InputElement[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0),
new InputElement("NORMAL", 0, Format.R32G32B32_Float, 12, 0),
new InputElement("TEXCOORD", 0, Format.R32G32_Float, 24, 0),
new InputElement("INSTANCEPOSITION",0,Format.R32G32B32_Float,0,1,InputClassification.PerInstanceData,1)
Nell' instancing il layout è formato dalla somma dei due formati vertici. Il secondo buffer è descritto dalla seconda parte dell'array. Si può notare infatti che l'indice è impostato ad 1 e non a 0 (perché sarà estratto dal secondo buffer), l'offset è a zero (perché si conta l'offset del buffer, non totale del vertice) e si imposta il tipo a PerInstanceData. L'ultimo valore, instance repetition, va impostato ad 1.
Ora quello che si dovrò fare in fase di rendering è passare i 2 vertex buffer contemporaneamente (quello con i dati e quello con i dati per l'instancing). Per far questo basta variare l’indice.
Quindi si procede al rendering definendo il numero di istanze da renderizzare
DeviceContext.DrawIndexedInstanced(indexCountPerInstance, count, startIndexLocation, 0, 0);
Con la funzione DrawIndexedInstance potrete renderizzare oggetti da un minimo di zero ad un massimo pari al numero di elementi nel buffer dell'instance.
Nello shader non noterete differenza
struct VS_INPUT
{
float4 Pos : POSITION;
float2 tex:TEXCOORD;
float4 posIstance:EXTRADATA;
};
Ogni vertice del primo buffer sarà combinato con ogni vertice (anche se in realtà sono dati anche se gestiti come vertici). Quindi se abbiamo un cubo e 1000 vettori posizione otterremo 8 (vertici del cubo) * 1000 = 8000 vertici e quindi 8000 chiamate allo shader con tutte le combinazioni. Nello shader ad esempio potremo utilizzare il vettore per spostare il cubo in modo da assegnare ad ognuno una posizione diversa. Passando 4 float4 avrete una intera matrice. Potete passare quanti dati volete.
Nella struttura di input potete utilizzare un campo non definito nei due buffer con sintatti SV_InstanceID
uint indice:SV_InstanceID
Questo campo contiene l'indice dell'instanza che stiamo renderizzando (che varierà quindi da 0 a 999 nell'esempio).
Vi rimando al tutorial numero 13 della serie (Github o in fondo dal primo tutorial)