Contexte:
Je conçois un système de rendu 3D simple pour une architecture de type système de composants d'entité utilisant C ++ et OpenGL. Le système se compose d'un moteur de rendu et d'un graphique de scène. Lorsque je termine la première itération du moteur de rendu, je peux distribuer le graphique de la scène dans l'architecture ECS. Pour l'instant, il est réservé d'une manière ou d'une autre. Si possible, voici mes objectifs pour le moteur de rendu:
- Simplicité . Il s'agit d'un projet de recherche et je souhaite pouvoir facilement changer et étendre mes systèmes (d'où l'approche ECS).
- Performance . Ma scène pourrait avoir de nombreux petits modèles et aussi de gros volumes avec beaucoup de géométrie. Il n'est pas acceptable d'acquérir des objets à partir du contexte OGL et de la géométrie du tampon à chaque image de rendu. Je vise la localité des données pour éviter les ratés du cache.
- Souplesse . Il doit être capable de rendre des sprites, des modèles et des volumes (voxels).
- Découplé . Le graphique de la scène peut être refactorisé dans l'architecture ECS de base après avoir écrit mon rendu.
- Modulaire . Ce serait bien de pouvoir échanger différents rendus sans changer mon graphique de scène.
- Transparence référentielle , ce qui signifie qu'à tout moment je peux lui donner n'importe quelle scène valide et qu'elle restituera toujours la même image pour cette scène. Cet objectif en particulier n'est pas nécessairement requis. Je pensais que cela aiderait à simplifier la sérialisation des scènes (je devrai être en mesure d'enregistrer et de charger des scènes) et me donnerait la flexibilité de permuter dans différentes scènes pendant l'exécution à des fins de test / expérimentation.
Problème et idées:
J'ai trouvé différentes approches à essayer, mais j'ai du mal à mettre en cache les ressources OGL (VAO, VBO, shaders, etc.) pour chaque noeud de rendu. Voici les différents concepts de mise en cache que j'ai imaginés jusqu'à présent:
- Cache centralisé. Chaque nœud de scène a un ID et le moteur de rendu a un cache qui mappe les ID aux nœuds de rendu. Chaque noeud de rendu contient les VAO et VBO associés à la géométrie. Un échec de cache acquiert des ressources et mappe la géométrie à un nœud de rendu dans le cache. Lorsque la géométrie est modifiée, un indicateur sale est défini. Si le moteur de rendu voit un indicateur de géométrie sale lors de l'itération à travers les nœuds de scène, il rebuffade les données à l'aide du nœud de rendu. Lorsqu'un nœud de scène est supprimé, un événement est diffusé et le moteur de rendu supprime le nœud de rendu associé du cache lors de la libération des ressources. Alternativement, le nœud est marqué pour suppression et le moteur de rendu est responsable de sa suppression. Je pense que cette approche atteint le plus étroitement l'objectif 6 tout en considérant également 4 et 5. 2 souffre de la complexité supplémentaire et de la perte de la localisation des données avec les recherches de cartes au lieu de l'accès aux tableaux.
- Cache distribué . Similaire ci-dessus, sauf que chaque nœud de scène a un nœud de rendu. Cela contourne la recherche de carte. Pour traiter la localité des données, les nœuds de rendu peuvent être stockés dans le moteur de rendu. Ensuite, les nœuds de scène pourraient plutôt avoir des pointeurs pour rendre les nœuds et le moteur de rendu définit le pointeur sur un échec de cache. Je pense que ce type imite une approche de composant d'entité, donc ce serait cohérent avec le reste de l'architecture. Le problème ici est que maintenant les nœuds de scène contiennent des données spécifiques à l'implémentation du rendu. Si je change la façon dont les choses sont rendues dans le moteur de rendu (comme le rendu des sprites par rapport aux volumes), je dois maintenant changer le nœud de rendu ou ajouter plus de "composants" au nœud de scène (ce qui signifie également changer le graphique de la scène). Du côté positif, cela semble être le moyen le plus simple de faire fonctionner mon rendu de première itération.
- Métadonnées distribuées . Un composant de métadonnées de cache de rendu est stocké dans chaque nœud de scène. Ces données ne sont pas spécifiques à l'implémentation mais contiennent plutôt un ID, un type et toute autre donnée pertinente requise par le cache. Ensuite, la recherche de cache peut être effectuée directement dans un tableau à l'aide de l'ID, et le type peut indiquer le type d'approche de rendu à utiliser (comme les sprites vs les volumes).
- Cartographie visiteur + distribué . Le rendu est un visiteur et les nœuds de scène sont des éléments du modèle de visiteur. Chaque nœud de scène contient une clé de cache (comme les métadonnées mais juste un ID) que seul le moteur de rendu manipule. L'ID peut être utilisé pour le tableau au lieu de la recherche de carte généralisée. Le moteur de rendu peut permettre au nœud de scène d'envoyer une fonction de rendu différente en fonction du type du nœud de scène, et l'ID peut être utilisé par n'importe quel cache. Un ID par défaut ou hors de portée indiquerait un échec de cache.
comment résoudrais-tu ce problème? Ou avez-vous des suggestions? Merci d'avoir lu mon mur de texte!
Réponses:
Après avoir relu votre question, je sens fortement que vous compliquez trop le problème. Voici pourquoi:
Il n'y a vraiment que deux types de système de rendu: Forward et Deferred, dont aucun ne dépend d'un graphe de scène.
Les seuls problèmes de performances que vous devriez vraiment rencontrer avec n'importe quel système de rendu, devraient provenir d'un nombre élevé de poly et d'un shader et d'un code client inefficaces.
Les échecs de cache réduisent en effet les performances, mais ils ne sont pas tout à fait les monstres que vous pourriez penser. 80% de vos améliorations de performances proviendront d'un algorithme plus efficace. Ne faites pas l'erreur de pré-optimiser votre code.
Cela dit:
Si vous utilisez un homebrew scenegraph, vous devriez déjà utiliser une interface "Renderer" (ou classe de base) pour concevoir la partie de rendu de votre code de scenegraph. Le modèle de visiteur utilisant la double répartition est une bonne approche, car vous pouvez très bien utiliser de nombreux types de nœuds de graphique tels que la couleur, la texture, le maillage, la transformation, etc. De cette façon, pendant le cycle de rendu, tout le rendu doit faire est parcourez l'arborescence de la scène en profondeur d'abord, en vous faisant passer pour un argument. De cette façon, le moteur de rendu est simplement une collection de shaders et peut-être un ou deux framebuffer. Le résultat de cela est que le code de recherche / suppression n'est plus nécessaire pour le système de rendu, juste le scénario lui-même.
Il existe certainement d'autres moyens de résoudre les problèmes auxquels vous êtes confronté, mais je ne veux pas donner une réponse trop longue. Donc, mon meilleur conseil est de commencer par travailler quelque chose de simple, puis de l'étendre pour trouver ses faiblesses, puis d'expérimenter d'autres approches et de voir où leurs forces / faiblesses se trouvent dans la pratique.
Ensuite, vous serez bien placé pour prendre une décision éclairée.
la source