Вы в основном сталкиваетесь с ситуацией, которая делает 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.