Эффективный способ рисования нескольких экземпляров геометрии (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 для идентификации индексированных геометрических данных. В этом случае stream 0 содержит индексированные данные, описывающие геометрию объекта. Это значение логически комбинируется с количеством экземпляров геометрии для отрисовки.
Обратите внимание, что 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.
Так как этот метод работает с неиндексированной геометрией, буфер индекса отсутствует. Как показано на схеме, буфер вершин, содержащий геометрию, содержит 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) определяет данные экземпляра каждого объекта. При создании первого буфера вершин обязательно загрузите его с числом экземпляров данных геометрии, которые вы будете рисовать.
Перед отрисовкой необходимо настроить разделитель, который сообщает среде выполнения, как разделить первый буфер вершин на 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 говорит, что stream 0 содержит n экземпляров вершин m. 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 не изменились для любой последовательности отрисовки.
Связанные разделы