Travailler avec beaucoup de cubes. Amélioration des performances?


12

Edit: Pour résumer la question, j'ai un monde basé sur les voxels (style Minecraft (Merci Communist Duck)) qui souffre de mauvaises performances. Je ne suis pas positif sur la source, mais j'aimerais avoir des conseils sur la façon de s'en débarrasser.

Je travaille sur un projet où un monde se compose d'une grande quantité de cubes (je vous donnerais un nombre, mais ce sont des mondes définis par l'utilisateur). Mon test est autour de blocs (48 x 32 x 48).

Fondamentalement, ces blocs ne font rien en eux-mêmes. Ils sont juste assis là.

Ils commencent à être utilisés pour l'interaction avec les joueurs.

J'ai besoin de vérifier avec quels cubes la souris des utilisateurs interagit (survol, clic, etc.) et pour détecter les collisions lorsque le joueur se déplace.

Maintenant, j'ai eu un énorme décalage au début, parcourant chaque bloc.

J'ai réussi à réduire ce décalage, en parcourant tous les blocs et en trouvant quels blocs se trouvent dans une plage particulière du personnage, puis en bouclant uniquement ces blocs pour la détection de collision, etc.

Cependant, je vais toujours à un 2fps déprimant.

Quelqu'un at-il d'autres idées sur la façon dont je pourrais réduire ce décalage?

Btw, j'utilise XNA (C #) et oui, c'est 3d.


Voulez-vous dire comme Minecraft? C'est-à-dire, voxel?
The Communist Duck

4
Avez-vous regardé des octrois? en.wikipedia.org/wiki/Octree
bummzack

1
Avez-vous essayé de profiler votre jeu? Il peut montrer certains domaines clés où le plus de temps est consacré. Ce n'est peut-être pas ce que vous pensez.
deceleratedcaviar

2
Plutôt que de dessiner les 6 faces de chaque cube, vous ne pouvez dessiner que les faces qui ne sont en contact avec rien
David Ashmore

1
@David: Oui, ou il pourrait tout d'abord arrêter de faire un seul appel de tirage par cube, puis s'inquiéter des polygones individuels plus tard.
Olhovsky

Réponses:


20

On dirait que vous cherchez à en apprendre davantage sur les arbres!

Et je suis sérieux, si vous parcourez actuellement un tableau de tous vos cubes, alors vous devriez vraiment examiner différentes structures de données spatiales. Dans ce cas, la meilleure façon de réimaginer votre monde de cube est comme un arbre.

Avant d'entrer dans les raisons pour lesquelles, réfléchissons à notre problème. Nous recherchons une solution où, pour le moins de frais possible, nous pouvons récupérer une liste des cubes à proximité avec lesquels le joueur peut entrer en collision. Cette liste doit être aussi petite, mais précise que possible.

Maintenant, pour déterminer cette zone, nous devons mapper l'espace de coordonnées de notre joueur à l'espace de coordonnées de la carte du cube; c'est-à-dire que nous devons mapper la position en virgule flottante du lecteur sur un index discret du tableau multidimensionnel de cubes (par exemple, la notation pourrait être world[31][31][31], c'est-à-dire le milieu exact pour un tableau multidimensionnel 64 * 64 * 64).

Nous pourrions simplement calculer les blocs environnants en utilisant cette même indexation discrète, en échantillonnant peut-être uniquement les cubes voisins, mais cela nécessite toujours un recalcul constant et ne permet aucun objet dont l'emplacement n'est pas discret (c.-à-d. Qui peut ne pas correspondre au cube carte).

La situation idéale est un ensemble de seaux qui contiennent nos ensembles de cubes pour des sections particulières de notre carte de cubes, divisés également afin qu'au lieu de recalculer la zone environnante, nous entrons et sortons simplement de ces zones . Pour tout calcul non trivial, conserver nos données de cette manière pourrait éliminer l'itération de tous les cubes, et uniquement de ces ensembles individuels qui se trouvent à proximité.

La question est: comment mettre en œuvre cela?

Pour le monde 64 * 64 * 64, imaginez qu'il se décompose en 8 * 8 * 8 zones . Cela signifie que dans votre monde, vous aurez 8 zones par axe (X, Y, Z). Chacune de ces zones contiendra 8 cubes, facilement récupérables grâce à ce nouvel index simplifié.

Si vous deviez effectuer une opération sur un ensemble de cubes voisins, au lieu d'itérer chaque cube de votre monde, vous pouvez simplement parcourir ces zones , décomposant le nombre maximal d'itérations des 64 * 64 * 64 d'origine (262144) à seulement 520 (8 * 8 * 8 + 8).

Maintenant, effectuez un zoom arrière sur ce monde de zones et placez les zones dans des super-zones plus grandes ; où chaque super-zone contient 2 * 2 * 2 zones régulières . En tant que votre monde contient actuellement 512 (8 * 8 * 8) zones , nous pouvons briser les 8 * 8 * 8 zones en 64 (4 * 4 * 4) super-zones en divisant 8 zones par 2 zones par super-zone . En appliquant la même logique que ci-dessus, cela briserait les itérations maximales de 512 à 8 pour trouver la super-zone ; puis un maximum de 64 pour trouver la zone de progression(total max 72)! Vous pouvez voir comment cela vous fait déjà économiser beaucoup d'itérations (262144: 72).

Je suis sûr que vous pouvez voir maintenant à quel point les arbres sont utiles. Chaque zone est une branche sur l'arbre, avec chaque super-zone comme branche précédente. Vous parcourez simplement l'arbre pour trouver ce dont vous avez besoin; en utilisant des ensembles de données plus petits pour minimiser le coût global.

Le diagramme ci-dessous devrait vous aider à visualiser le concept. (image de Wikipedia: Octrees ): Octree

Avertissement:

Dans une configuration idéale comme ci-dessus, où votre monde de voxels est déjà disposé dans un tableau multidimensionnel de taille fixe, vous pouvez simplement interroger la position du joueur, puis indexer les blocs environnants avec un coût O (1)! (Voir l'explication d'Olhovskys) Mais cela devient plus difficile lorsque vous commencez à considérer que votre monde est rarement de taille fixe dans un jeu de voxels; et vous pourriez avoir besoin de votre structure de données pour pouvoir charger des super-zones entières du disque dur dans la mémoire. Contrairement à un tableau multidimensionnel de taille fixe, les arbres le permettent facilement sans trop de temps consacré aux algorithmes combinatoires.


Je dirais que je comprends, mais malheureusement je ne le sais pas. Les cases de collision des blocs ne bougent pas, seulement la case de collision du joueur. Et j'ai maintenant une méthode qui (sans parcourir tous les blocs) renvoie tous les blocs qui se trouvent dans un rayon de 5 blocs du joueur. Désolé pour les tracas, mais une clarification? Soit dit en passant, pour simplifier, pourriez-vous supposer que le monde est de 64 x 64 x 64?
Joel

Et je me déplace toujours à 5 images par seconde :(
Joel

J'ai reformulé ma réponse, faites-moi savoir si cela a aidé.
deceleratedcaviar

J'ai donc réduit les blocs dans lesquels le joueur pourrait être, en utilisant cette technique d'octree. Je suis presque sûr d'avoir compris. Mais proposez-vous d'utiliser ma détection de collision une fois que je l'ai réduite à une petite sélection de blocs, ou avez-vous une autre méthode?
Joel

2
À moins que le joueur ne soit très grand par rapport à la taille des cubes, vérifier la collision autour du joueur est probablement le moyen le plus rapide. Si le joueur n'occupe pas plus d'espace qu'un cube par exemple, alors il n'a qu'à vérifier au maximum 27 cubes environnants, pour trouver une collision. Cela ne nécessite pas d'arborescence car vous pouvez indexer directement dans ces emplacements de cube, en supposant qu'il stocke les cubes dans un tableau dans lequel il peut indexer, avec un emplacement alloué pour chaque emplacement de cube possible.
Olhovsky

13

Je suis d'accord avec la réponse de Daniels, en ce sens que l'itération à travers de grandes quantités de boîtes est la cause la plus probable, et qu'en utilisant le partitionnement spatial, vous pouvez accélérer le jeu beaucoup - mais le problème pourrait également être ailleurs et vous pourriez perdre votre temps .

Afin d'augmenter considérablement la vitesse de votre jeu, vous devez profiler votre code. Identifiez où se trouve le goulot d'étranglement, cela vous permettra d'apporter les plus grandes améliorations.

Il existe de nombreuses façons de profiler votre code, vous pouvez rouler votre propre classe d'analyse des performances (qui pourrait utiliser la classe Stopwatch (MSDN) ), ou vous pouvez utiliser PIX pour avoir une idée générale de l'occupation du CPU / GPU .

Vous pouvez également placer des marqueurs d'événement PIX dans votre code, qui s'afficheront sous forme de régions colorées dans les affichages PIX. Il n'y a pas d'interface C # officielle pour ces fonctions, mais ce fil vous montre comment créer vous-même une interface C #.


2
+1, totalement d'accord. Vous ne devriez pas chercher dans les algorithmes, vous devriez regarder le profilage de votre code. Je suppose que cela n'a rien à voir avec le fait que vous parcourez les blocs (ce n'est pas beaucoup), c'est ce que vous faites avec chaque bloc. En fin de compte, oui, vous devez avoir une meilleure méthode que la force brute, mais si vous ne pouvez pas gérer une simple itération sur des cubes 48x32x48, vous devez repenser ce que vous faites avec chaque cube, pas comment vous bouclez.
Tim Holt

4
@Tim: À moins que son joueur ne soit assez grand pour occuper un espace de 48x32x48, il ne devrait pas parcourir à peu près autant de cubes. S'il parcourt 73000 cubes par image, je peux vous dire sans faire de profilage, que cela vaut la peine pour lui de résoudre ce problème, ne serait-ce que pour apprendre à éviter de faire des dizaines de milliers d'itérations plus que sont nécessaires. Ce n'est pas ce que j'appellerais une optimisation micro ou prématurée.
Olhovsky

Mon joueur est inférieur à la taille d'un cube, mais peut être plus grand à un certain stade (mais pas beaucoup)
Joel

Randomman159: Ensuite, il vous suffit de tester contre ses 27 cubes environnants pour trouver une collision. Voir ma réponse.
Olhovsky

6

Si votre lecteur est grand par rapport à la taille des cubes, alors vous voulez probablement un octree ou une autre structure de partitionnement spatial, comme d'autres l'ont suggéré.

Cependant, si votre joueur est petit par rapport à la taille des cubes, alors le moyen le plus rapide pour détecter une collision avec les cubes est de faire une recherche linéaire simple de la zone autour du joueur.

Étant donné que votre joueur est plus petit que 1 cube, il vous suffit de tester la collision contre les 27 cubes voisins, tout au plus.

Cela suppose que vous stockez les cubes dans un tableau dans lequel vous pouvez indexer, avec un emplacement dans le tableau pour chaque cube.

Comme d'autres l'ont souligné, vous devez profiler votre code pour voir ce qui vous ralentit réellement.

Si je devais deviner cependant, je dirais que vous faites probablement un appel de tirage pour chaque cube, ce qui serait de loin votre goulot d'étranglement. Pour résoudre ce problème, vous devez examiner l'instanciation de la géométrie.


Ou vous pouvez avoir une «boîte englobante» qui entoure le lecteur, puis vous vérifiez simplement avec quels objets il entre en collision pour déterminer avec quels objets le joueur doit entrer en collision. Un bon moteur physique pourra faire toutes les optimisations pour vous; il permettra également à plus de «blocs» d'entrer en collision.
deceleratedcaviar

Personnellement, je ne voudrais pas compter sur les phases larges d'un moteur physique pour tester contre 73000 cubes pour moi, alors que je pourrais simplement écrire deux douzaines de lignes de code pour tester efficacement la collision pour moi. De plus, il n'a probablement pas de moteur physique à exploiter pour le moment.
Olhovsky

1

Une autre suggestion pour accélérer les choses: vos blocs sont approximativement fixes - cela signifie qu'il n'y a aucun moyen qu'un joueur puisse entrer en collision avec la plupart d'entre eux. Ajoutez un booléen aux blocs indiquant s'ils sont exposés ou non. (Cela peut être recalculé en regardant leurs voisins.) Un bloc qui n'est pas exposé n'a pas besoin d'être vérifié pour les collisions.

Il est évident que Minecraft fait quelque chose de similaire à cela - j'ai frappé un morceau non chargé une fois qui m'a donné une vue sur le monde - je pouvais voir à travers le sol solide, tout ce qui s'est révélé était les espaces ouverts (le côté le plus éloigné de c'était une surface exposée et donc rendue.)


-1

J'ai eu ce problème avec mon moteur voxel.

Solution: (beaucoup plus simple que les octets) Au lieu de parcourir tous les blocs, utilisez simplement une équation pour déterminer la position du bloc dans le tableau des blocs.

BlockIndex = (x * WorldWidth * WorldHeight) + (z * WorldHeight) + y;

Ensuite, si vous voulez voir si un bloc existe:

Blocks[BlockIndex].Type > -1;

Ou comme vous déterminez si le bloc existe.


Le problème ici est plus compliqué, car vous n'avez que 2 coordonnées pour votre souris, pour tester avec le monde 3D. Si la vue était de haut en bas, vous pourriez trouver 2 des 3 index de droite pour une position de souris, puis boucler pour la hauteur, en partant du haut - plus près de la caméra, et trouver la première occurrence d'un bloc.
Markus von Broady
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.