Condividi tramite


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

Dato una scena che contiene molti oggetti che usano la stessa geometria, è possibile disegnare molte istanze di tale geometria con orientamento, 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 usano due buffer di vertice: uno per fornire dati geometry 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.

Geometria indicizzata

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

diagramma di un buffer dei vertici per la geometria indicizzata

Questa tecnica richiede un dispositivo che supporta il modello vertex 3_0. Questa tecnica funziona con qualsiasi shader programmabile, ma non con la pipeline della funzione fissa.

Per i buffer dei vertici illustrati in precedenza, ecco le dichiarazioni del buffer dei vertici 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 di vertice. La prima dichiarazione (per il flusso 0, indicato dagli zeri nella colonna 1) definisce i dati geometry costituiti da: posizione, normale, tangente, binormal e dati di coordinata trama.

La seconda dichiarazione (per il flusso 1, indicato da quelle nella colonna 1) definisce i dati dell'istanza 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, posizioni e ruotano ogni istanza della geometria in modo univoco. I primi quattro componenti usano una semantica della coordinata trama che, in questo caso, significa "questo è un numero generale a quattro componenti". Quando si usano dati arbitrari in una dichiarazione di vertice, usare una semantica della coordinata trama per contrassegnarla. L'ultimo elemento del flusso viene usato per i dati del colore. Ciò 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. Ecco un esempio che associa entrambi i buffer dei vertici:

// 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 geometry 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 disegnare devono essere sempre impostati in stream zero.

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

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

Al termine del rendering dei dati dell'istanza, assicurarsi di reimpostare la frequenza del flusso del vertice allo stato predefinito (che non usa instancing). Poiché questo esempio ha usato due flussi, impostare entrambi i flussi, come illustrato di seguito:

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

Confronto delle prestazioni geometry indicizzate

Anche se non è possibile fare una singola conclusione sulla quantità di 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 dello stato che verranno ridotte se si usa la tecnica di instancing. 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 geometry vengono trasmessi una sola volta e n istanze vengono trasmessi una volta. Questa sequenza di rendering successiva è identica alla funzionalità, ma non sfrutta l'instancing:

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 viene sottoposto a wrapping da un secondo ciclo per disegnare ogni oggetto. Ora i dati geometry vengono trasmessi al renderer n volte (anziché una sola volta) e tutti gli stati della pipeline possono essere impostati con ridondanza per ogni oggetto disegnato. Questa sequenza di rendering è molto probabile che sia significativamente più lenta. Si noti anche che i parametri di DrawIndexedPrimitive non sono stati modificati tra i due cicli di rendering.

Disegno di geometria non indicizzata

In Drawing Indexed Geometry, i buffer dei vertici sono stati configurati per disegnare più istanze di geometria indicizzata con maggiore efficienza. È anche possibile usare SetStreamSourceFreq per disegnare geometria non indicizzata. Ciò richiede un layout del buffer dei vertici diverso e presenta vincoli diversi. Per disegnare geometria non indicizzata, preparare i buffer dei vertici come il diagramma seguente.

diagramma di un buffer dei vertici per la geometria non indicizzata

Questa tecnica non è supportata dall'accelerazione hardware in qualsiasi dispositivo. È supportato solo dall'elaborazione dei vertici software e funzionerà solo con vs_3_0 shader.

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

Di seguito sono riportate le dichiarazioni del buffer dei vertici 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 geometry indicizzato. Ancora una volta, la prima dichiarazione (per il flusso 0) definisce i dati geometry e la seconda dichiarazione (per il flusso 1) definisce i dati dell'istanza per oggetto. Quando si crea il primo buffer del vertice, assicurarsi di caricarlo con il numero di istanze dei dati geometry che verranno disegnate.

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

// 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 vertici m. SetStreamSource associa quindi il flusso 0 al buffer del vertice geometry.

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 associa quindi il flusso 1 al buffer del vertice che contiene i dati dell'istanza.

Al termine del rendering dei dati dell'istanza, assicurarsi di reimpostare la frequenza del flusso del vertice allo stato predefinito. Poiché questo esempio ha usato due flussi, impostare entrambi i flussi, come illustrato di seguito:

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

Confronto tra prestazioni geometry non indicizzate

Il vantaggio principale di questo stile instancing è che può essere usato su geometria non indicizzata. Sebbene non sia possibile effettuare una singola conclusione sulla quantità di 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 dello 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 geometry vengono trasmessi una volta anche se sono presenti n istanze della geometria in flusso. I dati del buffer del vertice dell'istanza vengono trasmessi una volta. Questa sequenza di rendering successiva è identica alla funzionalità, ma non sfrutta l'instancing:

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 instancing, il ciclo di rendering deve essere sottoposto a wrapping da 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.

In generale, è ragionevole prevedere che la tecnica indicizzata (Disegno geometria indicizzata) funzioni meglio della tecnica non indicizzata (Disegno geometria non indicizzata) perché la tecnica indicizzata trasmette solo una copia dei dati geometry. Si noti che i parametri di DrawIndexedPrimitive non sono stati modificati per una delle sequenze di rendering.

Argomenti avanzati

Esempio di instancing