Как я могу реализовать рендер, который может рисовать много видов примитивов?

8

Это несколько связано с вопросом, который я ранее задавал относительно рисования индексированных примитивов .

Моя проблема заключалась в том, что я рисовал только один куб, когда хотел нарисовать много. Мне сказали, что проблема была в том, что я перезаписывал буферы вершин и индексов с каждым новым созданием экземпляра, Cubeи вместо этого я должен создать один в источнике, а затем нарисовать много, проходя через матрицу преобразования к шейдеру, который заставляет его появляться в разных мест. Это работало прекрасно.

Теперь у меня появилась новая проблема: как бы я нарисовал много разных типов примитивов?

Вот мой код из предыдущего вопроса:

Cube::Cube(D3DXCOLOR colour, D3DXVECTOR3 min, D3DXVECTOR3 max)
{
// create eight vertices to represent the corners of the cube
VERTEX OurVertices[] =
{
    {D3DXVECTOR3(min.x, max.y, max.z), colour},
    {D3DXVECTOR3(min.x, max.y, min.z), colour},
    {D3DXVECTOR3(min.x, min.y, max.z), colour},
    {min, colour},
    {max, colour},
    {D3DXVECTOR3(max.x, max.y, min.z), colour},
    {D3DXVECTOR3(max.x, min.y, max.z), colour},
    {D3DXVECTOR3(max.x, min.y, min.z), colour},
};

// create the vertex buffer
D3D10_BUFFER_DESC bd;
bd.Usage = D3D10_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(VERTEX) * 8;
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
bd.MiscFlags = 0;

device->CreateBuffer(&bd, NULL, &pBuffer);

void* pVoid;    // the void pointer

pBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid);    // map the vertex buffer
memcpy(pVoid, OurVertices, sizeof(OurVertices));    // copy the vertices to the buffer
pBuffer->Unmap();

// create the index buffer out of DWORDs
DWORD OurIndices[] = 
{
    0, 1, 2,    // side 1
    2, 1, 3,
    4, 0, 6,    // side 2
    6, 0, 2,
    7, 5, 6,    // side 3
    6, 5, 4,
    3, 1, 7,    // side 4
    7, 1, 5,
    4, 5, 0,    // side 5
    0, 5, 1,
    3, 7, 2,    // side 6
    2, 7, 6,
};

// create the index buffer
// D3D10_BUFFER_DESC bd;    // redefinition
bd.Usage = D3D10_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(DWORD) * 36;
bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
bd.MiscFlags = 0;

device->CreateBuffer(&bd, NULL, &iBuffer);

iBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid);    // map the index buffer
memcpy(pVoid, OurIndices, sizeof(OurIndices));    // copy the indices to the buffer
iBuffer->Unmap();

//this is simply a single call to the update method that sets up the scale, rotation
//and translation matrices, in case the cubes are static and you don't want to have to 
//call update every frame
Update(D3DXVECTOR3(1, 1, 1), D3DXVECTOR3(0, 0, 0), D3DXVECTOR3(0, 0, 0));
}

Ясно, что если бы я продублировал и изменил код, чтобы он стал другим объектом или формой, последняя инициализируемая форма перезапишет буфер вершин, не так ли?

Я использую многократные буферы вершины? Должен ли я добавить новый буфер вершин к старому и использовать соответствующие индексы для их рисования? Могу ли я сделать либо? Обе?

SirYakalot
источник

Ответы:

12

Вероятно, плохая идея создавать новые классы для каждого типа геометрии, который вы собираетесь поддерживать. Это не очень масштабируемый или поддерживаемый. Кроме того, кажется, что дизайн класса, который вам нужен, теперь объединяет задачи управления самой геометрией и данными экземпляра для этой геометрии.

Вот подход, который вы можете использовать:

Создать два класса, Meshи MeshInstance. A Meshсодержит все свойства разделяемой геометрии - в основном буфер вершин и индексный буфер. Если вы хотите, вы можете создавать вспомогательные функции, которые создают сетки, содержащие данные вершин куба (или данные вершин сферы, или все, что вам нравится). Вы должны адаптировать открытый интерфейс Meshкласса, чтобы такие вспомогательные функции могли быть реализованы как функции, не являющиеся членами, и не являющиеся друзьями.

MeshInstance, с другой стороны, должен быть построен со ссылкой на Mesh. MeshInstanceСодержит свойство отдельного объекта - это мировое преобразование и шейдерное переопределение , используемое , чтобы сделать ее, и так далее.

Таким образом, когда вы хотите создать новый куб, вы сначала получаете Meshобъект, представляющий куб, из некоторой общей библиотеки объектов примитивной сетки, которую вы создали при запуске. Затем вы создаете новый MeshInstance, назначая ему этот куб Mesh.

При рендеринге вы создаете список всех объектов, MeshInstanceкоторые хотите нарисовать, и отправляете их. Если вы сгруппируете их по Meshтекстуре или по текстуре, вы можете оптимизировать затраты на изменение состояния (т. Е. Вы одновременно рисуете все экземпляры сетки, соответствующие сетке куба, а затем все экземпляры сетки, соответствующие сетке сферы, поэтому у вас меньше SetVertexBufferвызовов на устройство D3D). Вы также можете группировать по другим состояниям, таким как текстура и шейдер.

Таким образом, вы не тратите память, дублирующую данные вершин, и гарантируете, что ваша система рендеринга может масштабироваться до любого произвольного набора примитивов, просто внедряя новые (а) функции для создания сеток программно или (б) функции для загрузки сеток из файлов конкретный формат.

Как только ваш конвейер рендеринга работает в терминах обобщенных Meshобъектов, гораздо проще адаптировать его глобально к новым методам или оптимизации.

Конкретные комментарии:

Ясно, что если бы я продублировал и изменил код, чтобы он стал другим объектом или формой, последняя инициализируемая форма перезапишет буфер вершин. не так ли?

Нет. В опубликованном вами коде единственный способ pBufferи т.п. были бы перезаписаны, если бы это была статическая переменная-член. Если вы скопировали класс оптом для создания (например) Sphereкласса, это будет новая статическая переменная. Это все еще плохая идея.

Я использую многократные буферы вершины? Должен ли я добавить новый буфер вершин к старому и использовать соответствующие индексы для их рисования? Могу ли я сделать либо? Обе?

Наивная реализация описанной выше техники включает несколько буферов (по одному для каждого набора общей геометрии). Если эта геометрия является статической, можно сохранить все это в одном (или нескольких, поскольку существует практический оптимальный предел размера буфера) буфер, чтобы минимизировать изменения состояния буфера еще больше. Это следует считать оптимизацией и оставить читателю в качестве упражнения; сначала заставьте это работать, затем беспокойтесь о том, чтобы сделать это быстро.


источник
Я знаю, что мы не должны оставлять комментарии «спасибо», но это так полезно! Спасибо!
SirYakalot
в настоящий момент pBuffer и iBuffer являются внешними. Должен ли я сделать эти экземпляры членами каждого объекта Mesh?
SirYakalot
1
Да, это хорошее место для начала.
Теперь, когда я пришел к реализации этого, у меня возникли небольшие проблемы с размышлениями о том, как это сделать, просто в той части, где вы говорите: «Если хотите, вы можете создавать вспомогательные функции, которые создают сетки, содержащие данные вершин куба (или данные вершин сферы, или что угодно еще.) Вы должны адаптировать открытый интерфейс класса Mesh, чтобы такие вспомогательные функции могли быть реализованы как функции, не являющиеся членами, и не являющиеся друзьями. " что именно вы подразумеваете под не вспомогательными, не дружественными функциями?
SirYakalot
Функция, которая не является членом класса (поэтому является глобальной функцией), которая также не объявлена ​​в качестве друга класса. Другими словами, «обычная» функция, которая манипулирует объектом-сеткой только через его открытый API.