Menggambar Beberapa Instans Geometri secara Efisien (Direct3D 9)

Mengingat adegan yang berisi banyak objek yang menggunakan geometri yang sama, Anda dapat menggambar banyak instans geometri tersebut pada orientasi, ukuran, warna, dan sebagainya yang berbeda dengan performa yang lebih baik secara dramatis dengan mengurangi jumlah data yang perlu Anda berikan ke perender.

Ini dapat dicapai melalui penggunaan dua teknik: yang pertama untuk menggambar geometri terindeks dan yang kedua untuk geometri yang tidak diindeks. Kedua teknik menggunakan dua buffer vertex: satu untuk memasok data geometri dan satu untuk menyediakan data instans per objek. Data instans dapat berupa berbagai informasi seperti transformasi, data warna, atau data pencahayaan - pada dasarnya apa pun yang dapat Anda jelaskan dalam deklarasi puncak. Menggambar banyak instans geometri dengan teknik ini dapat secara dramatis mengurangi jumlah data yang dikirim ke perender.

Menggambar Geometri Terindeks

Buffer vertex berisi data per vertex yang ditentukan oleh deklarasi vertex. Misalkan bagian dari setiap puncak berisi data geometri, dan bagian dari setiap vertex berisi data instans per objek, seperti yang ditunjukkan dalam diagram berikut.

diagram buffer vertex untuk geometri terindeks

Teknik ini membutuhkan perangkat yang mendukung model shader vertex 3_0. Teknik ini bekerja dengan shader yang dapat diprogram tetapi tidak dengan alur fungsi tetap.

Untuk buffer vertex yang ditunjukkan di atas, berikut adalah deklarasi buffer vertex yang sesuai:

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

Deklarasi ini mendefinisikan dua buffer vertex. Deklarasi pertama (untuk aliran 0, yang ditunjukkan oleh nol di kolom 1) menentukan data geometri yang terdiri dari: data koordinat posisi, normal, tangen, binormal, dan tekstur.

Deklarasi kedua (untuk aliran 1, yang ditunjukkan oleh yang ada di kolom 1) menentukan data instans per objek. Setiap instans didefinisikan oleh empat angka titik mengambang empat komponen, dan warna empat komponen. Empat nilai pertama dapat digunakan untuk menginisialisasi matriks 4x4, yang berarti bahwa data ini akan secara unik mengukur, memosisikan, dan memutar setiap instans geometri. Empat komponen pertama menggunakan semantik koordinat tekstur yang, dalam hal ini, berarti "ini adalah angka empat komponen umum." Saat Anda menggunakan data arbitrer dalam deklarasi puncak, gunakan semantik koordinat tekstur untuk menandainya. Elemen terakhir dalam aliran digunakan untuk data warna. Ini dapat diterapkan di shader vertex untuk memberi setiap instans warna yang unik.

Sebelum merender, Anda perlu memanggil SetStreamSourceFreq untuk mengikat aliran buffer vertex ke perangkat. Berikut adalah contoh yang mengikat kedua buffer vertex:

// 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 menggunakan D3DSTREAMSOURCE_INDEXEDDATA untuk mengidentifikasi data geometri terindeks. Dalam hal ini, aliran 0 berisi data terindeks yang menjelaskan geometri objek. Nilai ini secara logis dikombinasikan dengan jumlah instans geometri yang akan digambar.

Perhatikan bahwa D3DSTREAMSOURCE_INDEXEDDATA dan jumlah instans yang akan digambar harus selalu diatur dalam nol aliran.

Dalam panggilan kedua, SetStreamSourceFreq menggunakan D3DSTREAMSOURCE_INSTANCEDATA untuk mengidentifikasi aliran yang berisi data instans. Nilai ini secara logis dikombinasikan dengan 1 karena setiap puncak berisi satu set data instans.

Dua panggilan terakhir ke SetStreamSource mengikat penunjuk buffer vertex ke perangkat.

Setelah Anda selesai merender data instans, pastikan untuk mengatur ulang frekuensi aliran vertex kembali ke status defaultnya (yang tidak menggunakan instancing). Karena contoh ini menggunakan dua aliran, atur kedua aliran seperti yang ditunjukkan di bawah ini:

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

Perbandingan Performa Geometri Terindeks

Meskipun tidak mungkin untuk membuat satu kesimpulan tentang berapa banyak teknik ini dapat mengurangi waktu render di setiap aplikasi, pertimbangkan perbedaan jumlah data yang dialirkan ke dalam runtime dan jumlah perubahan status yang akan berkurang jika Anda menggunakan teknik instancing. Urutan render ini memanfaatkan menggambar beberapa instans dari geometri yang sama:

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

Perhatikan bahwa perulangan render dipanggil sekali, data geometri dialirkan sekali, dan instans n dialirkan sekali. Urutan render berikutnya ini identik dalam fungsionalitas, tetapi tidak memanfaatkan 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();
}

Perhatikan bahwa seluruh perulangan render dibungkus oleh perulangan kedua untuk menggambar setiap objek. Sekarang data geometri dialirkan ke perender n kali (bukan sekali) dan status alur apa pun juga dapat diatur secara berlebihan untuk setiap objek yang digambar. Urutan render ini sangat mungkin menjadi jauh lebih lambat. Perhatikan juga bahwa parameter ke DrawIndexedPrimitive tidak berubah di antara dua perulangan render.

Menggambar Geometri Tidak Terindeks

Dalam Menggambar Geometri Terindeks, buffer vertex dikonfigurasi untuk menggambar beberapa instans geometri terindeks dengan efisiensi yang lebih besar. Anda juga dapat menggunakan SetStreamSourceFreq untuk menggambar geometri yang tidak diindeks. Ini membutuhkan tata letak buffer vertex yang berbeda dan memiliki batasan yang berbeda. Untuk menggambar geometri yang tidak diindeks, siapkan buffer vertex Anda seperti diagram berikut.

diagram buffer vertex untuk geometri yang tidak diindeks

Teknik ini tidak didukung oleh akselerasi perangkat keras pada perangkat apa pun. Ini hanya didukung oleh pemrosesan puncak perangkat lunak dan hanya akan berfungsi dengan shader vs_3_0 .

Karena teknik ini bekerja dengan geometri yang tidak diindeks, tidak ada buffer indeks. Seperti yang ditunjukkan diagram, buffer vertex yang berisi geometri berisi n salinan data geometri. Untuk setiap instans yang digambar, data geometri dibaca dari buffer vertex pertama dan data instans dibaca dari buffer vertex kedua.

Berikut adalah deklarasi buffer vertex yang sesuai:

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

Deklarasi ini identik dengan deklarasi yang dibuat dalam contoh geometri terindeks. Sekali lagi, deklarasi pertama (untuk aliran 0) menentukan data geometri dan deklarasi kedua (untuk aliran 1) menentukan data instans per objek. Saat Anda membuat buffer vertex pertama, pastikan untuk memuatnya dengan jumlah instans data geometri yang akan Anda gambar.

Sebelum merender, Anda perlu menyiapkan pembagi yang memberi tahu runtime cara membagi buffer vertex pertama menjadi instans n. Kemudian atur pembagi menggunakan SetStreamSourceFreq seperti ini:

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

Panggilan pertama ke SetStreamSourceFreq mengatakan bahwa aliran 0 berisi n instans puncak m. SetStreamSource kemudian mengikat aliran 0 ke buffer vertex geometri.

Dalam panggilan kedua, SetStreamSourceFreq mengidentifikasi aliran 1 sebagai sumber data instans. Parameter kedua adalah jumlah simpul di setiap objek (m). Ingat bahwa aliran data instans harus selalu dinyatakan sebagai aliran kedua. SetStreamSource kemudian mengikat aliran 1 ke buffer vertex yang berisi data instans.

Setelah Anda selesai merender data instans, pastikan untuk mengatur ulang frekuensi aliran vertex kembali ke status defaultnya. Karena contoh ini menggunakan dua aliran, atur kedua aliran seperti yang ditunjukkan di bawah ini:

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

Perbandingan Performa Geometri Tidak Terindeks

Keuntungan utama dari gaya instancing ini adalah dapat digunakan pada geometri yang tidak diindeks. Meskipun tidak mungkin untuk membuat satu kesimpulan tentang berapa banyak teknik ini dapat mengurangi waktu render di setiap aplikasi, pertimbangkan perbedaan jumlah data yang dialirkan ke dalam runtime, dan jumlah perubahan status yang akan dikurangi untuk urutan render berikut:

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

Perhatikan bahwa perulangan render dipanggil sekali. Data geometri dialirkan sekali meskipun ada n instans geometri yang dialirkan. Data dari buffer vertex instans dialirkan sekali. Urutan render berikutnya ini identik dalam fungsionalitas, tetapi tidak memanfaatkan 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();
}

Tanpa instancing, perulangan render perlu dibungkus oleh perulangan kedua untuk menggambar setiap objek. Dengan menghilangkan perulangan render kedua, Anda harus mengharapkan performa yang lebih baik karena lebih sedikit perubahan status render yang dipanggil di dalam perulangan.

Secara keseluruhan, wajar untuk mengharapkan teknik terindeks (Menggambar Geometri Terindeks) berkinerja lebih baik daripada teknik yang tidak diindeks (Menggambar Geometri Non-Terindeks) karena teknik terindeks hanya mengalirkan satu salinan data geometri. Perhatikan bahwa parameter ke DrawIndexedPrimitive tidak berubah untuk salah satu urutan render.

Topik tingkat lanjut

Sampel Instancing