Meu código de renderização sempre foi o OpenGL. Agora, preciso oferecer suporte a uma plataforma que não possua OpenGL, portanto, preciso adicionar uma camada de abstração que inclua o OpenGL e o Direct3D 9. Suporte o Direct3D 11 posteriormente.
TL; DR: as diferenças entre o OpenGL e o Direct3D causam redundância para o programador, e o layout dos dados parece esquisito.
Por enquanto, minha API funciona um pouco assim. É assim que um shader é criado:
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");
Já existe um problema aqui: depois que um sombreador do Direct3D é compilado, não há como consultar um atributo de entrada por seu nome; aparentemente, apenas a semântica permanece significativa. É por isso GetAttribLocation
que esses argumentos extras ficam ocultos ShaderAttrib
.
Agora é assim que eu crio uma declaração de vértice e dois buffers de vértice:
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));
Outro problema: as informações VertexUsage::Position, 0
são totalmente inúteis para o back-end OpenGL / GLSL porque não se preocupam com a semântica.
Depois que os buffers de vértice foram preenchidos ou apontados para dados, este é o código de renderização:
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();
Você vê que decl
é um pouco mais do que apenas uma declaração de vértice do tipo D3D, ela também se encarrega de renderizar. Isso faz algum sentido? O que seria um design mais limpo? Ou uma boa fonte de inspiração?
fonte
Respostas:
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:
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
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:
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:
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.
fonte
Em primeiro lugar, sugiro usar
VertexBuffer<T>
para melhorar a segurança do tipo, mas em segundo lugar, acho que as diferenças entre as duas APIs são demais nesse nível. Pessoalmente, eu encapsularia totalmente os renderizadores por trás de uma interface que não lida com coisas como declarações de vértices ou configuração de atributos de sombreador.fonte
Pessoalmente, eu estabeleceria (e aplicaria) uma convenção padronizada para índices de atributos. O índice GL 0 é a posição. O índice GL 1 é a cor. O índice 2 é normal, com 3 e 4 para tangentes e binormais (se necessário). Os índices 5-7 são coordenadas de textura. Talvez 8 e 9 sejam para pesos ósseos. 10 pode ser uma segunda cor, se necessário. Se você não conseguir usar o
GL_ARB_explicit_attrib_location
GL 3.3+, também deverá estabelecer uma nomeação de atributo padronizada convenção .Dessa forma, o D3D possui convenções e o OpenGL possui convenções. Portanto, o usuário nem precisa perguntar qual é o índice de uma "posição"; eles sabem que é 0. E sua abstração sabe que 0 significa, na terra D3D
VertexUsage::Position
,.fonte