Leggere in inglese

Condividi tramite


Disegno efficiente di più istanze di geometria (Direct3D 9)

Data una scena che contiene molti oggetti che usano la stessa geometria, è possibile disegnare molte istanze di tale geometria con orientamenti, dimensioni, colori e così via con prestazioni notevolmente migliori riducendo la quantità di dati che è necessario fornire al renderer.

Questa operazione può essere eseguita tramite l'uso di due tecniche: la prima per la geometria indicizzata e la seconda per la geometria non indicizzata. Entrambe le tecniche utilizzano due buffer di vertici: uno per fornire dati di geometria e uno per fornire dati di istanza per oggetto. I dati dell'istanza possono essere un'ampia gamma di informazioni, ad esempio una trasformazione, dati di colore o dati di illuminazione, in pratica qualsiasi elemento che è possibile descrivere in una dichiarazione di vertice. Il disegno di molte istanze di geometria con queste tecniche può ridurre notevolmente la quantità di dati inviati al renderer.

Disegno della geometria indicizzata

Il buffer dei vertici contiene dati per vertice definiti da una dichiarazione di vertice. Si supponga che una parte di ogni vertice contenga dati geometrici e che una parte di ogni vertice contenga dati di istanza per oggetto, come illustrato nel diagramma seguente.

diagramma di un vertex buffer per geometria indicizzata

Questa tecnica richiede un dispositivo che supporta il modello di vertex shader 3_0. Questa tecnica funziona con qualsiasi shader programmabile, ma non con la pipeline di funzioni fisse.

Per i vertex buffer illustrati in precedenza, ecco le dichiarazioni di vertex buffer corrispondenti:

const D3DVERTEXELEMENT9 g_VBDecl_Geometry[] =
{
{0,  0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0},
{0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT,  0},
{0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 0},
{0, 48, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
D3DDECL_END()
};

const D3DVERTEXELEMENT9 g_VBDecl_InstanceData[] =
{
{1, 0,  D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1},
{1, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2},
{1, 32, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3},
{1, 48, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4},
{1, 64, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,    0},
D3DDECL_END()
};

Queste dichiarazioni definiscono due buffer dei vertici. La prima dichiarazione (per il flusso 0, indicata dagli zeri nella colonna 1) definisce i dati geometrici costituiti da: posizione, normale, tangente, binormale e dati delle coordinate della trama.

La seconda dichiarazione (per il flusso 1, indicato da quelli nella colonna 1) definisce i dati delle istanze per oggetto. Ogni istanza è definita da quattro numeri a virgola mobile a quattro componenti e da un colore a quattro componenti. I primi quattro valori possono essere usati per inizializzare una matrice 4x4, il che significa che questi dati avranno dimensioni univoche, posizione e rotazione di ogni istanza della geometria. I primi quattro componenti usano una semantica di coordinate della trama che, in questo caso, significa "si tratta di un numero generale a quattro componenti". Quando si usano dati arbitrari in una dichiarazione di vertice, usare una semantica di coordinate della trama per contrassegnarla. L'ultimo elemento nel flusso viene utilizzato per i dati a colori. Questo può essere applicato nel vertex shader per assegnare a ogni istanza un colore univoco.

Prima del rendering, è necessario chiamare SetStreamSourceFreq per associare i flussi del buffer dei vertici al dispositivo. Di seguito è riportato un esempio che associa entrambi i vertex buffer:

// Set up the geometry data stream
pd3dDevice->SetStreamSourceFreq(0,
    (D3DSTREAMSOURCE_INDEXEDDATA | g_numInstancesToDraw));
pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
    D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));

// Set up the instance data stream
pd3dDevice->SetStreamSourceFreq(1,
    (D3DSTREAMSOURCE_INSTANCEDATA | 1));
pd3dDevice->SetStreamSource(1, g_VB_InstanceData, 0, 
    D3DXGetDeclVertexSize( g_VBDecl_InstanceData, 1 ));

SetStreamSourceFreq usa D3DSTREAMSOURCE_INDEXEDDATA per identificare i dati di geometria indicizzati. In questo caso, il flusso 0 contiene i dati indicizzati che descrivono la geometria dell'oggetto. Questo valore viene combinato logicamente con il numero di istanze della geometria da disegnare.

Si noti che D3DSTREAMSOURCE_INDEXEDDATA e il numero di istanze da renderizzare devono essere sempre impostati nel flusso zero.

Nella seconda chiamata setStreamSourceFreq usa D3DSTREAMSOURCE_INSTANCEDATA per identificare il flusso contenente i dati dell'istanza. Questo valore viene combinato logicamente con 1 perché ogni vertice contiene un set di dati dell'istanza.

Le ultime due chiamate a SetStreamSource associano i puntatori del buffer dei vertici al dispositivo.

Al termine del rendering dei dati dell'istanza, assicurarsi di ripristinare la frequenza del flusso dei vertici al suo stato predefinito, che non utilizza l'instancing. Poiché in questo esempio sono stati usati due flussi, impostare entrambi i flussi come illustrato di seguito:

pd3dDevice->SetStreamSourceFreq(0,1);
pd3dDevice->SetStreamSourceFreq(1,1);

Confronto delle prestazioni di geometria indicizzata

Anche se non è possibile ottenere una singola conclusione sul modo in cui questa tecnica potrebbe ridurre il tempo di rendering in ogni applicazione, considerare la differenza nella quantità di dati trasmessi nel runtime e il numero di modifiche di stato che verranno ridotte se si usa la tecnica di istanziazione. Questa sequenza di rendering sfrutta il disegno di più istanze della stessa geometria:

if( SUCCEEDED( pd3dDevice->BeginScene() ) )
{
    // Set up the geometry data stream
    pd3dDevice->SetStreamSourceFreq(0,
                (D3DSTREAMSOURCE_INDEXEDDATA | g_numInstancesToDraw));
    pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
                D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));

    // Set up the instance data stream
    pd3dDevice->SetStreamSourceFreq(1,
                (D3DSTREAMSOURCE_INSTANCEDATA | 1));
    pd3dDevice->SetStreamSource(1, g_VB_InstanceData, 0, 
                D3DXGetDeclVertexSize( g_VBDecl_InstanceData, 1 ));

    pd3dDevice->SetVertexDeclaration( ... );
    pd3dDevice->SetVertexShader( ... );
    pd3dDevice->SetIndices( ... );

    pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, 
                g_dwNumVertices, 0, g_dwNumIndices/3 );
    
    pd3dDevice->EndScene();
}

Si noti che il ciclo di rendering viene chiamato una volta, i dati di geometria vengono trasmessi ancora una volta e n istanze vengono trasmesse ancora una volta. Questa sequenza di rendering successiva è identica nella funzionalità, ma non sfrutta la creazione di istanze:

if( SUCCEEDED( pd3dDevice->BeginScene() ) )
{
    for(int i=0; i < g_numObjects; i++)
    {
        pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
                D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));


        pd3dDevice->SetVertexDeclaration( ... );
        pd3dDevice->SetVertexShader( ... );
        pd3dDevice->SetIndices( ... );

        pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, 
                g_dwNumVertices, 0, g_dwNumIndices/3 );
    }                             
    
    pd3dDevice->EndScene();
}

Si noti che l'intero ciclo di rendering è racchiuso da un secondo ciclo per disegnare ogni oggetto. Ora i dati geometry vengono trasmessi nel renderer n volte (invece di una volta) e anche gli stati della pipeline possono essere impostati in modo ridondante per ogni oggetto disegnato. Questa sequenza di rendering è molto probabilmente più lenta. Si noti anche che i parametri per DrawIndexedPrimitive non sono stati modificati tra i due cicli di rendering.

Disegno di geometria non indicizzata

In Disegnare Geometria Indicizzata, i buffer dei vertici sono stati configurati per disegnare più istanze della geometria indicizzata con maggiore efficienza. È anche possibile usare SetStreamSourceFreq per disegnare una geometria non indicizzata. Ciò richiede un layout diverso del buffer dei vertici e ha vincoli diversi. Per disegnare una geometria non indicizzata, prepara i buffer dei vertici come il diagramma seguente.

diagramma del buffer dei vertici per geometria non indicizzata

Questa tecnica non è supportata dall'accelerazione hardware in alcun dispositivo. È supportato solo dall'elaborazione dei vertici software e funziona solo con gli shader vs_3_0.

Poiché questa tecnica funziona con una geometria non indicizzata, non esiste alcun buffer di indice. Come illustrato nel diagramma, il buffer dei vertici che contiene la geometria include n copie dei dati geometrici. Per ogni istanza disegnata, i dati di geometria vengono letti dal primo buffer dei vertici e i dati dell'istanza vengono letti dal secondo buffer dei vertici.

Ecco le dichiarazioni di vertex buffer corrispondenti:

const D3DVERTEXELEMENT9 g_VBDecl_Geometry[] =
{
{0,  0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0},
{0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT,  0},
{0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 0},
{0, 48, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
D3DDECL_END()
};

const D3DVERTEXELEMENT9 g_VBDecl_InstanceData[] =
{
{1, 0,  D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1},
{1, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2},
{1, 32, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3},
{1, 48, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4},
{1, 64, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,    0},
D3DDECL_END()
};

Queste dichiarazioni sono identiche alle dichiarazioni effettuate nell'esempio di geometria indicizzata. Ancora una volta, la prima dichiarazione (per il flusso 0) definisce i dati geometrici e la seconda dichiarazione (per il flusso 1) definisce i dati delle istanze per oggetto. Quando si crea il primo buffer dei vertici, assicurarsi di caricarlo con il numero di istanze dei dati geometrici che verranno disegnati.

Prima del rendering, è necessario configurare il divisore che indica al runtime come dividere il primo vertex buffer in n istanze. Impostare quindi il divisore usando SetStreamSourceFreq simile al seguente:

// Set the divider
pd3dDevice->SetStreamSourceFreq(0, 1);
// Bind the stream to the vertex buffer
pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
        D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));

// Set up the instance data stream
pd3dDevice->SetStreamSourceFreq(1, verticesPerInstance);
pd3dDevice->SetStreamSource(1, g_VB_InstanceData, 0, 
        D3DXGetDeclVertexSize( g_VBDecl_InstanceData, 1 ));

La prima chiamata a SetStreamSourceFreq indica che il flusso 0 contiene n istanze di m vertici. SetStreamSource quindi associa il flusso 0 al buffer dei vertici di geometria.

Nella seconda chiamata SetStreamSourceFreq identifica il flusso 1 come origine dei dati dell'istanza. Il secondo parametro è il numero di vertici in ogni oggetto (m). Tenere presente che il flusso di dati dell'istanza deve essere sempre dichiarato come secondo flusso. SetStreamSource quindi associa il flusso 1 al vertex buffer che contiene i dati dell'istanza.

Al termine del rendering dei dati dell'istanza, assicurati di ripristinare la frequenza del flusso dei vertici al suo stato predefinito. Poiché in questo esempio sono stati usati due flussi, impostare entrambi i flussi come illustrato di seguito:

pd3dDevice->SetStreamSourceFreq(0,1);
pd3dDevice->SetStreamSourceFreq(1,1);

Confronto delle prestazioni di geometria non indicizzate

Il vantaggio principale di questo stile di creazione di istanze è che può essere usato sulla geometria non indicizzata. Anche se non è possibile effettuare una singola conclusione sulla quantità di questa tecnica che potrebbe ridurre il tempo di rendering in ogni applicazione, considerare la differenza nella quantità di dati trasmessi nel runtime e il numero di modifiche di stato che verranno ridotte per la sequenza di rendering seguente:

if( SUCCEEDED( pd3dDevice->BeginScene() ) )
{
    // Set the divider
    pd3dDevice->SetStreamSourceFreq(0, 1);
    pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
                D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));

    // Set up the instance data stream
    pd3dDevice->SetStreamSourceFreq(1, verticesPerInstance));
    pd3dDevice->SetStreamSource(1, g_VB_InstanceData, 0, 
                D3DXGetDeclVertexSize( g_VBDecl_InstanceData, 1 ));

    pd3dDevice->SetVertexDeclaration( ... );
    pd3dDevice->SetVertexShader( ... );
    pd3dDevice->SetIndices( ... );

    pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, 
                g_dwNumVertices, 0, g_dwNumIndices/3 );
    
    pd3dDevice->EndScene();
}

Si noti che il ciclo di rendering viene chiamato una sola volta. I dati geometrici vengono trasmessi una volta anche se sono presenti n istanze della geometria. I dati del vertex buffer dell'istanza vengono trasmessi una sola volta. Questa sequenza di rendering successiva è identica nella funzionalità, ma non sfrutta la creazione di istanze:

if( SUCCEEDED( pd3dDevice->BeginScene() ) )
{
    for(int i=0; i < g_numObjects; i++)
    {
        pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0,
                D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));

        pd3dDevice->SetVertexDeclaration( ... );
        pd3dDevice->SetVertexShader( ... );
        pd3dDevice->SetIndices( ... );

        pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, 
                g_dwNumVertices, 0, g_dwNumIndices/3 );
    }
    
    pd3dDevice->EndScene();
}

Senza creare istanze, è necessario avvolgere il ciclo di rendering in un secondo ciclo per disegnare ogni oggetto. Eliminando il secondo ciclo di rendering, è consigliabile prevedere prestazioni migliori a causa di un minor numero di modifiche dello stato di rendering chiamate all'interno del ciclo.

Nel complesso, è ragionevole aspettarsi che la tecnica indicizzata (Disegnare la geometria indicizzata) per ottenere prestazioni migliori rispetto alla tecnica non indicizzata (Disegno geometria non indicizzata) perché la tecnica indicizzata trasmette solo una copia dei dati geometrici. Si noti che i parametri per DrawIndexedPrimitive non sono stati modificati per nessuna delle sequenze di rendering.

argomenti avanzati

Esempio di Instancing