Мой код рендеринга всегда был 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, оно также заботится о рендеринге. Имеет ли это смысл вообще? Каким будет более чистый дизайн? Или хороший источник вдохновения?
источник
Ответы:
Вы в основном сталкиваетесь с ситуацией, которая делает 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 и писать шейдерные пакеты, такие как:
Написание минимального парсера для этого тривиально (например, используйте TinyXML). Позвольте вашей библиотеке шейдеров загрузить этот пакет, выберите подходящий профиль для вашего текущего целевого рендерера и скомпилируйте шейдеры.
Также обратите внимание, что если вы предпочитаете, вы можете оставить источник внешним по отношению к определению шейдера, но при этом иметь файл. Просто поместите имена файлов вместо источника в исходные элементы. Это может быть полезно, если вы планируете, например, прекомпиляцию шейдеров.
Конечно, сейчас труднее всего разобраться с GLSL и его недостатками. Проблема в том, что вам нужно привязать расположение атрибутов к чему-то похожему на семантику HLSL. Это можно сделать, определив эту семантику в своем API, а затем используя glBindAttribLocation перед связыванием профиля GLSL. Каркас вашего шейдерного пакета может обрабатывать это явно, без необходимости для вашего графического API раскрывать детали.
Вы можете сделать это, расширив вышеуказанный формат XML некоторыми новыми элементами в профиле GLSL, чтобы явно указать расположение атрибутов, например
Код пакета вашего шейдера будет читать все элементы attrib в XML, извлекать из них имя и семантику, искать предварительно определенный индекс атрибута для каждой семантики, а затем автоматически вызывать glBindAttribLocation для вас при связывании шейдера.
Конечным результатом является то, что ваш API теперь может чувствовать себя намного лучше, чем ваш старый код GL, и даже немного чище, чем D3D11:
Также обратите внимание, что вам не нужен формат пакета шейдера. Если вы хотите, чтобы все было просто, вы можете просто использовать функцию loadShader (const char * name), которая автоматически захватывает GLSL-файлы name.vs и name.fs в режиме GL, компилирует и связывает их. Однако вы обязательно захотите метаданные этого атрибута. В простом случае вы можете дополнить свой код GLSL специальными удобными для анализа комментариями, такими как:
При разборе комментариев вы можете получить столько, сколько вам удобно. Более чем несколько профессиональных движков зайдут так далеко, что сделают небольшие языковые расширения, которые они будут анализировать и модифицировать, даже такие, как простое добавление семантических объявлений в стиле HLSL. Если вы хорошо разбираетесь в разборе, вы сможете надежно найти эти расширенные объявления, извлечь дополнительную информацию, а затем заменить текст на код, совместимый с GLSL.
Независимо от того, как вы это сделаете, короткая версия должна дополнить ваш GLSL семантической информацией об отсутствующем атрибуте, и ваша абстракция загрузчика шейдеров будет иметь дело с вызовом glBindAttribLocation, чтобы исправить положение и сделать их более похожими на простые и эффективные современные версии GLSL и HLSL.
источник
Во-первых, я бы предложил использовать
VertexBuffer<T>
для повышения безопасности типов, но во-вторых, я думаю, что различия между двумя API слишком велики на этом уровне. Лично я бы полностью инкапсулировал средства визуализации за интерфейсом, который не имеет дело с такими вещами, как объявления вершин или установка атрибутов шейдеров.источник
Лично я бы установил (и обеспечил) стандартное соглашение для индексов атрибутов. GL index 0 - это позиция. GL index 1 - это цвет. Индекс 2 является нормальным, с 3 и 4 для касательных и бинормалей (при необходимости). Индекс 5-7 - это координаты текстуры. Может быть 8 и 9 для веса костей. 10 может быть вторым цветом, если это необходимо. Если вы не можете использовать
GL_ARB_explicit_attrib_location
GL 3.3+, вам также следует установить стандартное соглашение об именах атрибутов .Таким образом, в D3D есть соглашения, а в OpenGL - соглашения. Таким образом, пользователю даже не нужно спрашивать, что такое индекс «позиции»; они знают , что это 0. И ваша абстракция знает , что 0 означает, в D3D земле
VertexUsage::Position
.источник