J'ai vu beaucoup de questions dans ce domaine, mais pas cette question exacte, alors veuillez m'excuser s'il s'agit d'un doublon.
Je fais un petit jeu en 3D. Eh bien, pour être honnête, ce n'est qu'un petit projet de loisir et ne se révélera probablement pas être un jeu réel, je serai heureux de faire une belle démonstration graphique et d'en apprendre davantage sur le rendu 3D et la conception c ++.
Mon intention est d'utiliser direct3d9 pour le rendu car j'en ai peu d'expérience, et il semble répondre à mes exigences. Cependant, si j'ai appris une chose en tant que programmeur, c'est de demander " y a-t-il une raison concevable pour que ce composant soit remplacé par une implémentation différente " et si la réponse est oui, alors je dois concevoir une abstraction et une interface appropriées pour ce composant . Donc, même si j'ai l'intention d'implémenter d3d9, j'ai besoin de concevoir une interface 3D qui pourrait être implémentée pour d3d11, opengl ...
Ma question est alors à quel niveau est-il préférable de le faire? Je pense qu'une interface capable de créer et de dessiner plus tard
- Tampons de sommet et tampons d'index
- Textures
- Vertex et Pixel "shaders"
- Quelques représentations de l'état du dessin (modes de fusion etc ...)
En d'autres termes, une interface de niveau assez bas où mon code pour dessiner par exemple un modèle animé utiliserait l'interface pour obtenir des tampons de sommets abstraits, etc.
L'alternative est de le faire à un niveau supérieur où l'interface peut dessiner des objets, des animations, des paysages, etc. et les implémenter pour chaque système. Cela semble plus de travail, mais c'est plus flexible, je suppose.
C'est vraiment ma question, lors de l'abstraction du système de dessin, quel niveau d'interface fonctionne le mieux?
Réponses:
Toutes mes excuses pour la durée de cette réponse!
«Toute raison concevable» est une position beaucoup trop extrême pour être adoptée. Presque chaque fonction peut être paramétrée d'une manière ou d'une autre, chaque module peut recevoir une stratégie ou une politique différente, et chaque constante peut être modifiée. Si vous fournissez un niveau d'abstraction supplémentaire pour chacun d'eux, vous finissez par écrire 2x plus de code sans gain immédiat, juste un gain potentiel plus tard si vous avez besoin de cette flexibilité.
Si vous suivez la voie de la fourniture d'interfaces afin que chacune de ces choses puisse être une façade pour l'une des différentes options, vous devez alors décider comment vous fournissez ces différentes options, y compris les valeurs par défaut, et quand vous essayez de rendre cela aussi simple comme l'objet d'origine était, vous vous retrouvez souvent avec une couche supplémentaire entière sur le dessus. Cela peut (et devient) absurde dans de nombreux logiciels `` d'entreprise '' du monde réel - puis-je suggérer humblement de lire cette satire qui, malheureusement, se rapproche trop près de chez soi dans de nombreux cas: Pourquoi je déteste les cadres .
Le plus grand argument contre cela est la complexité. Faisons l'hypothèse (probablement incorrecte) qu'il existe une bonne interface qui pourrait s'appliquer aussi bien à deux API de rendu 3D, et que quelqu'un peut vous le décrire dans une réponse ici. Vous travailleriez alors sur cette interface qui, en raison de sa nature polyvalente, couvrirait sans aucun doute les éléments que DX9 n'utilise pas - par exemple, la pléthore d'extensions d'API disponibles dans OpenGL mais pas dans DirectX 9. C'est une complication supplémentaire qui rendra votre code plus difficile à comprendre et à maintenir, et ralentira votre processus d'apprentissage. C'est déjà assez grave lorsque vous utilisez une bibliothèque multi-plate-forme existante telle que SDL, car les gens rencontrent toujours des fonctions et des restrictions étranges qui existent uniquement pour réparer quelque chose sur une plate-forme, mais vous '' Il faut non seulement savoir pourquoi cette partie de l'interface est là, mais aussi comment elle peut ou non interagir avec les parties que vous êtes sur le point d'écrire. par exemple. Vous finiriez par écrire des objets abstraits qui encapsulent les aspects des extensions OpenGL qui sont nécessaires dans votre code de rendu principal, même si vous n'avez pas encore de moyen de les utiliser.
Cependant, à l'avenir, une fois que vous maîtriserez le fonctionnement du système DX9, vous pourrez examiner le fonctionnement du système OpenGL. Vous verrez alors comment vous devez adapter votre système existant pour faire fonctionner les deux pièces, et le ferez, avec l'existence pratique de votre chemin de code DX9 existant comme test unitaire efficace pour vous assurer que votre refactoring est correct.
Vous avez la chance de travailler avec l'un des matériaux les plus fluides et les plus malléables connus de l'ingénierie - le code source stocké sous forme de données informatiques. Embrassez cette bénédiction en y apportant des changements lorsque vous savez que vous en avez besoin, plutôt que de les faire des mois à l'avance et de vous surcharger en attendant en ayant à écrire dans une abstraction que vous ne gagnez pas encore.
la source
Cela ne répond pas vraiment à votre question, mais d'après mon expérience de programmeur, la généralisation prématurée est tout aussi mauvaise que l'optimisation prématurée. Vous gagnez rarement quelque chose et devez le maintenir au cours du projet.
Mon conseil est d'utiliser directement le code client D3D9 dans votre projet, et lorsque vous déterminez que vous devez changer d'interface, créez une interface avec toutes les choses que vous utilisez actuellement. Les programmeurs sont terribles à prédire ce dont ils pourraient avoir besoin, d'autant plus dans votre cas où vous n'êtes pas très familier avec le matériel, de cette façon vous faites le moins de travail.
Voir aussi: http://c2.com/xp/YouArentGonnaNeedIt.html
la source
Hm, on dirait que je n'ai pas assez de réputation pour poster des commentaires aux réponses.
Kylotan et Tetrad ont donné d'excellentes réponses. Ce que j'ajouterais, c'est de vous suggérer de séparer le code de rendu de la logique du jeu - cela servira à plusieurs fins:
En prime, vous construirez ainsi progressivement votre abstraction au fur et à mesure que vous travaillez sur votre projet. Comme vous le dites, il s'agit d'un projet d'apprentissage, alors allez-y doucement et simplifiez-le, en ajoutant de la complexité et des abstractions au fur et à mesure au lieu de faire de grands plans car vous manquez probablement d'expertise pour bien faire les choses au départ (comme Tetrad l'a également suggéré).
la source
Vous ne pouvez pas écrire une abstraction de bas niveau sur ce genre de chose, car les détails d'un shader sont complètement différents entre D3D9, 11, OGL, etc. C'est tout simplement impossible.
Vous pouvez vérifier les abstractions implémentées dans, disons, SFML, SDL, OGRE si vous voulez voir ce que d'autres personnes ont choisi de faire pour rendre les abstractions.
la source
J'ai essayé d'écrire une API qui enveloppe parfaitement D3D et OGL. C'est une entreprise énorme, j'ai appris. Beaucoup de leurs différences peuvent être conciliées. Par exemple, le chargement de shaders. J'encapsule efficacement le code de shader D3D et OGL dans mon propre format. IE: Dans D3D, vous devez spécifier le profil de shader, afin que le fichier lui-même ait le profil de shader écrit dedans. Lorsque la source de shader encapsulée est remise à mon API, elle appelle la méthode de création de shader D3D appropriée avec le profil de shader approprié, etc.
Cependant, il y a encore beaucoup de différences que je n'ai pas résolues, ni que je pourrais jamais, car j'ai décidé qu'une abstraction de niveau supérieur est plus facile à concevoir et satisfaisante pour mes besoins. Je travaille actuellement sur quelque chose de similaire au framework D3DXEffect. Les objets ont une technique de rendu, qui contient des passes de rendu, qui ont un ensemble d'états de rendu, un ensemble de shaders et nécessitent certaines entrées. Cela me permet de résumer davantage l'API.
la source
Je ne sais pas pourquoi les gens passent automatiquement à une généralisation / optimisation prématurée lorsque des questions comme celles-ci apparaissent. Il y a quelque chose à dire sur la préemption et la réalisation d'un composant d'un système susceptible d'être échangé. Toutes les réponses sur "ne vous inquiétez pas et ne codez pas jusqu'à ce que vous ayez besoin de le changer" entraînent généralement des problèmes dans le monde réel car elles entraînent des changements plus importants en cours de route, ou laissent un code qui devrait être conçu différemment de commencer par, mais a été laissé parce qu'il "fonctionne". Réfléchir à l'avance et savoir où s'arrêter avant de trop réfléchir est une compétence en soi.
Il serait beaucoup plus facile de refactoriser les interfaces pour tenir compte des différences entre DX / OGL, que d'introduire une toute nouvelle implémentation (DX par exemple) dans un système strictement codé pour fonctionner avec OGL. L'abstraction du code devrait de toute façon se produire à un certain niveau car vous ne voudriez pas mélanger le code graphique avec le code logique du jeu partout, ou répéter du code. Si je travaille sur un jeu et que je commence avec OpenGL (parce que j'ai de l'expérience avec lui), mais que je sais aussi que j'aimerais éventuellement obtenir le jeu sur Xbox / Windows (DX), il serait idiot pour moi de ne pas en tenir compte lors de la conception de mon API graphique. L'idée de refactoriser quelque chose d'aussi complexe qu'un jeu et d'introduire de nouvelles API graphiques est fastidieuse et sujette aux erreurs.
Vous devez réfléchir à la façon dont vous allez utiliser votre API et la développer au fur et à mesure que vos besoins / connaissances changent au cours du processus de développement du jeu. Vous ne faites pas une abstraction sur OGL / DX de bas niveau, mais vous créez plutôt une API pour vous aider à charger la géométrie, à charger / compiler des shaders, à charger des textures, etc.
la source