Estoy tratando de hacer un motor de juego en 2D usando OpenGL ES 2.0 (iOS por ahora). He escrito Application layer en Objective C y un RendererGLES20 independiente en C ++. No se realiza una llamada específica de GL fuera del renderizador. Funciona perfectamente
Pero tengo algunos problemas de diseño cuando uso sombreadores. Cada sombreador tiene sus propios atributos y uniformes únicos que deben establecerse justo antes de la llamada del sorteo principal (glDrawArrays en este caso). Por ejemplo, para dibujar algo de geometría haría:
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 puede ver, este código es extremadamente rígido. Si tuviera que usar otro sombreador, digamos efecto ondulación, tendría que pasar uniformes adicionales, atributos de vértice, etc. En otras palabras, tendría que cambiar el código fuente de renderizado RendererGLES20 solo para incorporar el nuevo sombreador.
¿Hay alguna forma de hacer que el objeto sombreador sea totalmente genérico? ¿Qué sucede si solo quiero cambiar el objeto del sombreador y no preocuparme por la compilación de la fuente del juego? ¿Alguna forma de hacer que el renderizador sea agnóstico de uniformes y atributos, etc.? A pesar de que necesitamos pasar datos a los uniformes, ¿cuál es el mejor lugar para hacerlo? Clase de modelo? ¿La clase de modelo conoce los uniformes y atributos específicos del sombreador?
A continuación se muestra la clase de actor:
class Actor : public ISceneNode
{
ModelController * model;
AIController * AI;
};
Clase de controlador de modelo: class ModelController {class IShader * shader; int textureId; tinte vec4; flotador alfa; struct Vertex * vertexArray; };
La clase de sombreador solo contiene el objeto de sombreador, compilando y vinculando subrutinas, etc.
En la clase Game Logic, en realidad estoy renderizando el objeto:
void GameLogic::update(float dt)
{
IRenderer * renderer = g_application->GetRenderer();
Actor * a = GetActor(id);
renderer->render(a->model);
}
Tenga en cuenta que aunque Actor extiende ISceneNode, todavía no he comenzado a implementar SceneGraph. Lo haré tan pronto como resuelva este problema.
¿Alguna idea de cómo mejorar esto? ¿Patrones de diseño relacionados, etc.?
Gracias por leer la pregunta.
fuente
Respuestas:
Es posible hacer que su sistema de sombreado esté más basado en datos, de modo que no tenga tanto código específico de sombreador para uniformes y formatos de vértices, sino que los configure programáticamente en función de los metadatos adjuntos a los sombreadores.
Primero, el descargo de responsabilidad: un sistema basado en datos puede facilitar la adición de nuevos sombreadores, pero por otro lado, conlleva costos en términos de una mayor complejidad del sistema, lo que dificulta su mantenimiento y depuración. Por lo tanto, es una buena idea pensar detenidamente sobre la cantidad de datos que serán buenos para usted (para un proyecto pequeño, la respuesta podría ser "ninguno"), y no intente construir un sistema demasiado generalizado.
Bien, hablemos primero de los formatos de vértices (atributos). Puede crear una descripción de datos haciendo una estructura que contenga los datos a los que pasar
glVertexAttribPointer
- el índice, tipo, tamaño, etc. de un solo atributo - y que tenga una matriz de esas estructuras para representar el formato de vértice completo. Dada esta información, puede configurar mediante programación todos los estados GL relacionados con los atributos de vértice.¿De dónde provienen los datos para completar esta descripción? Conceptualmente, creo que la forma más limpia es hacer que pertenezca al sombreador. Cuando construye los datos de vértice para una malla, debe buscar qué sombreador se usa en la malla, encontrar el formato de vértice requerido por ese sombreador y construir el búfer de vértice en consecuencia. Solo necesita alguna forma para que cada sombreador especifique el formato de vértice que espera.
Hay una variedad de formas de hacer esto; por ejemplo, podría tener un conjunto estándar de nombres para los atributos en el sombreador ("attrPosition", "attrNormal", etc.) más algunas reglas codificadas como "posición es 3 flotantes". Luego usa
glGetAttribLocation
o similar para consultar qué atributos usa el sombreador, y aplica las reglas para construir el formato de vértice. Otra forma es tener un fragmento XML que defina el formato, incrustado en un comentario en la fuente del sombreador y extraído por sus herramientas, o algo por el estilo.Para uniformes, si puede usar OpenGL 3.1 o posterior, es una buena idea usar objetos de buffer uniformes (el equivalente de OpenGL de los buffers constantes de D3D). Por desgracia, GL ES 2.0 no tiene esos, por lo que los uniformes deben manejarse individualmente. Una forma de hacerlo sería crear una estructura que contenga la ubicación uniforme para cada parámetro que desee establecer: la matriz de la cámara, la potencia especular, la matriz mundial, etc. Las ubicaciones de muestra también podrían estar aquí. Este enfoque depende de que haya un conjunto estándar de parámetros compartidos entre todos los sombreadores. No todos los sombreadores tienen que usar todos los parámetros, pero todos los parámetros deberían estar en esta estructura.
Cada sombreador tendría una instancia de esta estructura, y cuando cargue un sombreador, consultará las ubicaciones de todos los parámetros, utilizando
glGetUniformLocation
nombres estandarizados. Luego, cada vez que necesite establecer un uniforme a partir del código, puede verificar si está presente en ese sombreador, y simplemente buscar su ubicación y configurarlo.fuente