Какова хорошая структура кода для api-независимой обработки вершин?
В настоящее время я работаю над 3D-движком с использованием C#, и я столкнулся с небольшой загадкой. Я разобрался со своим циклом отрисовки, у меня есть отличная архитектура подключаемых модулей и системы управления контентом, и даже запланированный конвейер материалов. Затем движок планируется использовать DirectX и OpenGL (через плагины 'рендерера') вместе с программируемым конвейером обоих API.
В любом случае, в начале этой недели я начал работать над абстрактным слоем движков для обработки вершин (я боюсь этого уже несколько недель). И, как некоторые из вас знают, обработка вершин между графическими API-интерфейсами совсем не связана или не одинакова. Ну, вроде как;), но не то же самое. В OpenGL обработка вершин очень проста: вы создаете собственную структуру вершин, отправляете ее в графический процессор, а затем позволяете вашим шейдерам обрабатывать все остальное. Это идеально подходит для гибкого графического конвейера, OpenGL не требуется знать, какие элементы содержатся в каждой вершине. С другой стороны, DirectX требует, чтобы мы создавали объявления для каждой структуры вершин и затем отправляли их в графический процессор.
Проблема заключается в том, что я не буду знать, какой тип структуры вершин передается, и я определенно хотел бы избежать создания слоя абстракции, который включает объявление каждого элемента вершины через перечисления и некоторый абстрактный класс 'VertexDeclaration'; это может вызвать некоторые проблемы:
1) Получение элементов вершины будет болезненно, если не сказать больше. Я мог бы использовать некоторый VertexSemantic и запросить позиции вершины "a - z", но при обработке большого количества вершин для чего-то вроде скелетной анимации это может привести к большим накладным расходам.
2) Не очень удобно, учитывая, что основное внимание двигателей уделяется "новичкам". Я хотел бы, чтобы пользователи могли создавать собственные вершины и сеточные буферы, без необходимости объявлять тонну объектов, что потребовало бы драгоценного времени на разработку.
3) больше?
Теперь я мог бы что-то сделать с атрибутами и затем создать объявления для структур вершин в DirectX-рендерере. Например, продолжайте и создайте некоторые перечисления:
// for getting the format layout of the element
public enum ElementFormat
{
Float, Float2, Float3, Byte, etc, etc
}
// for determining the 'usage'
// (here is 'another' where DirectX limits vertex structures ><)
public enum ElementUsage
{
Position, Normal, Color, TextureCoord, etc, etc
}
Теперь я могу создать атрибут, который пользователи могут применять к "полям" каждого элемента в своей структуре вершин:
public class VertexElementAttribute : Attribute
{
#region Properties
/// <summary>
/// Gets the total size (in bytes) of the element.
/// </summary>
public int Size
{
get;
set;
}
/// <summary>
/// Gets the number of values contained with-in the element.
/// </summary>
public int Count
{
get;
set;
}
/// <summary>
/// Gets the type semantic of the element.
/// </summary>
public ElementType Type
{
get;
set;
}
/// <summary>
/// Gets the usage semantic of the element.
/// </summary>
public ElementUsage Usage
{
get;
set;
}
#endregion
#region Init
/// <summary>
/// Creates a new vertex element attribute.
/// </summary>
/// <param name="count">The number of values contained within the element.</param>
/// <param name="size">The total size (in bytes) of the element.</param>
/// <param name="type">The type semantic of the element.</param>
/// <param name="usage">The usage semantic of the element.</param>
public VertexElementAttribute(int count, int size, ElementType type, ElementUsage usage)
{
Count = count;
Size = size;
Type = type;
Usage = usage;
}
#endregion
}
Пример того, как может выглядеть пользовательская структура вершин:
public struct VertexPositionColor
{
[VertexElement(3, sizeof(Vector3), ElementType.FLOAT3, ElementUsage.POSITION)]
public Vector3 Xyz;
[VertexElement(4, sizeof(Color), ElementType.FLOAT4, ElementUsage.COLOR)]
public Color Rgba;
... etc
}
Это было бы хорошо. В плагине DirectX (средство визуализации) я мог просто создать служебный класс, который может создавать семантику для каждого типа структуры, а затем кэшировать данные, чтобы объявления не создавались заново для каждой вершины.
Я мог бы даже добавить значение перечисления NONE в ELementUsage, чтобы пользовательские значения могли использоваться для всего, что когда-либо означало... но опять же, они работали бы только в OpenGL, потому что DirectX требует, чтобы вы отмечали каждую вершину... если я что-то не имею отсутствует.
Мои вопросы):
Есть ли лучший способ сделать это (кроме использования attirbutes)? Есть ли способ избежать использования VertexDeclarations в DirectX? Есть ли что-то, что вы можете не понять о "моем" вопросе?
РЕДАКТИРОВАТЬ:
Проблема с использованием атрибутов будет получать данные элемента из каждой вершины. Скажем, я хотел получить позиции каждой вершины в сетчатом буфере. Поскольку я работал с атрибутами, я не могу просто сделать "vertex.Position", мне нужно было бы создать служебный метод, который мог бы извлекать ссылку на поле из структуры вершины, например "Utility.GetElement(vertex, ElementUsage.POSITION)"., Этот метод должен будет использовать отражение, чтобы сначала найти атрибут, а затем вернуть ссылку на значение поля. Установка значения даже не будет (я думаю) возможной?
Другой способ - создать интерфейс IElement и реализовать каждый элемент (Positon, Normal и т. Д.). Интерфейс может иметь свойство Name, которое я могу вернуть непосредственно в структуре унаследованного элемента, подобно тому, как свойство Name PositionElements просто вернет "Positon".
Затем я мог бы хранить массив IElement внутри структуры Vertex, содержащей такие методы, как AddElement(IElement), GetElement(имя строки), GetElement(int index), Insert, Replace и т. Д. Я бы реализовал все элементы, известные DirectX, чтобы Плагин рендерера может анализировать структуру вершин, чтобы создать массив объявлений вершин.
Проблема в том, что я не уверен, можно ли использовать массив '[]' в качестве данных элемента вершины. Например, какие еще байты содержит массив (если есть), который мешал бы мне передавать структуру Vertex (содержит массив IElement) непосредственно в DirectX, а затем в графический процессор?
Реализация этого способа была бы абсолютно идеальной для того, для чего мне это нужно. Другой вопрос: могут ли наследуемые типы IElement (элементы) быть классом, или значения элементов должны быть типами значений?
2 ответа
Прошло много времени с тех пор, как я занимался DirectX или OpenGL, поэтому примите мой совет с недоверием, но я помню, что делал что-то подобное некоторое время назад.
Я думаю, что сделал что-то вроде этого:
var graphicsStream = new GraphicsStream();
var elements = graphicsStream.Create<Vector3>(Usage.Position);
graphicsStream.Create<Color>(Usage.Color);
graphicsStream.Create<Quaternion>(Usage.Fribble);
elements.SetData(new[] { new Vector3(), new Vector3() });
var vertexFormat = graphicsStream.GetFormat();
graphicsStream.Validate(); // ensure all the streams have the same length
// get a particular element by type
var p = graphicsStream.GetData(Usage.Position, 1);
У вас будет графический поток, который представляет собой набор типизированных наборов данных с примененным применением. Из этого вы можете создать соответствующий формат вершины. API позволит вам изменять отдельные элементы или загружать и заменять весь буфер вершин за один раз.
Самым большим недостатком является то, что у вас нет одной структуры, которая представляет собой "столбец" в вашей структуре вершин.
Я не знаю, если это то, что вы ищете. Почему такой дизайн не подходит?
Вы могли бы взглянуть на то, как OGRE справляется с этим. В частности , документация системы рендеринга о VertexElement, а также классы буфера вершин. OGRE - это C++, но я уверен, что вы сможете легко преобразовать шаблоны проектирования в C#.