Share via


有效率地繪製 Geometry (Direct3D 9)

假設場景包含許多使用相同幾何的物件,您可以在不同的方向、大小、色彩等等繪製該幾何的許多實例,藉由減少提供給轉譯器所需的資料量,大幅提升效能。

這可透過使用兩種技術來完成:第一個用於繪製索引幾何,第二個用於非索引幾何。 這兩種技術都使用兩個頂點緩衝區:一個提供幾何資料,另一個用來提供個別物件實例資料。 實例資料可以是各種不同的資訊,例如轉換、色彩資料或光來源資料 ,基本上就是您可以在頂點宣告中描述的任何專案。 使用這些技術繪製許多幾何實例,可以大幅減少傳送至轉譯器的資料量。

繪圖索引幾何

頂點緩衝區包含頂點宣告所定義的個別頂點資料。 假設每個頂點的一部分包含幾何資料,而每個頂點的一部分則包含個別物件實例資料,如下圖所示。

索引幾何頂點緩衝區的圖表

這項技術需要支援 3_0 頂點著色器模型的裝置。 這項技術適用于任何可程式化著色器,但不適用於固定函式管線。

針對上述頂點緩衝區,以下是對應的頂點緩衝區宣告:

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()
};

這些宣告會定義兩個頂點緩衝區。 資料流程 0 的第一個宣告 (,由資料行 1 中的零表示,) 定義幾何資料,其中包含:位置、一般、正切、二進位和紋理座標資料。

資料流程 1 的第二個宣告 (,資料行 1 中的宣告) 定義每個物件實例資料。 每個實例都是由四個元件浮點數和四個元件色彩所定義。 前四個值可用來初始化 4x4 矩陣,這表示此資料會唯一的大小、位置和旋轉幾何的每個實例。 前四個元件使用紋理座標語意,在此案例中表示「這是一般四個元件編號」。當您在頂點宣告中使用任意資料時,請使用紋理座標語意來標記它。 資料流程中的最後一個專案用於色彩資料。 這可以在頂點著色器中套用,為每個實例提供唯一的色彩。

在轉譯之前,您必須呼叫 SetStreamSourceFreq ,將頂點緩衝區資料流程系結至裝置。 以下是系結兩個頂點緩衝區的範例:

// 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 會使用 D3DSTREAMSOURCE_INDEXEDDATA 來識別索引的幾何資料。 在此情況下,資料流程 0 包含描述物件幾何的索引資料。 這個值會以邏輯方式結合要繪製之 geometry 的實例數目。

請注意,D3DSTREAMSOURCE_INDEXEDDATA和要繪製的實例數目一律必須在資料流程零中設定。

第二次呼叫 時,SetStreamSourceFreq 會使用 D3DSTREAMSOURCE_INSTANCEDATA 來識別包含實例資料的資料流程。 這個值會以邏輯方式結合 1,因為每個頂點都包含一組實例資料。

最後兩次呼叫 SetStreamSource 會將頂點緩衝區指標系結至裝置。

當您完成轉譯實例資料時,請務必將頂點資料流程頻率重設為其預設狀態, (不會使用具現化) 。 由於此範例使用兩個數據流,因此請設定這兩個數據流,如下所示:

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

索引幾何效能比較

雖然無法對這項技術減少每個應用程式中的轉譯時間量做出單一結論,但請考慮串流至執行時間的資料量差異,以及如果您使用實例技術將會減少的狀態變更數目。 此轉譯順序會利用繪製相同幾何的多個實例:

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();
}

請注意,轉譯迴圈會呼叫一次,幾何資料會串流處理一次,而 n 個實例則會串流處理一次。 下一個轉譯順序在功能中完全相同,但不會利用實例:

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();
}

請注意,整個轉譯迴圈會由第二個迴圈包裝,以繪製每個物件。 現在,幾何資料會串流至轉譯器 n 次 (,而不是一次) ,而且任何管線狀態也可以針對繪製的每個物件重複設定。 這個轉譯順序可能非常慢。 另請注意, DrawIndexedPrimitive 的參數在兩個轉譯迴圈之間並未變更。

繪製非索引幾何

繪圖索引幾何中,頂點緩衝區已設定為以更高的效率繪製多個索引幾何實例。 您也可以使用 SetStreamSourceFreq 來繪製非索引幾何。 這需要不同的頂點緩衝區配置,而且有不同的條件約束。 若要繪製非索引幾何,請準備頂點緩衝區,如下圖所示。

非索引幾何頂點緩衝區的圖表

任何裝置上的硬體加速都不支援這項技術。 它僅受軟體頂點處理支援,且僅適用于 vs_3_0 著色器。

由於這項技術適用于非索引幾何,因此沒有索引緩衝區。 如圖所示,包含 geometry 的頂點緩衝區包含 n 份幾何資料。 針對每個繪製的實例,幾何資料是從第一個頂點緩衝區讀取,而實例資料則是從第二個頂點緩衝區讀取。

以下是對應的頂點緩衝區宣告:

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()
};

這些宣告與索引幾何範例中所做的宣告相同。 同樣地,資料流程 0 的第一個宣告 () 定義幾何資料,而資料流程) 1 的第二個宣告 (則定義個別物件實例資料。 當您建立第一個頂點緩衝區時,請務必使用您要繪製之 geometry 資料的實例數目來載入它。

在轉譯之前,您必須設定分隔線,告知執行時間如何將第一個頂點緩衝區分割成 n 個實例。 然後使用 SetStreamSourceFreq 來 設定分隔符號,如下所示:

// 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 ));

第一次呼叫 SetStreamSourceFreq 表示資料流程 0 包含 m 頂點的 n 個實例。 SetStreamSource 接著會將資料流程 0 系結至幾何頂點緩衝區。

第二次呼叫 時,SetStreamSourceFreq 會將資料流程 1 識別為實例資料的來源。 第二個參數是每個物件中的頂點數目, (m) 。 請記住,實例資料流程必須一律宣告為第二個數據流。 SetStreamSource 接著會將資料流程 1 系結至包含實例資料的頂點緩衝區。

當您完成轉譯實例資料時,請務必將頂點資料流程頻率重設為其預設狀態。 由於此範例使用兩個數據流,因此請設定這兩個數據流,如下所示:

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

非索引幾何效能比較

此實例樣式的主要優點是可用於非索引幾何。 雖然無法對這項技術減少每個應用程式中的轉譯時間做出單一結論,但請考慮串流至執行時間的資料量差異,以及下列轉譯順序將會減少的狀態變更數目:

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();
}

請注意,轉譯迴圈會呼叫一次。 雖然有 n 個正在串流處理的幾何實例,但幾何資料會串流處理一次。 來自實例頂點緩衝區的資料會串流處理一次。 下一個轉譯順序在功能中完全相同,但不會利用實例:

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();
}

如果沒有實例,轉譯迴圈必須由第二個迴圈包裝,才能繪製每個物件。 藉由消除第二個轉譯迴圈,您應該預期效能較佳,因為迴圈內呼叫的轉譯狀態變更較少。

整體來說,預期 (繪製索引幾何) 的索引技術比繪製非索引的幾何技術更好, (繪製非索引幾何) ,因為索引技術只會串流一份幾何資料。 請注意, DrawIndexedPrimitive 的參數尚未變更任何轉譯序列。

進階主題

實例範例