Você está basicamente enfrentando o tipo de situação que torna o NVIDIA Cg um software tão atraente (além do fato de que ele não suporta o GL | ES, que você disse que está usando).
Observe também que você realmente não deve usar glGetAttribLocation. Essa função é ruim desde os dias iniciais do GLSL, antes que o pessoal encarregado do GL realmente começasse a entender como uma boa linguagem de sombreamento deveria funcionar. Ele não foi descontinuado, pois tem uso ocasional, mas, em geral, prefere glBindAttibLocation ou a extensão explícita de localização de atributos (principal no GL 3.3+).
Lidar com as diferenças nas linguagens de sombreador é de longe a parte mais difícil de portar software entre GL e D3D. Os problemas de API que você está enfrentando em relação à definição de layout de vértice também podem ser vistos apenas como um problema de linguagem de sombreador, pois as versões GLSL anteriores à 3.30 não suportam a localização explícita do atributo (semelhante em espírito à semântica de atributos no HLSL) e versões GLSL anteriores 4.10 O iirc não suporta ligações uniformes explícitas.
A melhor "abordagem" é ter uma biblioteca de linguagem de sombreamento de alto nível e formato de dados que encapsule seus pacotes de sombreador. NÃO basta alimentar um monte de GLSL / HLSL bruto para uma classe Shader fina e esperar poder criar qualquer tipo de API sã.
Em vez disso, coloque seus shaders em um arquivo. Envolva-os em um pouco de metadados. Você pode usar XML e escrever pacotes shader como:
<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>
Escrever um analisador mínimo para isso é trivial (basta usar o TinyXML, por exemplo). Deixe sua biblioteca de sombreadores carregar esse pacote, selecione o perfil apropriado para seu renderizador de destino atual e compile os sombreadores.
Observe também que, se preferir, você pode manter a fonte externa à definição de sombreador, mas ainda assim possui o arquivo. Basta colocar nomes de arquivos em vez de fonte nos elementos de origem. Isso pode ser benéfico se você planeja pré-compilar shaders, por exemplo.
A parte difícil agora, é claro, é lidar com o GLSL e suas deficiências. O problema é que você precisa vincular os locais dos atributos a algo semelhante à semântica do HLSL. Isso pode ser feito definindo essas semânticas em sua API e usando glBindAttribLocation antes de vincular o perfil GLSL. Sua estrutura de pacotes de sombreador pode lidar com isso explicitamente, sem a necessidade de sua API de gráficos expor os detalhes.
Você pode fazer isso estendendo o formato XML acima com alguns novos elementos no perfil GLSL para especificar explicitamente os locais dos atributos, por exemplo
<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>
O código do pacote do shader lê todos os elementos de atributo no XML, pega o nome e a semântica a partir deles, consulta o índice de atributo predefinido para cada semântica e, em seguida, chama automaticamente glBindAttribLocation para você ao vincular o shader.
O resultado final é que sua API agora pode parecer muito melhor do que provavelmente seu código GL antigo já pareceu, e até um pouco mais limpo do que o D3D11 permitiria:
// 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));
Observe também que você não precisa estritamente do formato do pacote shader. Se você quiser manter as coisas simples, você está livre para ter apenas um tipo de função loadShader (const char * name) que pega automaticamente os arquivos GLSL name.vs e name.fs GLSL no modo GL e os compila e vincula. No entanto, você absolutamente desejará os metadados desse atributo. No caso simples, você pode aumentar seu código GLSL com comentários especiais fáceis de analisar, como:
#version 150
/// ATTRIB(inPosition,POSITION)
in vec4 inPosition;
/// ATTRIB(inColor,COLOR0)
in vec4 inColor;
out vec4 vColor
void main() {
vColor = inColor;
gl_Position = inPosition;
}
Você pode se sentir mais à vontade na análise de comentários. Mais do que alguns mecanismos profissionais chegarão ao ponto de criar pequenas extensões de linguagem que eles analisam e modificam, inclusive, como simplesmente adicionar declarações semânticas no estilo HLSL. Se seu conhecimento de análise for robusto, você poderá encontrar com segurança essas declarações estendidas, extrair informações adicionais e substituir o texto pelo código compatível com GLSL.
Não importa como você faz isso, a versão curta é aumentar seu GLSL com as informações semânticas dos atributos ausentes e fazer com que a abstração do shader loader lide com glBindAttribLocation para corrigir as coisas e torná-las mais parecidas com as versões modernas e fáceis e eficientes do GLSL e do HLSL.