Dessiner efficacement une énorme quantité de tuiles dans Monogame (XNA)

8

Je pose cette question parce que je n'ai pas trouvé de réponse définitive.

Permettez-moi tout d'abord de dire quelques choses sur le jeu et ce que j'ai déjà fait. Le jeu va être un RTS situé dans un monde généré de manière procédurale utilisant du bruit Simplex. Le monde se compose de morceaux de 16 x 16 en sprites de 64 x 64. J'ai réussi à charger et à décharger des morceaux de manière dynamique, ce qui fonctionne très bien. Le monde va ressembler un peu à Rimworld, donc de haut en bas avec différentes couches de sprites (terrain d'abord, sprites de transition, arbres, décalcomanies, etc.). Les mondes nouvellement générés peuvent contenir des entités qui peuvent affecter l'environnement (par exemple, un village qui est devenu une ville) et donc le morceau. Je suis sûr que cela peut être calculé en utilisant une sorte de fonction, mais c'est quelque chose à noter.

Le principal problème que j'ai, c'est quand je fais un zoom arrière, de plus en plus de tuiles sont dessinées, ce qui affecte gravement les performances. À environ 30000 sprites, la section de tirage prend 8 ms, ce qui est la moitié de ce qui est requis pour fonctionner à 60 FPS. Et c'est juste le terrain. J'utilise des atlas de texture pour limiter le nombre de tirages (30000 sprites dessinés en 6 coups).

L' objectif est de pouvoir effectuer un zoom arrière du niveau de la ville / du village / de la ville jusqu'à pouvoir voir un pays entier. Cela doit être fait de manière dynamique (par exemple, pas en cliquant sur une icône de mini-carte, mais juste en arrière comme dans Supreme Commander).

J'ai lu de nombreux articles sur ce problème, mais je n'ai pas trouvé ni vu un exemple clair de son fonctionnement. Voici une liste de techniques que j'ai trouvées qui sont censées fonctionner:

  • Rectangles sales, comme décrit ici , où vous ne dessinez que de nouvelles choses et gardez le reste dans le backbuffer. Cela a beaucoup de sens, mais je ne sais pas comment l'implémenter dans Monogame.
  • Mon choix préféré: faites un usage intensif de RenderTargets, comme décrit ici , où vous dessinez vers un RenderTarget puis enregistrez-le en tant que texture. Dans mon cas, un bloc 16 x 16 composé de 64 x 64 créerait une texture 1024 x 1024. Je doute vraiment que cela fonctionne en termes de performances, mais le résultat consisterait en des textures très détaillées et est également excellent à utiliser étant donné que la plupart d'entre elles sont statiques (terrain / arbres, etc.), et ne changent pas autant. Mais cela signifie également que chaque fois qu'un changement est apporté à un morceau, que Texture2D doit être modifié à l'aide de SetData, qui d'après ce que j'ai vécu est assez gourmand en ressources processeur. Cependant, si les textures étaient de 16 x 16, cela pourrait en fait fonctionner, et cela réduirait également l'utilisation de la mémoire et du disque.
  • Til textures en spécifiant le SourceRectangle dans SpriteBatch. Pour les vastes prairies / océans, c'est un plus, car un seul sprite est dessiné. Cependant, pour un terrain détaillé avec différentes couleurs et différents sprites mélangés (biomes et transitions de biomes), je crains que cela ne fasse pas une énorme différence.
  • Ma propre solution, qui compromet les détails, était d'utiliser une tuile blanche standard de 64 x 64, de lui donner la couleur des 4 tuiles environnantes, puis de la mettre à l'échelle pour qu'elle couvre les 4 tuiles précédentes. La différence ici (en plus de la tuile de couleur unie) est que le paysage a visiblement changé. Je dois également mentionner que les forêts doivent encore être dessinées individuellement car elles ne sont pas parfaitement carrées.

Si quelqu'un a une idée de la façon de résoudre ce problème, même si cela signifie compromettre certaines choses (comme les détails ou utiliser des sprites 16 x 16), j'aimerais l'entendre.

Guguhl Pluhs
la source
Ne laissez pas l'utilisateur dézoomer autant
Bálint
J'ai également rencontré des problèmes de performances avec mon projet MonoGame généré de manière procédurale. Ma solution était de limiter ce qui était rendu (limiter la plage de zoom arrière et ne dessiner que ce qui était à l'écran). Il semble que MonoGame soit conçu avec un mantra "dessiner tout à chaque image", mais j'espère que quelqu'un de plus expérimenté avec MonoGame me corrigera.
Logical Fallacy
Comment restituez-vous les tuiles? Plus précisément - utilisez-vous SpriteBatch et comment l'utilisez-vous?
loodakrawa

Réponses:

3

Les cartes graphiques sont optimisées pour dessiner plusieurs choses plusieurs fois plutôt que l'inverse.

Dans votre cas, vous avez beaucoup de choses (différentes textures) que vous souhaitez dessiner beaucoup de fois (nombre de tuiles).

IMO, les optimisations les plus simples que vous pouvez faire pour obtenir des gains de performances significatifs sont:

  1. Combinez votre texture en atlas. Cela devrait accélérer considérablement les choses, car votre carte graphique ne dessine désormais qu'une (ou quelques) textures au lieu de nombreuses petites.
  2. Batch vos tuiles avec SpriteBatch en mode SpriteSortMode.Texture. Cela triera vos sprites par texture et réduira le nombre de commutateurs de texture au nombre réel de textures différentes. L'inconvénient de cette approche est que vos sprites ne seront pas triés par profondeur. Ce qui est probablement bien puisque vos carreaux sont tous à la même profondeur. Je suppose que vous avez également d'autres éléments, il est donc logique de séparer un SpriteBatch pour les tuiles et un autre pour tout le reste (défini sur SpriteSortMode.BackToFront).

Je recommanderais de faire les deux choses. Bonne chance.

loodakrawa
la source
2

J'ai découvert que SpriteBatch ne convient pas pour dessiner des carreaux. L'une des raisons est que les sprites sont destinés à des objets dynamiques et sont utilisés à des fins multiples. Une autre raison est qu'il est incroyablement lié au processeur , par exemple. comme expliqué dans le dessin de description, 30 000 objets coûtent 8 ms (alors que mon chipset a atteint un maximum de 30%). De plus, jusqu'à 3 000 sprites peuvent être dessinés avant qu'un nouvel appel de tirage ne soit effectué vers le GPU (par lots et tous).

La solution était de dessiner mes propres tuiles en utilisant des quads texturés . Ceci en combinaison avec un VertexBuffer m'a permis de dessiner jusqu'à 500 000 tuiles texturées en 1 appel de tirage où la méthode de tirage a consommé environ 1 / 20e de milliseconde. Bien sûr, je n'aurai probablement pas à en tirer autant, mais dans tous les cas, j'ai finalement obtenu que mon GPU ait une charge de travail de 75% à 60 images par seconde.

Guguhl Pluhs
la source
Sonne très bien. J'aimerais en savoir plus - avez-vous suivi des didacticiels ou avez-vous des liens avec une description plus détaillée de ce processus?
loodakrawa
2
J'ai appris la majeure partie de celui-ci partout qui mentionnait XNA / Monogame et les quads texturés. Cependant, riemers.net m'a beaucoup aidé car il s'adresse aux personnes qui veulent simplement mettre en place rapidement quelque chose qui fonctionne. Vous voudrez suivre spécifiquement son didacticiel de terrain 3D car il couvre également les VertexBuffers et IndexBuffers. Le transformer en un type de terrain en tuile ne devrait pas être un problème.
Guguhl Pluhs
Si c'est ce qui répond le mieux à votre question, n'hésitez pas à la marquer comme acceptée :)
Vaillancourt
@GuguhlPluhs Je sais que c'est tard, mais avez-vous encore plus d'informations sur ce sujet? Je fais face au même problème.
Jelle