Ich schreibe meinen eigenen Klon von Minecraft (auch in Java geschrieben). Es funktioniert gerade großartig. Mit einem Betrachtungsabstand von 40 Metern kann ich mit meinem MacBook Pro 8,1 problemlos 60 FPS erreichen. (Intel i5 + Intel HD Graphics 3000). Wenn ich den Betrachtungsabstand auf 70 Meter stelle, erreiche ich nur 15-25 FPS. In der realen Minecraft kann ich die Sichtweite (= 256m) problemlos einstellen. Meine Frage ist also, was ich tun soll, um mein Spiel zu verbessern?
Die Optimierungen, die ich implementiert habe:
- Lasse nur lokale Chunks im Speicher (abhängig vom Betrachtungsabstand des Players)
- Frustum Culling (Erst auf den Brocken, dann auf den Blöcken)
- Zeichne nur wirklich sichtbare Flächen der Blöcke
- Verwenden von Listen pro Block, die die sichtbaren Blöcke enthalten. Blöcke, die sichtbar werden, fügen sich dieser Liste hinzu. Wenn sie unsichtbar werden, werden sie automatisch aus dieser Liste entfernt. Blöcke werden (in) sichtbar, indem ein Nachbarblock gebaut oder zerstört wird.
- Verwenden von Listen pro Block, die die Aktualisierungsblöcke enthalten. Gleicher Mechanismus wie die sichtbaren Sperrlisten.
- Verwenden Sie fast keine
new
Anweisungen innerhalb der Spielschleife. (Mein Spiel dauert ungefähr 20 Sekunden, bis der Garbage Collector aufgerufen wird.) - Ich verwende derzeit OpenGL-Anruflisten. (
glNewList()
,glEndList()
,glCallList()
) Für jede Seite einer Art von Block.
Momentan benutze ich überhaupt kein Beleuchtungssystem. Ich habe schon von VBO's gehört. Aber ich weiß nicht genau was es ist. Ich werde jedoch einige Nachforschungen anstellen. Verbessern sie die Leistung? Bevor ich VBOs implementiere, möchte ich versuchen, glCallLists()
eine Liste von Anruflisten zu verwenden und zu übergeben. Stattdessen tausendmal verwenden glCallList()
. (Ich möchte dies versuchen, weil ich denke, dass das echte MineCraft keine VBOs verwendet. Richtig?)
Gibt es andere Tricks, um die Leistung zu verbessern?
Die VisualVM-Profilerstellung hat mir dies gezeigt (Profilerstellung für nur 33 Frames mit einem Betrachtungsabstand von 70 Metern):
Profilierung mit 40 Metern (246 Frames):
Hinweis: Ich synchronisiere viele Methoden und Codeblöcke, da ich Blöcke in einem anderen Thread generiere. Ich denke, dass das Erlangen einer Sperre für ein Objekt ein Leistungsproblem ist, wenn man so viel in einer Spieleschleife macht (ich spreche natürlich von der Zeit, in der es nur eine Spieleschleife gibt und keine neuen Blöcke erzeugt werden). Ist das richtig?
Bearbeiten: Nach dem Entfernen einiger synchronised
Blöcke und einiger anderer kleiner Verbesserungen. Die Leistung ist schon viel besser. Hier sind meine neuen Profilierungsergebnisse mit 70 Metern:
Ich denke, es ist ziemlich klar, dass selectVisibleBlocks
hier das Problem ist.
Danke im Voraus!
Martijn
Update : Nach einigen zusätzlichen Verbesserungen (z. B. Verwenden von for-Schleifen anstelle von for-Schleifen, Puffern von Variablen außerhalb von Schleifen usw.) kann ich jetzt die Anzeigedistanz 60 ziemlich gut ausführen.
Ich denke, ich werde VBOs so schnell wie möglich implementieren.
PS: Der gesamte Quellcode ist auf GitHub verfügbar:
https://github.com/mcourteaux/CraftMania
quelle
Antworten:
Sie erwähnen das Ausmerzen einzelner Blöcke mit Kegelstumpf - werfen Sie das aus. Die meisten Rendering-Chunks sollten entweder vollständig sichtbar oder vollständig unsichtbar sein.
Minecraft erstellt nur dann eine Anzeigeliste / einen Vertex-Puffer neu (ich weiß nicht, welchen er verwendet), wenn ein Block in einem bestimmten Block geändert wird, und ich auch . Wenn Sie die Anzeigeliste bei jeder Änderung der Ansicht ändern, profitieren Sie nicht von Anzeigelisten.
Außerdem scheinen Sie Brocken von Welthöhe zu verwenden. Beachten Sie, dass Minecraft im Gegensatz zum Laden und Speichern kubische 16 × 16 × 16-Blöcke für seine Anzeigelisten verwendet. Wenn Sie das tun, gibt es noch weniger Gründe, einzelne Stücke zu entfernen.
(Hinweis: Ich habe den Code von Minecraft nicht untersucht. Alle diese Informationen sind entweder Hörensagen oder meine eigenen Schlussfolgerungen aus der Beobachtung von Minecrafts Rendering während des Spiels.)
Allgemeine Hinweise:
Denken Sie daran, dass Ihr Rendering auf zwei Prozessoren ausgeführt wird: CPU und GPU. Wenn Ihre Bildrate nicht ausreicht, ist der eine oder andere die begrenzende Ressource - Ihr Programm ist entweder CPU- oder GPU-gebunden (vorausgesetzt, es wird nicht ausgetauscht oder es treten Planungsprobleme auf).
Wenn Ihr Programm zu 100% mit CPU läuft (und keine andere unbegrenzte Aufgabe zu erledigen hat), dann arbeitet Ihre CPU zu viel. Sie sollten versuchen, die Aufgabe zu vereinfachen (z. B. weniger Culling), damit die GPU mehr leistet. Ich vermute sehr, dass dies Ihr Problem ist, wenn Sie Ihre Beschreibung geben.
Auf der anderen Seite sollten Sie überlegen, wie Sie weniger Daten senden oder weniger Pixel ausfüllen müssen, wenn die GPU das Limit darstellt (leider gibt es normalerweise keine geeigneten 0% -100% -Lastmonitore).
quelle
Was nennt Vec3f.set so viel? Wenn Sie jedes Bild von Grund auf neu rendern möchten, sollten Sie damit beginnen, es zu beschleunigen. Ich bin kein großer OpenGL-Benutzer und ich weiß nicht viel darüber, wie Minecraft rendert, aber es scheint, dass die von Ihnen verwendeten mathematischen Funktionen Sie gerade töten (sehen Sie sich nur an, wie viel Zeit Sie damit verbringen und wie oft Sie es tun) sie werden gerufen - der Tod durch tausend Schnitte, die sie rufen).
Im Idealfall wird Ihre Welt so segmentiert, dass Sie zu rendernde Objekte gruppieren, Vertex-Pufferobjekte erstellen und über mehrere Frames hinweg wiederverwenden können. Sie müssten ein VBO nur ändern, wenn sich die Welt, die es darstellt, irgendwie ändert (wie der Benutzer es bearbeitet). Sie können dann VBOs für das erstellen / zerstören, was Sie darstellen, da sie sichtbar werden, um den Speicherverbrauch gering zu halten. Sie würden nur den Treffer erhalten, da der VBO erstellt wurde, und nicht jeden Frame.
Wenn die Anzahl der "Aufrufe" in Ihrem Profil korrekt ist, rufen Sie sehr viele Dinge sehr oft an. (10 Millionen Anrufe bei Vec3f.set ... autsch!)
quelle
Meine Beschreibung (aus meinen eigenen Experimenten) gilt hier:
Was ist für das Voxel-Rendering effizienter: vorgefertigtes VBO oder ein Geometrie-Shader?
Minecraft und Ihr Code verwenden wahrscheinlich die feste Funktionspipeline. Ich habe mich selbst um GLSL bemüht, aber das Wesentliche ist meines Erachtens allgemein gültig:
(Aus dem Gedächtnis) Ich habe einen Kegelstumpf gemacht, der einen halben Block größer war als der Bildschirmkegelstumpf. Ich habe dann die Mittelpunkte jedes Blocks getestet ( Minecraft hat 16 * 16 * 128 Blöcke ).
Die Flächen in jeder haben Spannweiten in einem Elementarray-VBO (viele Flächen aus Blöcken teilen sich die gleiche VBO, bis sie "voll" sind; denken Sie, wie
malloc
diejenigen mit der gleichen Textur in der gleichen VBO, wenn möglich) und die Scheitelindizes für den Norden Gesichter, Südwände und so weiter sind eher benachbart als gemischt. Wenn ich zeichne, mache ich eineglDrawRangeElements
für die Nordwände, wobei die Normalen bereits projiziert und normalisiert sind, in Uniform. Dann mache ich die Südwände und so weiter, damit die Normalen in keinem VBO sind. Für jeden Chunk muss ich nur die sichtbaren Gesichter ausgeben - nur die in der Mitte des Bildschirms müssen zum Beispiel die linke und die rechte Seite zeichnen. Dies istGL_CULL_FACE
auf Anwendungsebene einfach .Die größte Beschleunigung, iirc, bestand darin, beim Polygonisieren der einzelnen Teile Innenflächen auszusondern.
Wichtig ist auch das Textur-Atlas- Management und das Sortieren von Gesichtern nach Textur und das Einfügen von Gesichtern mit derselben Textur in dasselbe VBO wie die von anderen Stücken. Sie möchten zu viele Texturänderungen vermeiden und die Flächen nach Textur sortieren und so weiter, um die Anzahl der Bereiche in der zu minimieren
glDrawRangeElements
. Das Zusammenführen benachbarter Flächen gleicher Kacheln zu größeren Rechtecken war ebenfalls eine große Sache. Ich spreche über das Zusammenführen in der anderen oben zitierten Antwort.Offensichtlich polygonisieren Sie nur die Chunks, die jemals sichtbar waren. Möglicherweise verwerfen Sie die Chunks, die lange Zeit nicht mehr sichtbar waren, und polygonisieren bearbeitete Chunks erneut (da dies im Vergleich zum Rendern selten vorkommt).
quelle
Woher kommen all deine Vergleiche (
BlockDistanceComparator
)? Wenn es sich um eine Sortierfunktion handelt, könnte diese durch eine Radix-Sortierung ersetzt werden (die asymptotisch schneller und nicht vergleichsbasiert ist)?Wenn Sie Ihre Zeitabläufe betrachten, wird Ihre
relativeToOrigin
Funktion für jedecompare
Funktion zweimal aufgerufen , auch wenn die Sortierung selbst nicht so schlecht ist. Alle diese Daten sollten einmal berechnet werden. Es sollte schneller sein, eine Hilfsstruktur zu sortieren, zund dann in Pseudocode
Tut mir leid, wenn dies keine gültige Java-Struktur ist (ich habe Java seit Undergrad nicht mehr angerührt), aber Sie haben hoffentlich eine Idee.
quelle
Yeah, benutze VBOs und CULL Faces, aber das gilt für so ziemlich jedes Spiel. Was Sie tun möchten, ist, den Würfel nur zu rendern, wenn er für den Spieler sichtbar ist, UND wenn sich die Blöcke auf eine bestimmte Weise berühren (sagen wir einen Block, den Sie nicht sehen können, weil er unterirdisch ist), fügen Sie die Scheitelpunkte der Blöcke hinzu und erstellen es ist fast wie ein "größerer Block" oder in Ihrem Fall ein Stück. Dies wird als "Greedy Meshing" bezeichnet und steigert die Leistung drastisch. Ich entwickle ein Spiel (Voxel-basiert) und es verwendet einen Greedy-Meshing-Algorithmus.
Anstatt alles so zu rendern:
Es macht es so:
Der Nachteil dabei ist, dass Sie mehr Berechnungen pro Chunk für den anfänglichen World Build durchführen müssen oder wenn der Spieler einen Block entfernt / hinzufügt.
So ziemlich jede Art von Voxel-Motor benötigt dies für eine gute Leistung.
Dabei wird geprüft, ob die Blockfläche eine andere Blockfläche berührt. In diesem Fall wird nur eine (oder keine) Blockfläche (n) gerendert. Es ist eine teure Sache, wenn Sie Brocken sehr schnell rendern.
quelle
Es scheint, dass Ihr Code in Objekten und Funktionsaufrufen ertrinkt. Nach den Zahlen zu urteilen, scheint es nicht so zu sein, als ob es irgendwelche Zwischenfälle gibt.
Sie könnten versuchen, eine andere Java-Umgebung zu finden oder einfach mit den Einstellungen der Java-Umgebung zu experimentieren, aber eine einfache Methode, Ihren Code nicht schnell, sondern viel langsamer zu machen, ist zumindest intern in Vec3f zu stoppen Kodierung OOO *. Machen Sie jede Methode eigenständig. Rufen Sie keine der anderen Methoden auf, nur um eine grundlegende Aufgabe auszuführen.
Bearbeiten: Während es überall Overhead gibt, scheint es, dass das Ordnen der Blöcke vor dem Rendern der schlechteste Leistungsfresser ist. Ist das wirklich notwendig? In diesem Fall sollten Sie wahrscheinlich zunächst eine Schleife durchlaufen, die Entfernung jedes Blocks zum Ursprung berechnen und danach sortieren.
* Übermäßig objektorientiert
quelle
You could also try to break down Math operations down to bitwise operators. If you have
128 / 16
, try to make a bitwise operator:128 << 4
. This will help a lot with your problems. Don't try to make things run at full speed. Make your game update at a rate of 60 or something, and even break that down for other things, but you would have to do destroying and or placing voxels or you would have to make a todo-list, which would bring down your fps. You could do an update rate of about 20 for entities. And something like 10 for world updates and or generation.quelle