J'essaie de créer un moteur de jeu 2D en utilisant OpenGL ES 2.0 (iOS pour l'instant). J'ai écrit la couche Application dans Objective C et un RendererGLES20 autonome séparé en C ++. Aucun appel spécifique à GL n'est effectué en dehors du moteur de rendu. Cela fonctionne parfaitement.
Mais j'ai des problèmes de conception lors de l'utilisation de shaders. Chaque shader a ses propres attributs et uniformes uniques qui doivent être définis juste avant l'appel de dessin principal (glDrawArrays dans ce cas). Par exemple, pour dessiner une géométrie, je ferais:
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(.......);
}
Comme vous pouvez le voir, ce code est extrêmement rigide. Si je devais utiliser un autre shader, disons effet d'entraînement, j'aurais besoin de passer des uniformes supplémentaires, des attributs de vertex, etc. En d'autres termes, je devrais changer le code source du rendu RendererGLES20 juste pour incorporer le nouveau shader.
Existe-t-il un moyen de rendre l'objet shader totalement générique? Comme si je voulais juste changer l'objet shader et ne pas m'inquiéter de la recompilation de la source du jeu? N'importe quel moyen de rendre le rendu indépendant des uniformes et des attributs, etc.? Même si nous devons transmettre des données aux uniformes, quel est le meilleur endroit pour le faire? Classe de modèle? La classe de modèle connaît-elle les uniformes et les attributs spécifiques au shader?
Spectacles suivants: Classe d'acteur:
class Actor : public ISceneNode
{
ModelController * model;
AIController * AI;
};
Classe de contrôleur de modèle: classe ModelController {class IShader * shader; int textureId; teinte vec4; float alpha; struct Vertex * vertexArray; };
La classe Shader contient simplement l'objet Shader, compilant et liant des sous-routines, etc.
Dans la classe Game Logic, je rend en fait l'objet:
void GameLogic::update(float dt)
{
IRenderer * renderer = g_application->GetRenderer();
Actor * a = GetActor(id);
renderer->render(a->model);
}
Veuillez noter que même si Actor étend ISceneNode, je n'ai pas encore commencé à implémenter SceneGraph. Je le ferai dès que je résoudrai ce problème.
Des idées pour améliorer cela? Modèles de conception associés, etc.?
Merci d'avoir lu la question.
la source
Réponses:
Il est possible de rendre votre système de shaders plus axé sur les données, de sorte que vous n'ayez pas autant de code spécifique aux shaders pour les uniformes et les formats de sommets, mais plutôt de les définir par programme en fonction des métadonnées attachées aux shaders.
D'abord l'avertissement: un système basé sur les données peut faciliter l'ajout de nouveaux shaders, mais d'un autre côté, il a des coûts en termes de complexité accrue du système, ce qui le rend plus difficile à maintenir et à déboguer. C'est donc une bonne idée de bien réfléchir à la quantité de données qui vous sera bénéfique (pour un petit projet, la réponse pourrait bien être "aucune"), et n'essayez pas de construire un système trop généralisé.
D'accord, parlons d'abord des formats de sommet (attributs). Vous pouvez créer une description de données en créant une structure qui contient les données à transmettre
glVertexAttribPointer
- l'index, le type, la taille, etc. d'un seul attribut - et en ayant un tableau de ces structures pour représenter le format de sommet entier. Compte tenu de ces informations, vous pouvez configurer par programme tous les états GL liés aux attributs de sommet.D'où proviennent les données pour remplir cette description? Conceptuellement, je pense que le moyen le plus propre est de le faire appartenir au shader. Lorsque vous créez les données de sommet pour un maillage, vous recherchez le shader utilisé sur le maillage, trouvez le format de sommet requis par ce shader et créez le tampon de sommet en conséquence. Vous avez juste besoin d'un moyen pour chaque shader de spécifier le format de sommet qu'il attend.
Il existe différentes façons de procéder; par exemple, vous pourriez avoir un ensemble standard de noms pour les attributs dans le shader ("attrPosition", "attrNormal", etc.) plus quelques règles codées en dur comme "position is 3 floats". Ensuite, vous utilisez
glGetAttribLocation
ou similaire pour rechercher les attributs utilisés par le shader et appliquer les règles pour créer le format de sommet. Une autre façon est d'avoir un extrait XML définissant le format, incorporé dans un commentaire dans la source du shader et extrait par vos outils, ou quelque chose dans ce sens.Pour les uniformes, si vous pouvez utiliser OpenGL 3.1 ou une version ultérieure, c'est une bonne idée d'utiliser des objets de tampon uniformes (l'équivalent OpenGL des tampons constants de D3D). Hélas, GL ES 2.0 n'en a pas, donc les uniformes doivent être manipulés individuellement. Une façon de le faire serait de créer une structure contenant l'emplacement uniforme pour chaque paramètre que vous souhaitez définir - la matrice de la caméra, la puissance spéculaire, la matrice mondiale, etc. Les emplacements de l'échantillonneur pourraient également être ici. Cette approche dépend de l'existence d'un ensemble standard de paramètres partagés entre tous les shaders. Tous les shaders ne doivent pas utiliser tous les paramètres, mais tous les paramètres devraient être dans cette structure.
Chaque shader aurait une instance de cette structure, et lorsque vous chargeriez un shader, vous lui demanderiez les emplacements de tous les paramètres, en utilisant
glGetUniformLocation
des noms standardisés. Ensuite, chaque fois que vous devez définir un uniforme à partir du code, vous pouvez vérifier s'il est présent dans ce shader, puis rechercher son emplacement et le définir.la source