Что является лучшим уровнем абстракции для управления данными вершин D3D9 и OpenGL?

8

Мой код рендеринга всегда был OpenGL. Теперь мне нужно поддерживать платформу, которая не имеет OpenGL, поэтому мне нужно добавить слой абстракции, который оборачивает OpenGL и Direct3D 9. Я буду поддерживать Direct3D 11 позже.

TL; DR: различия между OpenGL и Direct3D вызывают избыточность для программиста, а расположение данных кажется нестабильным.

На данный момент мой API работает примерно так. Вот как создается шейдер:

Shader *shader = Shader::Create(
    " ... GLSL vertex shader ... ", " ... GLSL pixel shader ... ",
    " ... HLSL vertex shader ... ", " ... HLSL pixel shader ... ");
ShaderAttrib a1 = shader->GetAttribLocation("Point", VertexUsage::Position, 0);
ShaderAttrib a2 = shader->GetAttribLocation("TexCoord", VertexUsage::TexCoord, 0);
ShaderAttrib a3 = shader->GetAttribLocation("Data", VertexUsage::TexCoord, 1);
ShaderUniform u1 = shader->GetUniformLocation("WorldMatrix");
ShaderUniform u2 = shader->GetUniformLocation("Zoom");

Здесь уже есть проблема: после компиляции шейдера Direct3D невозможно запросить входной атрибут по его имени; очевидно, только семантика остается значимой. Вот почему GetAttribLocationесть эти дополнительные аргументы, которые скрыты в ShaderAttrib.

Теперь вот как я создаю объявление вершины и два буфера вершины:

VertexDeclaration *decl = VertexDeclaration::Create(
        VertexStream<vec3,vec2>(VertexUsage::Position, 0,
                                VertexUsage::TexCoord, 0),
        VertexStream<vec4>(VertexUsage::TexCoord, 1));

VertexBuffer *vb1 = new VertexBuffer(NUM * (sizeof(vec3) + sizeof(vec2));
VertexBuffer *vb2 = new VertexBuffer(NUM * sizeof(vec4));

Другая проблема: информация VertexUsage::Position, 0совершенно бесполезна для бэкэнда OpenGL / GLSL, потому что она не заботится о семантике.

Как только буферы вершин заполнены данными или наведены на них, это код рендеринга:

shader->Bind();
shader->SetUniform(u1, GetWorldMatrix());
shader->SetUniform(u2, blah);
decl->Bind();
decl->SetStream(vb1, a1, a2);
decl->SetStream(vb2, a3);
decl->DrawPrimitives(VertexPrimitive::Triangle, NUM / 3);
decl->Unbind();
shader->Unbind();

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

Сэм Хоцевар
источник
На какую версию OpenGL вы ориентируетесь?
Никол Болас
@NicolBolas На данный момент я использую OpenGL 2.1 и OpenGL ES 2.0 и планирую поддерживать OpenGL 3.3 или 4.0, но я не решил, откажусь ли я от поддержки предыдущих версий. Моя текущая проблема заключается в том, что я также использую подмножество старых OpenGL на PS3, что является неоптимальным, но довольно удобным…
Сэм Хочевар,
Вы, вероятно, уже знаете об этом, но проверьте источник Ogre, чтобы увидеть, как они это сделали ogre3d.org
Aralox
4
@Aralox: OGRE - беспорядок, зараженный Синглтоном, и я бы никогда никому не посоветовал следовать его замыслу.
DeadMG

Ответы:

8

Вы в основном сталкиваетесь с ситуацией, которая делает NVIDIA Cg таким привлекательным программным обеспечением (кроме того факта, что оно не поддерживает GL | ES, который, как вы сказали, вы используете).

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

Обработка различий в языках шейдеров - самая сложная часть переноса программного обеспечения между GL и D3D. Проблемы API, с которыми вы сталкиваетесь в отношении определения вершинного макета, также могут рассматриваться как проблема языка шейдеров, поскольку версии GLSL до 3.30 не поддерживают явное расположение атрибутов (сходное по духу с семантикой атрибутов в HLSL) и версии GLSL ранее. 4.10. Iirc не поддерживает явные унифицированные привязки.

«Лучший» подход - иметь высокоуровневую библиотеку языка шейдинга и формат данных, который инкапсулирует ваши пакеты шейдеров. НЕ просто передавайте кучу необработанных GLSL / HLSL тонкому классу шейдеров и не ожидайте, что сможете найти любой нормальный API.

Вместо этого поместите ваши шейдеры в файл. Оберните их в немного метаданных. Вы можете использовать XML и писать шейдерные пакеты, такие как:

<shader name="bloom">
  <profile type="glsl" version="1.30">
    <source type="vertex"><![CDATA[
      glsl vertex shader code goes here
    ]]></source>
    <source type="fragment"><![CDATA[
      glsl fragment shader code goes here
    ]]></source>
  </profile>
  <profile type="hlsl" version="sm3">
    <source type="fx"><![CDATA[
      hlsl effects code goes here
      you could also split up the source elements for hlsl
    ]]></source>
  </profile>
</shader>

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

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

Конечно, сейчас труднее всего разобраться с GLSL и его недостатками. Проблема в том, что вам нужно привязать расположение атрибутов к чему-то похожему на семантику HLSL. Это можно сделать, определив эту семантику в своем API, а затем используя glBindAttribLocation перед связыванием профиля GLSL. Каркас вашего шейдерного пакета может обрабатывать это явно, без необходимости для вашего графического API раскрывать детали.

Вы можете сделать это, расширив вышеуказанный формат XML некоторыми новыми элементами в профиле GLSL, чтобы явно указать расположение атрибутов, например

<shader name="bloom">
  <profile type="glsl" version="1.30">
    <attrib name="inPosition" semantic="POSITION"/>
    <attrib name="inColor" semantic="COLOR0"/>
    <source type="vertex"><![CDATA[
      #version 150
      in vec4 inPosition;
      in vec4 inColor;

      out vec4 vColor;

      void main() {
        vColor = inColor;
        gl_Position = position;
      }
    ]]></source>
  </profile>
</shader>

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

Конечным результатом является то, что ваш API теперь может чувствовать себя намного лучше, чем ваш старый код GL, и даже немного чище, чем D3D11:

// simple example, easily improved
VertexLayout layout = api->createLayout();
layout.bind(gfx::POSITION, buffer0, gfx::FLOATx4, sizeof(Vertex), offsetof(Vertex, position));
layout.bind(gfx::COLOR0, buffer0, gfx::UBYTEx4, sizeof(Vertex), offsetof(Vertex, color));

Также обратите внимание, что вам не нужен формат пакета шейдера. Если вы хотите, чтобы все было просто, вы можете просто использовать функцию loadShader (const char * name), которая автоматически захватывает GLSL-файлы name.vs и name.fs в режиме GL, компилирует и связывает их. Однако вы обязательно захотите метаданные этого атрибута. В простом случае вы можете дополнить свой код GLSL специальными удобными для анализа комментариями, такими как:

#version 150

/// ATTRIB(inPosition,POSITION)
in vec4 inPosition;
/// ATTRIB(inColor,COLOR0)
in vec4 inColor;

out vec4 vColor

void main() {
  vColor = inColor;
  gl_Position = inPosition;
}

При разборе комментариев вы можете получить столько, сколько вам удобно. Более чем несколько профессиональных движков зайдут так далеко, что сделают небольшие языковые расширения, которые они будут анализировать и модифицировать, даже такие, как простое добавление семантических объявлений в стиле HLSL. Если вы хорошо разбираетесь в разборе, вы сможете надежно найти эти расширенные объявления, извлечь дополнительную информацию, а затем заменить текст на код, совместимый с GLSL.

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

Шон Миддледич
источник
Спасибо за чрезвычайно полный ответ. Дополнительное предложение о семантических комментариях просто, но имеет большой смысл!
Сэм Хоцевар
Я наконец принимаю ваш ответ, даже если другие оказались очень полезными. Я потратил много времени на размышления о том, как это сделать правильно, и закончил тем, что написал полный анализатор GLSL / HLSL, который помогает мне эмулировать явное расположение атрибутов, когда это не поддерживается.
Сэм Хоцевар
5

Во-первых, я бы предложил использовать VertexBuffer<T>для повышения безопасности типов, но во-вторых, я думаю, что различия между двумя API слишком велики на этом уровне. Лично я бы полностью инкапсулировал средства визуализации за интерфейсом, который не имеет дело с такими вещами, как объявления вершин или установка атрибутов шейдеров.

DeadMG
источник
Откомандирован; ваш уровень абстракции в настоящее время находится на слишком низком уровне и должен быть выше, чтобы действительно справляться с различиями API.
Максимус Минимус
2

Лично я бы установил (и обеспечил) стандартное соглашение для индексов атрибутов. GL index 0 - это позиция. GL index 1 - это цвет. Индекс 2 является нормальным, с 3 и 4 для касательных и бинормалей (при необходимости). Индекс 5-7 - это координаты текстуры. Может быть 8 и 9 для веса костей. 10 может быть вторым цветом, если это необходимо. Если вы не можете использовать GL_ARB_explicit_attrib_locationGL 3.3+, вам также следует установить стандартное соглашение об именах атрибутов .

Таким образом, в D3D есть соглашения, а в OpenGL - соглашения. Таким образом, пользователю даже не нужно спрашивать, что такое индекс «позиции»; они знают , что это 0. И ваша абстракция знает , что 0 означает, в D3D земле VertexUsage::Position.

Николь Болас
источник