快速繪製相同模型的多個實例

前幾天同公司同事聊天談及一個非常有趣、高效的技術,用以實現快速繪製相同模型的多個實例,比如在一個場景裏有很多樹,而這些樹都是相同的模型,只是位置、方向、大小、顏色不同,我們就可以使用這種技術提高渲染效率。

在最新的D3D9 SDK有例子演示了這個技術(Direct3D/Instancing下,如果沒有可能是因爲版本不夠新),與一般的渲染方法的區別在於,一般的方法需要爲每個模型設定一次stream source,雖然這些模型的頂點、索引都是一致的;而Instancing僅需要設置一次stream source,在一次DrawIndexedPrimitive調用中完成全部實例的繪製。Instancing例子中提供了4種實現方法,分別是:

實現方法

需要Shader Model3

需要額外的頂點緩衝

需要額外的常數寄存器

CPU限制

1.         硬件實現

2.         Shader實現

3.         Constants實現

4.         多流實現

如果用戶的顯卡非常強勁,支持Shader Model3(這意味着需要6系列以上的N卡,A卡暫時還沒有支持),使用方法1會非常高效,它的方法是這樣:通過使用多流技術,將頂點數據和每個實例的不同屬性(例如顏色、位置、大小)通過頂點緩衝一次送入顯卡,在vertex shader中讀取這些屬性,並變換頂點位置、輸出顏色就ok了,vertex shader看起來是這樣:

 

 

void VS_HWInstancing( float4 vPos : POSITION,   float3 vNormal : NORMAL, float2 vTex0 : TEXCOORD0,

                                             float4 vColor : COLOR0, float4 vBoxInstance : COLOR1,

                                             out float4 oPos : POSITION,        out float4 oColor : COLOR0,

                                             out float2 oTex0 : TEXCOORD0 )

{

         //vColorvBoxInstance保存了每個實例的屬性,vColor是顏色,vBoxInstance.xyz是位置

//vBoxInstane.w是繞y的旋轉角度

         vBoxInstance.w *= 2 * 3.1415;

         float4 vRotatedPos = vPos;

         vRotatedPos.x = vPos.x * cos(vBoxInstance.w) + vPos.z * sin(vBoxInstance.w);

         vRotatedPos.z = vPos.z * cos(vBoxInstance.w) - vPos.x * sin(vBoxInstance.w);

...... //輸出頂點位置和顏色

}

這裏需要注意的就是頂點數據可能和每個模型實例屬性的數據長度不相同,在vertex shader處理的時候爲了能正確匹配頂點和其相關的屬性數據,需要爲每個流指定不同的頻率,例如:

V( pd3dDevice->SetStreamSource( 0, g_pVBBox, 0, sizeof(BOX_VERTEX)) );

V( pd3dDevice->SetStreamSourceFreq( 0, D3DSTREAMSOURCE_INDEXEDDATA | g_NumBoxes ) );

V( pd3dDevice->SetStreamSource( 1, g_pVBInstanceData, 0, sizeof( BOX_INSTANCEDATA_POS ) ) );

V( pd3dDevice->SetStreamSourceFreq( 1, D3DSTREAMSOURCE_INSTANCEDATA | 1ul ) );

這樣便告訴vertex shader1/g_NumBoxes個頂點對應一個實例屬性數據,最後就是注意一下頂點的格式申明是這樣:

D3DVERTEXELEMENT9 g_VertexElemHardware[] =

{

    { 0, 0,     D3DDECLTYPE_FLOAT3,     D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_POSITION,  0 },

    { 0, 3 * 4, D3DDECLTYPE_FLOAT3,     D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_NORMAL,    0 },

    { 0, 6 * 4, D3DDECLTYPE_FLOAT2,     D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_TEXCOORD,  0 },

    { 1, 0,     D3DDECLTYPE_D3DCOLOR,   D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_COLOR,     0 },

    { 1, 4,     D3DDECLTYPE_D3DCOLOR,   D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_COLOR,     1 },

    D3DDECL_END()

};

方法2和方法3都是通過使用顯卡的常數寄存器來存儲實例屬性數據,不同之處在於方法2一次傳送儘量多的數據到顯卡(通過SetVectorArray函數),但如果實例數據太多,常數寄存器也不夠用,那麼可能會多次調用DrawPrimitive函數,每次傳送數據的不同部分,如果是Shader Model2的顯卡,由於擁有比Shader Model1顯卡更多的常數寄存器,所以效率會更高;方法3則是每個實例都調用DrawPrimitive函數,通過SetVector每次輸入實例數據到shader,這樣的方法已經退化到普通的方法來處理多實例渲染的地步。

下圖是4種方法在我的顯卡上繪製1000個盒子的幀率比較:

可以看出,方法1具有明顯的優勢,在支持ShaderModel2的情況下,方法2也比較理想,但在真實應用情況下不可能使用那麼多的常數寄存器;方法34因爲受CPU制約,不能最大發揮GPU性能,所以效率是最差的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章