J'écris mon propre clone de Minecraft (également écrit en Java). Cela fonctionne très bien en ce moment. Avec une distance de visualisation de 40 mètres, je peux facilement atteindre 60 ips sur mon MacBook Pro 8,1. (Intel i5 + Intel HD Graphics 3000). Mais si je mets la distance de vision sur 70 mètres, je n’atteins que 15-25 FPS. Dans le vrai Minecraft, je peux mettre le désordre de visionnage loin (= 256m) sans problème. Ma question est donc: que dois-je faire pour améliorer mon jeu?
Les optimisations que j'ai mises en place:
- Ne conservez que des morceaux locaux en mémoire (en fonction de la distance de visualisation du lecteur)
- Frustum culling (d'abord sur les morceaux, puis sur les blocs)
- Dessiner seulement les faces vraiment visibles des blocs
- Utilisation de listes par morceau contenant les blocs visibles. Les morceaux qui deviennent visibles s'ajouteront à cette liste. S'ils deviennent invisibles, ils sont automatiquement supprimés de cette liste. Les blocs deviennent (in) visibles en construisant ou en détruisant un bloc voisin.
- Utilisation de listes par morceau contenant les blocs de mise à jour. Même mécanisme que les listes de blocs visibles.
- N'utilisez presque pas d'
new
énoncés dans la boucle de jeu. (Mon jeu dure environ 20 secondes jusqu'à ce que le récupérateur de place soit appelé) - J'utilise les listes d'appels OpenGL pour le moment. (
glNewList()
,glEndList()
,glCallList()
) Pour chaque côté d'un type de bloc.
Actuellement, je n'utilise même pas de système d'éclairage. J'ai déjà entendu parler de VBO. Mais je ne sais pas exactement ce que c'est. Cependant, je vais faire des recherches à leur sujet. Est-ce qu'ils vont améliorer les performances? Avant d'implémenter les VBO, je veux essayer d'utiliser glCallLists()
et de transmettre une liste de listes d'appels. Au lieu d'utiliser mille fois glCallList()
. (Je veux essayer ceci, parce que je pense que le vrai MineCraft n'utilise pas de VBO. Correct?)
Existe-t-il d'autres astuces pour améliorer les performances?
Le profilage VisualVM m'a montré ceci (profilage pour seulement 33 images, avec une distance de visualisation de 70 mètres):
Profiler avec 40 mètres (246 images):
Remarque: Je synchronise beaucoup de méthodes et de blocs de code, car je génère des fragments dans un autre thread. Je pense qu'acquérir un verrou pour un objet est un problème de performance lorsqu'il effectue autant de choses dans une boucle de jeu (bien sûr, je parle du moment où il n'y a que la boucle de jeu et aucun nouveau fragment n'est généré). Est-ce correct?
Edit: Après avoir supprimé quelques synchronised
blocs et quelques autres petites améliorations. La performance est déjà bien meilleure. Voici mes nouveaux résultats de profilage avec 70 mètres:
Je pense qu'il est assez clair que selectVisibleBlocks
c'est le problème ici.
Merci d'avance!
Martijn
Mise à jour : après quelques améliorations supplémentaires (utilisation de boucles for à la place de chacune, mise en mémoire tampon des variables en dehors des boucles, etc.), je peux maintenant parcourir la distance de visualisation 60 plutôt bien.
Je pense que je vais mettre en œuvre les VBO dès que possible.
PS: tout le code source est disponible sur GitHub:
https://github.com/mcourteaux/CraftMania
la source
Réponses:
Vous mentionnez la suppression sélective de blocs individuels - essayez de le jeter. La plupart des blocs de rendu doivent être entièrement visibles ou totalement invisibles.
Minecraft ne reconstruit qu'un tampon liste / sommets d'affichage (je ne sais pas lequel il utilise) lorsqu'un bloc est modifié dans un bloc donné, et moi aussi . Si vous modifiez la liste d'affichage chaque fois que l'affichage change, vous ne bénéficiez pas des listes d'affichage.
En outre, vous semblez utiliser des morceaux de taille mondiale. Notez que Minecraft utilise des blocs cubiques 16 × 16 × 16 pour ses listes d’affichage, contrairement au chargement et à la sauvegarde. Si vous faites cela, il y a encore moins de raisons de supprimer des morceaux individuels.
(Remarque: je n'ai pas examiné le code de Minecraft. Toute cette information est du ouï-dire ou bien ma propre conclusion de l'observation du rendu de Minecraft pendant que je joue.)
Conseil plus général:
N'oubliez pas que votre rendu s'exécute sur deux processeurs: le processeur et le processeur graphique. Lorsque votre fréquence d'images est insuffisante, l' une ou l'autre des ressources est la ressource limitante - votre programme est lié au processeur ou au processeur graphique (en supposant qu'il ne permute pas ou n'ait pas de problèmes de planification).
Si votre programme fonctionne à 100% du processeur (et qu'aucune autre tâche non liée ne se termine), votre processeur effectue trop de travail. Vous devriez essayer de simplifier sa tâche (par exemple, faire moins de cueillage) en échange de faire en sorte que le GPU en fasse plus. Je soupçonne fortement que c'est votre problème, compte tenu de votre description.
D'un autre côté, si le GPU est la limite (malheureusement, il n'y a généralement pas de moniteur de charge pratique à 0% -100%), vous devriez réfléchir à la manière de l'envoyer moins de données ou de l'exiger pour remplir moins de pixels.
la source
Comment appelle-t-on tellement Vec3f.set? Si vous construisez ce que vous voulez rendre à partir de zéro à chaque image, vous voudrez certainement commencer à l’accélérer. Je ne suis pas un grand utilisateur OpenGL et je ne connais pas beaucoup le rendu de Minecraft, mais il semble que les fonctions mathématiques que vous utilisez vous tuent en ce moment (regardez le temps que vous y passez et le nombre de fois que vous y passez). on les appelle - la mort par mille coupures les appelant).
Idéalement, votre monde serait segmenté de manière à ce que vous puissiez regrouper les éléments à rendre ensemble, en créant des objets Vertex Buffer et en les réutilisant dans plusieurs cadres. Vous n'auriez besoin de modifier un VBO que si le monde qu'il représente change d'une manière ou d'une autre (comme l'utilisateur le modifie). Vous pouvez ensuite créer / détruire des VBO correspondant à ce que vous représentez, car cela permet de réduire la consommation de mémoire. Vous ne feriez que prendre la mesure, car le VBO a été créé plutôt que chaque image.
Si le nombre d'invocations est correct dans votre profil, vous appelez un nombre incalculable de fois. (10 millions d'appels vers Vec3f.set ... ouch!)
la source
Ma description (de ma propre expérimentation) ici est applicable:
Pour le rendu en voxels, quoi de plus efficace: un VBO préfabriqué ou un shader de géométrie?
Minecraft et votre code utilisent probablement le pipeline de fonctions fixes; mes propres efforts ont été avec GLSL mais l'essentiel est généralement applicable, je sens:
(De mémoire) J'ai créé un tronc d'un demi-bloc plus grand que celui de l'écran. J'ai ensuite testé les points centraux de chaque morceau ( minecraft a 16 * 16 * 128 blocs ).
Les faces dans chacune ont des étendues dans un VBO d’éléments (de nombreuses faces de morceaux partagent le même VBO jusqu’à ce qu’il soit «plein»; pensez comme
malloc
celles ayant la même texture dans le même VBO si possible) et les indices de sommet pour le nord. les faces, les faces sud, etc., sont adjacentes plutôt que mélangées. Quand je dessine, je fais unglDrawRangeElements
pour les faces nord, avec la normale déjà projetée et normalisée, dans un uniforme. Ensuite, je fais les faces sud et ainsi de suite, donc les normales ne figurent dans aucun VBO. Pour chaque morceau, je n'ai qu'à émettre les visages qui seront visibles - seuls ceux au centre de l'écran doivent dessiner les côtés gauche et droit, par exemple; c'est simpleGL_CULL_FACE
au niveau de l'application.La plus grande accélération, iirc, consistait à éliminer les faces intérieures lors de la polygonisation de chaque bloc.
Il est également important de gérer les atlas de texture , de trier les faces par texture et de placer les faces de même texture dans le même vbo que celles des autres morceaux. Vous souhaitez éviter de trop modifier la texture et de trier les faces par texture, etc. afin de réduire le nombre d'étendues dans le fichier
glDrawRangeElements
. La fusion de faces adjacentes de même mosaïque en de plus grands rectangles était également un problème important. Je parle de la fusion dans l'autre réponse citée ci-dessus.Évidemment, vous ne polygonisez que les morceaux qui ont déjà été visibles, vous pouvez supprimer les morceaux qui ne sont pas visibles depuis longtemps, et vous polygamiser de nouveau les morceaux qui ont été édités (car il s'agit d'un événement rare par rapport à leur rendu).
la source
D'où proviennent toutes vos comparaisons (
BlockDistanceComparator
)? S'il s'agit d'une fonction de tri, pourrait-elle être remplacée par une sorte de base (qui est asymptotiquement plus rapide et non basée sur la comparaison)?Si vous examinez vos horaires, même si le tri en soi n’est pas si mauvais, votre
relativeToOrigin
fonction est appelée deux fois pour chaquecompare
fonction; toutes ces données doivent être calculées une fois. Il devrait être plus rapide de trier une structure auxiliaire, par exemplepuis en pseudoCode
Désolé si ce n'est pas une structure Java valide (je n'ai pas touché Java depuis le premier cycle), mais j'espère que vous aurez l'idée.
la source
Oui, utilisez les VBO et CULL, mais cela vaut pour presque tous les matchs. Ce que vous voulez faire, c'est ne rendre le cube que s'il est visible par le joueur, ET si les blocs se touchent d'une manière spécifique (disons un bloc que vous ne pouvez pas voir parce qu'il est souterrain), vous ajoutez les sommets des blocs et faites c'est presque comme un "plus gros bloc", ou dans votre cas, un morceau. Cela s'appelle un maillage gourmand et augmente considérablement les performances. Je développe un jeu (basé sur voxel) qui utilise un algorithme de maillage gourmand.
Au lieu de tout rendre comme ceci:
Cela le rend comme ceci:
L'inconvénient à ceci est que vous devez faire plus de calculs par bloc lors de la construction initiale du monde, ou si le joueur supprime / ajoute un bloc.
à peu près tout type de moteur voxel en a besoin pour de bonnes performances.
Cela vérifie si la face du bloc touche une autre face du bloc, et si c'est le cas: ne restitue que comme une (ou zéro) face (s) du bloc. C'est une touche coûteuse lorsque vous rendez des morceaux très rapidement.
la source
Il semblerait que votre code soit noyé dans des objets et des appels de fonction. À en juger par les chiffres, il ne semble pas qu’il y ait d’intronisation.
Vous pouvez essayer de trouver un environnement Java différent ou simplement jouer avec les paramètres de celui que vous avez, mais un moyen simple et rapide de rendre votre code non pas rapide, mais beaucoup moins lent est au moins en interne dans Vec3f pour arrêter codant OOO *. Faites en sorte que chaque méthode soit autonome, n'appelez aucune des autres méthodes uniquement pour effectuer une tâche quelconque.
Edit: Bien qu’il y ait une surcharge partout, il semblerait que commander les blocs avant le rendu est le pire casse-tête. Est-ce vraiment nécessaire? Si tel est le cas, vous devriez probablement commencer par parcourir une boucle et calculer chaque distance entre les blocs jusqu'à l'origine, puis effectuer un tri.
* Trop orienté objet
la source
Vous pouvez également essayer de décomposer les opérations mathématiques en opérateurs au niveau des bits. Si vous avez
128 / 16
, essayez de faire un opérateur de bits:128 << 4
. Cela aidera beaucoup avec vos problèmes. N'essayez pas de faire tourner les choses à toute vitesse. Mettez votre jeu à jour à un rythme de 60 ou quelque chose d’autre, et décomposez-le pour d’autres choses, mais vous devrez détruire et / ou placer des voxels ou créer une liste de tâches qui réduirait votre fps. Vous pouvez faire un taux de mise à jour d'environ 20 pour les entités. Et quelque chose comme 10 pour les mises à jour mondiales et / ou la génération.la source