OpenGL orienté objet

12

J'utilise OpenGL depuis un certain temps et j'ai lu un grand nombre de tutoriels. Mis à part le fait que beaucoup d'entre eux utilisent toujours le pipeline fixe, ils jettent généralement toutes les initialisations, les changements d'état et le dessin dans un fichier source. C'est très bien pour la portée limitée d'un tutoriel, mais j'ai du mal à trouver comment le faire évoluer jusqu'à un jeu complet.

Comment répartissez-vous votre utilisation d'OpenGL entre les fichiers? Conceptuellement, je peux voir les avantages d'avoir, disons, une classe de rendu qui restitue purement des trucs à l'écran, mais comment les trucs comme les shaders et les lumières fonctionneraient-ils? Dois-je avoir des cours séparés pour des choses comme les lumières et les shaders?

Sullivan
la source
1
Il existe des jeux open source ainsi que des tutoriels plus volumineux qui contiennent plus de code. Découvrez-en certains et voyez comment le code est organisé.
MichaelHouse
Êtes-vous un débutant en matière de moteur de rendu?
Samaursa
On ne peut raisonnablement répondre à cette question sans un rétrécissement de la portée. Qu'essayez-vous de construire? Quel genre de jeu, quel type de graphisme a-t-il, etc.?
Nicol Bolas
1
@Sullivan: "Je pensais que ce genre de concepts s'appliquerait à presque tous les jeux." Ils ne le font pas. Si vous me demandez de faire Tetris, je ne vais pas m'embêter à faire une grosse couche wrapper ou quelque chose autour d'OpenGL. Utilisez-le directement. Si vous me demandez de créer quelque chose comme Mass Effect, alors nous allons avoir besoin de plusieurs couches d'abstraction autour de notre système de rendu. Lancer le moteur Unreal à Tetris consiste à utiliser un fusil de chasse pour chasser les mouches; cela rend la tâche beaucoup plus difficile que de la coder directement à la main. Vous ne devez construire un système aussi grand et complexe que nécessaire , et pas plus gros.
Nicol Bolas
1
@Sullivan: La seule complexité de votre question est de savoir où vous parlez de fractionner plusieurs fichiers. C'est quelque chose que je ferais même dans Tetris. Et il existe de nombreux niveaux de complexité entre Tetris et le moteur Unreal. Encore une fois: lequel demandez-vous?
Nicol Bolas

Réponses:

6

Je pense que OO OpenGL n'est pas si nécessaire. C'est différent lorsque vous parlez de shader, de modèle, etc. de classe.

Fondamentalement, vous feriez d'abord l'initialisation du jeu / moteur (et d'autres choses). Ensuite, vous chargez des textures, des modèles et des shaders dans la RAM (si nécessaire) et des objets tampons, et téléchargez / compilez des shaders. Après cela, vous, dans votre structure de données ou classe de shader, modèle, avez des identifiants int de shaders, de modèle et d'objets de tampon de texture.

Je pense que la plupart des moteurs ont des composants moteurs et chacun d'entre eux a certaines interfaces. Tous les moteurs que j'ai examinés ont un composant tel que Renderer ou SceneManager ou les deux (cela dépend de la complexité du jeu / moteur). Vous pouvez alors avoir la classe OpenGLRenderer et / ou DXRenderer qui implémentent l'interface Renderer. Ensuite, si vous avez SceneManager et Renderer, vous pouvez effectuer certaines des opérations suivantes:

  • Traversez SceneManager et envoyez chaque objet à Renderer pour le rendu
  • Encapsuler les majuscules dans une méthode SceneManager
  • Envoyer SceneManager vers Renderer pour qu'il le gère

Le rendu appellera probablement la fonction de dessin de l'objet qui appellera la fonction de dessin de chaque maillage qui est composé et le maillage liera l'objet de texture, liera le shader, appellera la fonction de dessin d'OpenGL, puis utilisera les shaders, les objets de texture et les objets de tampon de données.

REMARQUE: ceci n'est qu'un exemple, vous devez étudier SceneManager plus en détail et analyser votre cas d'utilisation pour voir quelle est la meilleure option d'implémentation

Vous auriez bien sûr d'autres composants du moteur, tels que MemoryManager , ResourceLoader, etc., qui prendraient en charge l'utilisation de la mémoire vidéo et RAM, afin qu'ils puissent charger / décharger certains modèles / shaders / textures selon les besoins. Les concepts pour cela incluent la mise en cache de la mémoire, le mappage de la mémoire, etc. etc. Il y a beaucoup de détails et de concepts sur chaque composant.

Jetez un oeil à une description plus détaillée des autres moteurs de jeu, ils sont nombreux et leur documentation est à peu près disponible.

Mais oui, les cours facilitent la vie; vous devez les utiliser totalement et vous souvenir de l'encapsulation, de l'héritage, des interfaces et d'autres trucs sympas.

edin-m
la source
C'est la réponse que je cherchais. Cependant, lorsque vous dites "... charger des textures, des modèles et des shaders dans la RAM (si nécessaire) et des objets tampons, et télécharger / compiler des shaders ..." me ramène à ma question d'origine; dois-je utiliser des objets pour encapsuler des choses comme des modèles et des shaders?
Sullivan
objet est une instance d'une classe, et «vous devez les utiliser totalement».
edin-m
Désolé, je voulais demander «comment dois-je utiliser les objets». Ma faute.
Sullivan
Eh bien, cela dépend de la conception de votre classe. Un exemple de base serait intuitif, quelque chose comme: object3d (classe) a des mailles (classe); chaque maille a un matériau (classe); chaque matériau a une texture (peut être une classe ou juste un id) et un shader (classe). Mais en lisant sur chaque composant, vous verrez qu'il existe des approches différentes mais égales pour créer SceneManager, Renderer, MemoryManager (tous ces éléments peuvent être une seule classe ou plusieurs d'entre eux, hérités, etc., etc.). Des dizaines de livres sont écrits sur ce sujet (architecture du moteur de jeu), et pour répondre à la question en détail, on pourrait en écrire un.
edin-m
Je pense que c'est la meilleure réponse que je vais obtenir. Merci pour votre aide :)
Sullivan
2

OpenGL contient déjà quelques concepts «Object».

Par exemple, tout ce qui a un identifiant peut être considéré comme un objet (il y a aussi des choses spécifiquement nommées «Objets»). Buffers, Textures, Vertex Buffer Objects, Vertex Array Objects, Frame Buffer Objects et ainsi de suite. Avec un peu de travail, vous pouvez envelopper les cours autour d'eux. Il vous donne également un moyen facile de revenir aux anciennes fonctions OpenGL obsolètes si votre contexte ne prend pas en charge les extensions. Par exemple, un VertexBufferObject pourrait retomber sur l'utilisation de glBegin (), glVertex3f (), etc.

Il y a quelques façons dont vous pourriez avoir besoin de vous éloigner des concepts OpenGL traditionnels, par exemple vous voulez probablement stocker des métadonnées sur les tampons dans les objets tampons. Par exemple, si le tampon stocke des sommets. Quel est le format des sommets (c'est-à-dire la position, les normales, les texcoords, etc.). Quelles primitives il utilise (GL_TRIANGLES, GL_TRIANGLESTRIP, etc ...), les informations de taille (combien de flottants sont stockés, combien de triangles ils représentent, etc ...). Juste pour le rendre facile à brancher dans les commandes draw arrays.

Je vous recommande de regarder OGLplus . Il s'agit de liaisons C ++ pour OpenGL.

Aussi glxx , c'est seulement pour le chargement d'extension.

En plus d'encapsuler l'API OpenGL, vous devriez envisager de créer un niveau légèrement supérieur au-dessus.

Par exemple, une classe de gestionnaire de matériel qui est responsable de tous vos shaders, de les charger et de les utiliser. Il serait également responsable de leur transférer des propriétés. De cette façon, vous pouvez simplement appeler: materials.usePhong (); material.setTexture (someexture); material.setColor (). Cela permet une plus grande flexibilité car vous pouvez utiliser des choses plus récentes comme des objets de tampon uniforme partagés pour n'avoir qu'un seul grand tampon contenant toutes les propriétés que vos shaders utilisent dans 1 bloc, mais si cela ne vous prend pas en charge, vous pouvez revenir au téléchargement vers chaque programme de shader. Vous pouvez avoir 1 grand shader monolithique et permuter entre différents modèles de shader en utilisant des routines uniformes si cela est pris en charge ou vous pouvez revenir à l'utilisation d'un tas de différents petits shaders.

Vous pouvez également consulter les dépenses à partir des spécifications GLSL pour écrire votre code de shader. Par exemple, le #include serait incroyablement utile et très facile à implémenter dans votre code de chargement de shader (il y a aussi une extension ARB pour cela). Vous pouvez également générer votre code à la volée en fonction des extensions prises en charge, par exemple utiliser un objet uniforme partagé ou recourir à des uniformes normaux.

Enfin, vous voudrez une API de pipeline de rendu de niveau supérieur qui fait des choses comme les graphiques de scène, les effets spéciaux (flou, lueur), les choses qui nécessitent plusieurs passes de rendu comme les ombres, l'éclairage et autres. Et en plus de cela, une API de jeu qui n'a rien à voir avec l'API graphique mais qui traite uniquement des objets d'un monde.

David C. Bishop
la source
2
"Je vous recommande de regarder OGLplus. Ce sont des liaisons C ++ pour OpenGL." Je recommanderais contre cela. J'adore RAII, mais c'est totalement inapproprié pour la plupart des objets OpenGL. Les objets OpenGL sont associés à une construction globale: le contexte OpenGL. Vous ne pourrez donc pas créer ces objets C ++ avant d'avoir un contexte et vous ne pourrez pas supprimer ces objets si le contexte a déjà été détruit. En outre, cela donne l' illusion que vous pouvez modifier des objets sans changer l'état global. C'est un mensonge (sauf si vous utilisez EXT_DSA).
Nicol Bolas
@NicolBolas: ne pourrais-je pas vérifier s'il y a un contexte et ne l'initialiser qu'à ce moment-là? Ou peut-être seulement permettre aux gestionnaires de créer les objets qui nécessitent un contexte OpenGL? --- btw, grand ensemble de tutoriels (lien dans votre profil)! D'une certaine manière, je ne les ai jamais rencontrés lors de la recherche d'OpenGL / Graphics.
Samaursa
@Samaursa: À quelle fin? Si vous avez un gestionnaire pour les objets OpenGL, alors ... vous n'avez pas vraiment besoin que les objets prennent soin d'eux-mêmes; le manager peut le faire pour eux. Et si vous les initialisez uniquement lorsqu'un contexte existe, que se passe-t-il si vous essayez d'utiliser un objet qui n'a pas été correctement initialisé? Cela crée juste de la fragilité dans votre base de code. Cela ne change rien à ce que j'ai dit sur la possibilité de modifier ces objets sans toucher à l'état global.
Nicol Bolas
@NicolBolas: "vous ne pourriez pas créer ces objets C ++ avant d'avoir un contexte" ... est-ce différent de l'utilisation de constructions OpenGL nues? À mes yeux, la oglplus::Contextclasse rend cette dépendance très visible - serait-ce un problème? Je crois que cela aidera les nouveaux utilisateurs d'OpenGL à éviter beaucoup de problèmes.
xtofl
1

Dans OpenGL moderne, vous pouvez presque totalement séparer les objets rendus les uns des autres, en utilisant différents vaos et programmes de shader. Et même l'implémentation d'un objet peut être séparée en plusieurs couches d'abstraction.

Par exemple, si vous souhaitez implémenter un terrain, vous pouvez définir un TerrainMesh dont le constructeur crée les sommets et les indices du terrain, et les définit dans des tampons de tableau, et - si vous lui donnez une position d'attribut - il ombrage vos données. Il doit également savoir comment le restituer, et il doit prendre soin de rétablir tous les changements de contexte qu'il a effectués pour configurer le rendu. Cette classe elle-même ne devrait rien savoir du programme de shader qui va le rendre, et elle ne devrait rien savoir des autres objets de la scène. Au-dessus de cette classe, vous pouvez définir un Terrain, qui connaît le code du shader, et son travail consiste à créer la connexion entre le shader et TerrainMesh. Cela devrait signifier obtenir des positions d'attribut et uniformes et charger des textures, et des choses comme ça. Cette classe ne devrait rien savoir de la façon dont le terrain est implémenté, de l'algorithme LoD qu'elle utilise, elle est juste responsable de l'ombrage du terrain. Au-dessus de cela, vous pouvez définir la fonctionnalité non-OpenGL comme le comportement et la détection de collision et autres.

Pour en venir au fait, même si OpenGL est conçu pour être utilisé à bas niveau, vous pouvez toujours créer des couches d'abstraction indépendantes, qui vous permettent de vous adapter aux applications avec la taille d'un jeu Unreal. Mais le nombre de couches que vous voulez / avez besoin dépend vraiment de la taille de l'application que vous souhaitez.

Mais ne vous mentez pas à propos de cette taille, n'essayez pas d'imiter le modèle d'objet d'Unity dans une application en ligne 10k, le résultat sera un désastre complet. Construisez les couches progressivement, n'augmentez le nombre de couches d'abstraction que lorsque cela est nécessaire.

Csala Tamás
la source
0

ioDoom3 est probablement un excellent point de départ, car vous pouvez en quelque sorte compter sur Carmack pour suivre une excellente pratique de codage. Je pense également qu'il n'utilise pas megatexturing dans Doom3, il est donc relativement simple comme canal de rendu.

chrisvarnz
la source
6
"comme vous pouvez en quelque sorte compter sur Carmack pour suivre une grande pratique de codage" Oh mec, c'est une bonne chose. Carmack suit une excellente pratique de codage C ++. C'est une émeute! Oh ... tu ne plaisantais pas. Um ... avez-vous déjà regardé son code? C'est un programmeur C et c'est comme ça qu'il pense. Ce qui est bien pour C, mais la question concerne C ++ .
Nicol Bolas
Je viens d'un milieu C, donc son code a beaucoup de sens pour moi: P et oui, j'ai passé un peu de temps à travailler sur des jeux basés sur Quake.
chrisvarnz