Efficiënt meerdere instanties van geometrie tekenen (Direct3D 9)
Gezien een scène met veel objecten die dezelfde geometrie gebruiken, kunt u veel exemplaren van die geometrie tekenen op verschillende oriëntaties, grootten, kleuren, enzovoort met aanzienlijk betere prestaties door de hoeveelheid gegevens die u aan de renderer moet leveren, te verminderen.
Dit kan worden bereikt door gebruik te maken van twee technieken: de eerste voor het tekenen van geïndexeerde geometrie en de tweede voor niet-geïndexeerde geometrie. Beide technieken maken gebruik van twee hoekpuntbuffers: één voor het leveren van geometriegegevens en één voor het leveren van gegevens per objectexemplaren. De instantiegegevens kunnen een grote verscheidenheid aan informatie zijn, zoals een transformatie, kleurgegevens of belichtingsgegevens, in principe alles wat u in een hoekpuntdeclaratie kunt beschrijven. Het tekenen van veel geometrie-exemplaren met deze technieken kan de hoeveelheid gegevens die naar de renderer wordt verzonden aanzienlijk verminderen.
Geindexeerde geometrie tekenen
De hoekpuntbuffer bevat gegevens per hoekpunt die worden gedefinieerd door een hoekpuntdeclaratie. Stel dat een deel van elk hoekpunt geometriegegevens bevat en dat een deel van elk hoekpunt gegevens per objectexemplaren bevat, zoals wordt weergegeven in het volgende diagram.
Voor deze techniek is een apparaat vereist dat ondersteuning biedt voor het 3_0-hoekpunt-shadermodel. Deze techniek werkt met programmeerbare shader, maar niet met de vaste functiepijplijn.
Voor de hierboven weergegeven hoekpuntbuffers ziet u hier de bijbehorende declaraties van de hoekpuntbuffer:
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()
};
Deze declaraties definiëren twee hoekpuntbuffers. De eerste declaratie (voor stroom 0, aangegeven door de nullen in kolom 1) definieert de geometriegegevens die bestaan uit: positie, normale, tangens, binormale en patrooncoördinaatgegevens.
De tweede declaratie (voor stream 1, aangegeven door de gegevens in kolom 1) definieert de gegevens per objectinstantie. Elk exemplaar wordt gedefinieerd door vier drijvendekommanummers en een kleur van vier onderdelen. De eerste vier waarden kunnen worden gebruikt om een 4x4-matrix te initialiseren, wat betekent dat deze gegevens op unieke wijze de grootte, positie en rotatie van elk exemplaar van de geometrie zullen bepalen. De eerste vier onderdelen gebruiken een semantisch patrooncoördinaat, wat in dit geval 'dit is een algemeen vier-onderdeelnummer' betekent. Wanneer u willekeurige gegevens in een hoekpuntdeclaratie gebruikt, gebruikt u een semantische patrooncoördinaat om deze te markeren. Het laatste element in de stroom wordt gebruikt voor kleurgegevens. Dit kan worden toegepast in de hoekpunt-shader om elk exemplaar een unieke kleur te geven.
Voordat u gaat renderen, moet u SetStreamSourceFreq- aanroepen om de hoekpuntbufferstromen aan het apparaat te binden. Hier volgt een voorbeeld waarin beide hoekpuntbuffers worden gekoppeld:
// 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 gebruikt D3DSTREAMSOURCE_INDEXEDDATA om de geïndexeerde geometriegegevens te identificeren. In dit geval bevat stream 0 de geïndexeerde gegevens die de objectgeometrie beschrijven. Deze waarde wordt logisch gecombineerd met het aantal exemplaren van de geometrie dat moet worden getekend.
Houd er rekening mee dat D3DSTREAMSOURCE_INDEXEDDATA en het aantal exemplaren dat moet worden getekend, altijd in stream 0 moeten worden ingesteld.
In de tweede aanroep gebruikt SetStreamSourceFreqD3DSTREAMSOURCE_INSTANCEDATA om de stroom met de instantiegegevens te identificeren. Deze waarde wordt logisch gecombineerd met 1 omdat elk hoekpunt één set exemplaargegevens bevat.
De laatste twee aanroepen naar SetStreamSource de hoekpuntbufferpunten aan het apparaat binden.
Wanneer u klaar bent met het weergeven van de instantiegegevens, moet u de frequentie van de hoekpuntstroom terugzetten naar de standaardstatus (die geen instancing gebruikt). Omdat in dit voorbeeld twee streams zijn gebruikt, stelt u beide streams in zoals hieronder wordt weergegeven:
pd3dDevice->SetStreamSourceFreq(0,1);
pd3dDevice->SetStreamSourceFreq(1,1);
Vergelijking van geïndexeerde geometrieprestaties
Hoewel het niet mogelijk is om één conclusie te trekken over hoeveel deze techniek de rendertijd in elke toepassing kan verminderen, moet u rekening houden met het verschil in de hoeveelheid gegevens die in de runtime wordt gestreamd en het aantal statuswijzigingen dat wordt verminderd als u de methode voor instancing gebruikt. Deze weergavevolgorde maakt gebruik van het tekenen van meerdere exemplaren van dezelfde geometrie:
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();
}
U ziet dat de renderlus eenmaal wordt aangeroepen, dat de geometriegegevens eenmaal worden gestreamd en dat n exemplaren eenmaal worden gestreamd. Deze volgende renderreeks is identiek in functionaliteit, maar profiteert niet van 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();
}
U ziet dat de hele renderlus wordt verpakt door een tweede lus om elk object te tekenen. De geometriegegevens worden nu n keer in de renderer gestreamd (in plaats van één keer) en eventuele pijplijnstatussen kunnen ook redundant worden ingesteld voor elk getekend object. Deze rendervolgorde is zeer waarschijnlijk aanzienlijk langzamer. U ziet ook dat de parameters voor DrawIndexedPrimitive niet zijn gewijzigd tussen de twee renderlussen.
Niet-geïndexeerde geometrie tekenen
In Teken van Geïndexeerde Geometriezijn vertexbuffers geconfigureerd voor het efficiënter tekenen van meerdere exemplaren van de geïndexeerde geometrie. U kunt ook SetStreamSourceFreq- gebruiken om niet-geïndexeerde geometrie te tekenen. Hiervoor is een andere indeling voor de hoekpuntbuffer vereist en zijn er verschillende beperkingen. Als u niet-geïndexeerde geometrie wilt tekenen, bereidt u de hoekpuntbuffers voor, zoals in het volgende diagram.
Deze techniek wordt niet ondersteund door hardwareversnelling op elk apparaat. Het wordt alleen ondersteund door softwarematige verwerking van hoekpunten en werkt uitsluitend met vs_3_0 shaders.
Omdat deze techniek werkt met niet-geïndexeerde geometrie, is er geen indexbuffer. Zoals in het diagram wordt weergegeven, bevat de hoekpuntbuffer met geometrie n kopieën van de geometriegegevens. Voor elk getekend exemplaar worden de geometriegegevens gelezen uit de eerste hoekpuntbuffer en worden de instantiegegevens gelezen uit de tweede hoekpuntbuffer.
Dit zijn de bijbehorende declaraties voor hoekpuntbuffers:
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()
};
Deze declaraties zijn identiek aan de declaraties in het voorbeeld van de geïndexeerde geometrie. Opnieuw definieert de eerste declaratie (voor stream 0) de geometriegegevens en de tweede declaratie (voor stream 1) definieert de gegevens per objectinstantie. Wanneer u de eerste hoekpuntbuffer maakt, moet u deze laden met het aantal exemplaren van de geometriegegevens die u gaat tekenen.
Voordat u gaat renderen, moet u de scheidingslijn instellen die de runtime vertelt hoe de eerste hoekpuntbuffer in n exemplaren moet worden verdeeld. Stel vervolgens de scheidingslijn in met behulp van SetStreamSourceFreq als volgt:
// 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 ));
De eerste aanroep naar SetStreamSourceFreq zegt dat stream 0 n exemplaren van m hoekpunten bevat. SetStreamSource- verbindt stream 0 vervolgens met de geometrie-hoekpuntbuffer.
In de tweede aanroep identificeert SetStreamSourceFreq stream 1 als de bron van de exemplaargegevens. De tweede parameter is het aantal hoekpunten in elk object (m). Houd er rekening mee dat de instantiegegevensstroom altijd als de tweede stream moet worden gedeclareerd. SetStreamSource verbindt stream 1 vervolgens met de hoekpuntbuffer die de instantiegegevens bevat.
Wanneer u klaar bent met het weergeven van de instantiegegevens, moet u de frequentie van de hoekpuntstroom terugzetten naar de standaardstatus. Omdat in dit voorbeeld twee streams zijn gebruikt, stelt u beide streams in zoals hieronder wordt weergegeven:
pd3dDevice->SetStreamSourceFreq(0,1);
pd3dDevice->SetStreamSourceFreq(1,1);
Vergelijking van niet-geïndexeerde geometrieprestaties
Het belangrijkste voordeel van deze instancing-stijl is dat deze kan worden gebruikt voor niet-geïndexeerde geometrie. Hoewel het niet mogelijk is om één conclusie te trekken over hoeveel deze techniek de weergavetijd in elke toepassing kan verminderen, moet u rekening houden met het verschil in de hoeveelheid gegevens die in de runtime wordt gestreamd en het aantal statuswijzigingen dat voor de volgende weergavevolgorde wordt verminderd:
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();
}
U ziet dat de renderlus eenmaal wordt aangeroepen. De geometriegegevens worden eenmaal gestreamd, hoewel er n exemplaren zijn van de geometrie die worden gestreamd. De data uit de vertexbuffer van het exemplaar wordt eenmaal doorgestuurd. Deze volgende renderreeks is identiek in functionaliteit, maar profiteert niet van 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();
}
Zonder instancing moet de rendercyclus worden omgeven door een tweede lus om elk object te tekenen. Door de tweede renderlus te elimineren, zou u betere prestaties moeten verwachten als gevolg van minder statuswijzigingen die in de lus worden aangeroepen.
Over het algemeen is het redelijk om te verwachten dat de geïndexeerde techniek (Drawing Indexed Geometry) beter presteert dan de niet-geïndexeerde techniek (Teken niet-geïndexeerde geometrie) omdat de geïndexeerde techniek slechts één kopie van de geometriegegevens streamt. U ziet dat de parameters voor DrawIndexedPrimitive niet zijn gewijzigd voor een van de renderreeksen.
Verwante onderwerpen