Estou tentando criar um mecanismo de jogo 2D usando o OpenGL ES 2.0 (iOS por enquanto). Eu escrevi a camada Application no Objective C e um RendererGLES20 independente em C ++. Nenhuma chamada específica de GL é feita fora do renderizador. Está funcionando perfeitamente.
Mas tenho alguns problemas de design ao usar shaders. Cada sombreador possui seus próprios atributos e uniformes que precisam ser configurados imediatamente antes da chamada de desenho principal (neste caso, glDrawArrays). Por exemplo, para desenhar alguma geometria, eu faria:
void RendererGLES20::render(Model * model)
{
// Set a bunch of uniforms
glUniformMatrix4fv(.......);
// Enable specific attributes, can be many
glEnableVertexAttribArray(......);
// Set a bunch of vertex attribute pointers:
glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, m->pCoords);
// Now actually Draw the geometry
glDrawArrays(GL_TRIANGLES, 0, m->vertexCount);
// After drawing, disable any vertex attributes:
glDisableVertexAttribArray(.......);
}
Como você pode ver, esse código é extremamente rígido. Se eu usasse outro shader, digamos efeito cascata, eu precisaria passar uniformes extras, atributos de vértices etc. Em outras palavras, eu teria que alterar o código-fonte de renderização do RendererGLES20 apenas para incorporar o novo shader.
Existe alguma maneira de tornar o objeto shader totalmente genérico? Como E se eu apenas quiser alterar o objeto shader e não me preocupar com a recompilação da fonte do jogo? Qualquer maneira de tornar o renderizador independente de uniformes e atributos etc? Mesmo que precisamos passar dados para uniformes, qual é o melhor lugar para fazer isso? Classe modelo? A classe do modelo está ciente dos atributos e uniformes específicos do shader?
A seguir, mostra a classe Ator:
class Actor : public ISceneNode
{
ModelController * model;
AIController * AI;
};
Classe do controlador de modelo: class ModelController {class IShader * shader; int textureId; tonalidade vec4; alfa flutuante; struct Vertex * vertexArray; };
A classe Shader contém apenas o objeto shader, compilando e vinculando sub-rotinas etc.
Na classe Game Logic, na verdade, estou processando o objeto:
void GameLogic::update(float dt)
{
IRenderer * renderer = g_application->GetRenderer();
Actor * a = GetActor(id);
renderer->render(a->model);
}
Observe que, embora o Actor estenda o ISceneNode, ainda não comecei a implementar o SceneGraph. Farei isso assim que resolver esse problema.
Alguma idéia de como melhorar isso? Padrões de design relacionados etc?
Obrigado por ler a pergunta.
fonte
Respostas:
É possível tornar seu sistema de sombreador mais orientado a dados, para que você não tenha muito código específico para sombreamento para formatos de uniformes e vértices, mas defina-os programaticamente com base nos metadados anexados aos sombreadores.
Primeiro, o aviso: um sistema orientado a dados pode facilitar a adição de novos sombreadores, mas, por outro lado, possui custos em termos de maior complexidade do sistema, o que dificulta a manutenção e a depuração. Portanto, é uma boa idéia pensar com cuidado em quanto o direcionamento de dados será bom para você (para um projeto pequeno, a resposta pode ser "nenhuma") e não tente criar um sistema excessivamente generalizado.
Ok, vamos falar primeiro sobre os formatos de vértices (atributos). Você pode criar uma descrição de dados criando uma estrutura que contenha os dados a serem passados
glVertexAttribPointer
- o índice, tipo, tamanho etc. de um único atributo - e possuindo uma matriz dessas estruturas para representar todo o formato do vértice. Dadas essas informações, você pode configurar programaticamente todo o estado GL relacionado aos atributos do vértice.De onde vêm os dados para preencher essa descrição? Conceitualmente, acho que a maneira mais limpa é fazer com que ele pertença ao shader. Ao criar os dados de vértice para uma malha, você pesquisaria qual shader é usado na malha, localizaria o formato de vértice exigido por esse shader e construiria o buffer de vértice de acordo. Você só precisa de uma maneira para cada sombreador especificar o formato de vértice que espera.
Existem várias maneiras de fazer isso; por exemplo, você pode ter um conjunto padrão de nomes para os atributos no shader ("attrPosition", "attrNormal" etc.), além de algumas regras codificadas como "position is 3 floats". Em seguida, use
glGetAttribLocation
ou semelhante para consultar quais atributos o shader usa e aplique as regras para criar o formato de vértice. Outra maneira é ter um trecho XML definindo o formato, incorporado em um comentário na fonte do sombreador e extraído por suas ferramentas ou algo assim.Para uniformes, se você pode usar o OpenGL 3.1 ou posterior, é uma boa ideia usar objetos de buffer uniformes (o equivalente ao OpenGL dos buffers constantes do D3D). Infelizmente, o GL ES 2.0 não possui, então os uniformes precisam ser manuseados individualmente. Uma maneira de fazer isso seria criar uma estrutura contendo a localização uniforme para cada parâmetro que você deseja definir - a matriz da câmera, a potência especular, a matriz mundial etc. As localizações dos amostradores também poderiam estar aqui. Essa abordagem depende da existência de um conjunto padrão de parâmetros compartilhados em todos os shaders. Nem todo shader precisa usar todos os parâmetros, mas todos os parâmetros teriam que estar nessa estrutura.
Cada sombreador teria uma instância dessa estrutura e, quando você carrega um sombreador, consultaria os locais de todos os parâmetros, usando
glGetUniformLocation
nomes padronizados. Então, sempre que você precisar definir um uniforme a partir do código, poderá verificar se ele está presente nesse sombreador e apenas procurar sua localização e configurá-lo.fonte