Wie kann ich eine Minecraft-artige Voxelwelt optimieren?


76

Ich fand Minecrafts wunderbare große Welten extrem langsam zu navigieren, selbst mit einem Quad-Core und einer fleischigen Grafikkarte.

Ich nehme an, Minecrafts Langsamkeit kommt von:

  • Java, da räumliche Partitionierung und Speicherverwaltung in nativem C ++ schneller sind.
  • Schwache Weltaufteilung.

Ich könnte in beiden Annahmen falsch liegen. Dies brachte mich jedoch dazu, darüber nachzudenken, wie ich große Voxelwelten am besten verwalten kann. Da es dich um eine echte 3D - Welt, wo ein Block in jedem Teil der Welt existieren kann, ist es im Grunde ein großer 3D - Array [x][y][z], wobei jeder Block in der Welt hat einen Typen (dh BlockType.Empty = 0, BlockType.Dirt = 1usw.)

Ich gehe davon aus, dass Sie für eine gute Leistung dieser Art von Welt Folgendes benötigen:

  • Verwenden Sie einen Baum einer Sorte ( oct / kd / bsp ), um alle Würfel aufzuteilen. Es sieht so aus, als wäre ein oct / kd die bessere Option, da Sie nur auf einer Ebene pro Würfel und nicht auf einer Ebene pro Dreieck partitionieren können.
  • Verwenden Sie einen Algorithmus, um herauszufinden, welche Blöcke derzeit sichtbar sind, da Blöcke, die sich näher am Benutzer befinden, die dahinter liegenden Blöcke verschleiern und das Rendern sinnlos machen können.
  • Halten Sie das Blockobjekt selbst leicht, damit Sie es schnell zu den Bäumen hinzufügen und daraus entfernen können.

Ich denke, es gibt keine richtige Antwort darauf, aber ich wäre interessiert, die Meinungen der Menschen zu diesem Thema zu sehen. Wie würden Sie die Leistung in einer großen voxelbasierten Welt verbessern?



2
Also, was fragst du eigentlich? Fragen Sie nach guten Ansätzen für das Management großer Welten oder nach Rückmeldungen zu Ihrem speziellen Ansatz oder nach Meinungen zum Thema des Managements großer Welten?
Doppelgreener

1
Good stuff , so weit ist es mehr über das, was ist der häufigste gemeinsame Ansatz auf diese Art von Dingen. Ich bin nicht speziell auf Rückmeldungen zu meiner Herangehensweise fixiert, da alles, was ich vorgeschlagen habe, das ist, was ich logischerweise erwarten würde. Ich wollte eigentlich nur mehr Informationen zu diesem Thema und nach ein paar Suchen war nicht viel zu sehen. Ich denke , meine Frage ist nicht nur um Leistung zu machen , sondern darüber , wie eine so große Menge an Daten zu verwalten ... wie die Bereiche etc Chunking
SomeXnaChump

2
Machen Sie sich also klar und fügen Sie Ihrer Nachricht eine Frage hinzu, damit wir wissen, welche Frage wir beantworten. ;)
doppelgreener

3
Was meinst du mit "extrem langsam zu navigieren"? Es gibt definitiv eine gewisse Verlangsamung, wenn das Spiel neues Terrain generiert, aber danach meistert Minecraft das Terrain ziemlich gut.
thedaian

Antworten:


106

Voxel Engine Rocks

Voxel-Motor Gras

In Bezug auf Java vs C ++ habe ich in beiden Versionen eine Voxel-Engine geschrieben (C ++ - Version siehe oben). Ich schreibe auch Voxel-Motoren seit 2004 (als sie nicht Mode waren). :) Ich kann mit ein wenig Zögern sagen, dass die C ++ - Leistung weit überlegen ist (aber es ist auch schwieriger zu programmieren). Es geht weniger um die Rechengeschwindigkeit als vielmehr um die Speicherverwaltung. Zweifellos ist C (++) die zu übertreffende Sprache, wenn Sie so viele Daten wie in einer Voxel-Welt zuweisen / freigeben. jedochsollten Sie über Ihr Ziel nachdenken. Wenn Leistung Ihre höchste Priorität ist, fahren Sie mit C ++ fort. Wenn Sie nur ein Spiel schreiben möchten, bei dem die Leistung auf dem neuesten Stand ist, ist Java definitiv akzeptabel (wie Minecraft beweist). Es gibt viele Trivial- / Edge-Fälle, aber im Allgemeinen können Sie davon ausgehen, dass Java etwa 1,75-2,0-mal langsamer läuft als (gut geschriebenes) C ++. Sie können hier eine schlecht optimierte, ältere Version meiner Engine in Aktion sehen (EDIT: neuere Version hier ). Während die Chunk-Generierung langsam erscheint, sollten Sie bedenken, dass 3D-Voronoi-Diagramme volumetrisch generiert werden und Oberflächennormalen, Beleuchtung, AO und Schatten auf der CPU mit Brute-Force-Methoden berechnet werden. Ich habe verschiedene Techniken ausprobiert und kann mit verschiedenen Caching- und Instanzentechniken ca. 100x schnellere Chunk-Generierung erzielen.

Um den Rest Ihrer Frage zu beantworten, gibt es viele Möglichkeiten, die Leistung zu verbessern.

  1. Caching. Wo immer Sie können, sollten Sie die Daten einmal berechnen. Zum Beispiel brenne ich die Beleuchtung in die Szene ein. Es könnte eine dynamische Beleuchtung (im Bildschirmbereich, als Nachbearbeitung) verwenden, aber das Einbrennen der Beleuchtung bedeutet, dass ich die Normalen für die Dreiecke nicht einhalten muss, was bedeutet, dass ...
  2. Übergeben Sie so wenig Daten wie möglich an die Grafikkarte. Eine Sache, die die Leute gerne vergessen, ist, dass je mehr Daten Sie an die GPU übergeben, desto mehr Zeit wird benötigt. Ich übergebe in einer einzelnen Farbe und einer Scheitelpunktposition. Wenn ich Tag / Nacht-Zyklen machen möchte, kann ich einfach eine Farbkorrektur durchführen oder die Szene neu berechnen, wenn sich die Sonne allmählich ändert.

  3. Da die Weitergabe von Daten an die GPU so teuer ist, ist es möglich, eine Engine in Software zu schreiben, die in mancher Hinsicht schneller ist. Der Vorteil von Software ist, dass sie alle Arten von Datenmanipulationen / Speicherzugriffen ausführen kann, die auf einer GPU einfach nicht möglich sind.

  4. Spielen Sie mit der Losgröße. Wenn Sie eine GPU verwenden, kann die Leistung dramatisch variieren, je nachdem, wie groß jedes Vertex-Array ist, das Sie übergeben. Spielen Sie entsprechend mit der Größe der Chunks (wenn Sie Chunks verwenden). Ich habe festgestellt, dass 64x64x64 Chunks ziemlich gut funktionieren. Egal was passiert, halten Sie Ihre Stücke kubisch (keine rechteckigen Prismen). Dadurch werden die Codierung und verschiedene Vorgänge (z. B. Transformationen) einfacher und in einigen Fällen leistungsfähiger. Wenn Sie nur einen Wert für die Länge jeder Dimension speichern, beachten Sie, dass dies zwei Register weniger sind, die während der Berechnung vertauscht werden.

  5. Betrachten Sie Anzeigelisten (für OpenGL). Obwohl sie der "alte" Weg sind, können sie schneller sein. Sie müssen eine Anzeigeliste in eine Variable backen ... Wenn Sie Anzeigelisten-Erstellungsvorgänge in Echtzeit aufrufen, ist dies gottlos langsam. Wie ist eine Anzeigeliste schneller? Es wird nur der Status im Vergleich zu Attributen pro Scheitelpunkt aktualisiert. Dies bedeutet, dass ich bis zu sechs Gesichter und dann eine Farbe (gegenüber einer Farbe für jeden Scheitelpunkt des Voxels) übergeben kann. Wenn Sie GL_QUADS und kubische Voxel verwenden, können Sie bis zu 20 Byte (160 Bit) pro Voxel einsparen! (15 Bytes ohne Alpha, obwohl normalerweise 4 Bytes ausgerichtet bleiben sollen.)

  6. Ich verwende eine Brute-Force-Methode zum Rendern von "Chunks" oder Datenseiten, was eine übliche Technik ist. Im Gegensatz zu Octrees ist es viel einfacher / schneller, die Daten zu lesen / zu verarbeiten, obwohl es viel weniger speicherfreundlich ist (heutzutage kann man jedoch 64 Gigabyte Speicher für 200-300 US-Dollar erhalten) ... nicht, dass der durchschnittliche Benutzer das hat. Offensichtlich können Sie nicht ein einziges großes Array für die ganze Welt zuweisen (ein Satz von 1024 x 1024 x 1024 Voxeln entspricht 4 Gigabyte Arbeitsspeicher, vorausgesetzt, ein 32-Bit-Int pro Voxel wird verwendet). Sie ordnen also viele kleine Arrays zu, basierend auf ihrer Nähe zum Betrachter. Sie können die Daten auch zuordnen, die erforderliche Anzeigeliste abrufen und dann die Daten sichern, um Speicherplatz zu sparen. Ich denke, die ideale Kombination könnte darin bestehen, einen hybriden Ansatz aus Octrees und Arrays zu verwenden - speichern Sie die Daten in einem Array, wenn Sie die prozedurale Generierung der Welt, der Beleuchtung usw. durchführen.

  7. Nah / Fern rendern ... ein Pixelausschnitt spart Zeit. Die GPU wirft ein Pixel, wenn sie den Tiefenpuffertest nicht besteht.

  8. Rendern Sie nur Teile / Seiten im Ansichtsfenster (selbsterklärend). Auch wenn die GPU weiß, wie Polgyons außerhalb des Ansichtsfensters abgeschnitten werden, dauert das Übergeben dieser Daten noch einige Zeit. Ich weiß nicht, was die effizienteste Struktur dafür wäre ("schade", ich habe noch nie einen BSP-Baum geschrieben), aber selbst ein einfacher Raycast auf Blockbasis könnte die Leistung verbessern, und Tests gegen den Betrachtungskegel würden dies offensichtlich tun Zeit sparen.

  9. Offensichtliche Informationen, aber für Anfänger: Entfernen Sie jedes einzelne Polygon, das sich nicht auf der Oberfläche befindet - dh wenn ein Voxel aus sechs Flächen besteht, entfernen Sie die Flächen, die niemals gerendert werden (berühren ein anderes Voxel).

  10. Grundsätzlich gilt: CACHE LOCALITY! Wenn Sie die Dinge lokal im Cache halten können (auch für eine kurze Zeit), wird dies einen enormen Unterschied bedeuten. Dies bedeutet, dass Sie Ihre Daten kongruent halten (in derselben Speicherregion) und nicht zu oft zwischen Speicherbereichen wechseln, um sie zu verarbeiten Bearbeiten Sie im Idealfall einen Block pro Thread und behalten Sie diesen Speicher ausschließlich für den Thread bei. Dies gilt nicht nur für den CPU-Cache. Stellen Sie sich die Cache-Hierarchie wie folgt vor (am langsamsten bis am schnellsten): Netzwerk (Cloud / Datenbank / usw.) -> Festplatte (besorgen Sie sich eine SSD, falls Sie noch keine haben), RAM (besorgen Sie sich einen Dreifachkanal oder mehr RAM, falls Sie noch keine haben), CPU-Cache (s), Register. Versuchen Sie, Ihre Daten zu behalten das letztere Ende, und tauschen Sie es nicht mehr als Sie müssen.

  11. Einfädeln. Tu es. Voxel-Welten eignen sich gut zum Threading, da jeder Teil (meistens) unabhängig von anderen berechnet werden kann ... Ich sah buchstäblich eine fast 4-fache Verbesserung (gegenüber einem 4-Kern-, 8-Thread-Core i7) bei der Erstellung der prozeduralen Welt Routinen zum Einfädeln.

  12. Verwenden Sie keine char / byte-Datentypen. Oder Shorts. Ihr Durchschnittsverbraucher wird (wie Sie wahrscheinlich auch) über einen modernen AMD- oder Intel-Prozessor verfügen. Diese Prozessoren haben keine 8-Bit-Register. Sie berechnen Bytes, indem sie sie in einen 32-Bit-Slot stecken und sie dann (möglicherweise) zurück in den Speicher konvertieren. Ihr Compiler kann alle Arten von Voodoo ausführen, aber wenn Sie eine 32- oder 64-Bit-Zahl verwenden, erhalten Sie die vorhersehbarsten (und schnellsten) Ergebnisse. Ebenso benötigt ein "Bool" -Wert nicht 1 Bit; Der Compiler verwendet häufig volle 32 Bit für einen Bool. Es kann verlockend sein, bestimmte Arten der Komprimierung Ihrer Daten vorzunehmen. Beispielsweise könnten Sie 8 Voxel als einzelne Zahl (2 ^ 8 = 256 Kombinationen) speichern, wenn sie alle vom selben Typ / von derselben Farbe wären. Sie müssen jedoch über die Konsequenzen nachdenken - es könnte eine Menge Speicher sparen, Aber es kann auch die Leistung beeinträchtigen, selbst bei einer kleinen Dekomprimierungszeit, da selbst diese kleine Menge an zusätzlicher Zeit kubisch mit der Größe Ihrer Welt skaliert. Stellen Sie sich vor, Sie berechnen einen Raycast. Für jeden Schritt des Raycasts müssten Sie den Dekomprimierungsalgorithmus ausführen (es sei denn, Sie haben eine clevere Methode gefunden, um die Berechnung für 8 Voxel in einem Strahlschritt zu verallgemeinern).

  13. Wie Jose Chavez erwähnt, kann das Muster des Fliegengewichts nützlich sein. So wie Sie eine Bitmap verwenden würden, um ein Plättchen in einem 2D-Spiel darzustellen, können Sie Ihre Welt aus mehreren 3D-Plättchentypen (oder Blocktypen) erstellen. Der Nachteil dabei ist die Wiederholung von Texturen, aber Sie können dies verbessern, indem Sie Varianztexturen verwenden, die zusammenpassen. Als Faustregel möchten Sie Instanzen verwenden, wo immer Sie können.

  14. Vermeiden Sie die Verarbeitung von Scheitelpunkten und Pixeln im Shader, wenn Sie die Geometrie ausgeben. In einer Voxel-Engine sind zwangsläufig viele Dreiecke vorhanden, sodass selbst ein einfacher Pixel-Shader die Renderzeit erheblich verkürzen kann. Es ist besser, in einen Puffer zu rendern, als Pixel-Shader als Nachbearbeitung. Wenn Sie das nicht können, versuchen Sie, Berechnungen in Ihrem Vertex-Shader durchzuführen. Andere Berechnungen sollten nach Möglichkeit in die Eckendaten eingearbeitet werden. Zusätzliche Durchläufe werden sehr teuer, wenn Sie die gesamte Geometrie neu rendern müssen (z. B. Schatten- oder Umgebungszuordnung). Manchmal ist es besser, eine dynamische Szene zugunsten von detaillierteren Details aufzugeben. Wenn Ihr Spiel veränderbare Szenen enthält (dh zerstörbares Gelände), können Sie die Szene immer neu berechnen, wenn die Dinge zerstört werden. Die Neukompilierung ist nicht teuer und sollte weniger als eine Sekunde dauern.

  15. Wickeln Sie Ihre Loops ab und halten Sie die Arrays flach! Mach das nicht:

    for (i = 0; i < chunkLength; i++) {
     for (j = 0; j < chunkLength; j++) {
      for (k = 0; k < chunkLength; k++) {
       MyData[i][j][k] = newVal;
      }
     }
    }
    //Instead, do this:
    for (i = 0; i < chunkLengthCubed; i++) {
     //figure out x, y, z index of chunk using modulus and div operators on i
     //myData should have chunkLengthCubed number of indices, obviously
     myData[i] = newVal;
    }

    EDIT: Durch umfangreichere Tests habe ich festgestellt, dass dies falsch sein kann. Verwenden Sie den Fall, der für Ihr Szenario am besten geeignet ist. Generell sollten Arrays flach sein, aber die Verwendung von Schleifen mit mehreren Indizes kann je nach Fall oft schneller sein

EDIT 2: Wenn Sie Multi-Index-Schleifen verwenden, schleifen Sie am besten in der Reihenfolge z, y, x und nicht umgekehrt. Ihr Compiler könnte dies optimieren, aber ich wäre überrascht, wenn dies der Fall wäre. Dies maximiert die Effizienz des Speicherzugriffs und der Lokalität.

for (k < 0; k < volumePitch; k++) {
    for (j = 0; j < volumePitch; j++) {
        for (i = 0; i < volumePitch; i++) {
            myIndex = k*volumePitch*volumePitch + j*volumePitch + i;
        }
    }
}
  1. Manchmal muss man Annahmen, Verallgemeinerungen und Opfer bringen. Das Beste, was Sie machen können, ist anzunehmen, dass der größte Teil Ihrer Welt vollständig statisch ist und sich nur alle paar tausend Frames ändert. Für die animierten Teile der Welt können diese in einem separaten Durchgang durchgeführt werden. Nehmen Sie auch an, dass der größte Teil Ihrer Welt völlig undurchsichtig ist. Transparente Objekte können in einem separaten Durchgang gerendert werden. Angenommen, die Texturen variieren nur alle x Einheiten oder Objekte können nur in x-Schritten platziert werden. Angenommen, eine feste Weltgröße ... so verlockend eine unendliche Welt ist, kann sie zu unvorhersehbaren Systemanforderungen führen. Um zum Beispiel die Erzeugung des Voronoi-Musters in den darüber liegenden Felsen zu vereinfachen, nahm ich an, dass jeder Voronoi-Mittelpunkt in einem einheitlichen Raster mit einem leichten Versatz (mit anderen Worten, implizites geometrisches Hashing) lag. Angenommen, eine Welt, die nicht umhüllt (Kanten hat). Dies kann viele Komplexitäten vereinfachen, die durch ein Umbruchkoordinatensystem mit minimalen Kosten für die Benutzererfahrung eingeführt werden.

Weitere Informationen zu meinen Implementierungen finden Sie auf meiner Website


9
+1. Nette Geste mit Bildern oben als Anreiz zum Lesen des Aufsatzes. Nachdem ich den Aufsatz gelesen habe, kann ich sagen, dass sie nicht gebraucht wurden und es sich gelohnt hat. ;)
George Duckett

Danke - ein Bild sagt mehr als tausend Worte. :) Abgesehen davon, dass meine Textwand weniger einschüchternd ist, wollte ich den Lesern eine Vorstellung davon geben, wie viele Voxel man mit den beschriebenen Techniken mit einer angemessenen Geschwindigkeit rendern kann.
Gavan Woolery

14
Ich wünschte immer noch, SE würde es erlauben, bestimmte Antworten zu favorisieren.
Joltmode

2
@PatrickMoriarty # 15 ist ein ziemlich häufiger Trick. Angenommen, Ihr Compiler führt diese Optimierung nicht durch (möglicherweise wird die Schleife entrollt, ein mehrdimensionales Array wird jedoch wahrscheinlich nicht komprimiert). Sie möchten alle Ihre Daten für das Caching im selben zusammenhängenden Speicherbereich speichern. Ein mehrdimensionales Array kann (möglicherweise) über mehrere Bereiche verteilt sein, da es sich um ein Array von Zeigern handelt. Denken Sie beim Abrollen der Schleife daran, wie der kompilierte Code aussieht. Um die geringsten Register- und Cache-Auslagerungen zu erzielen, möchten Sie die wenigsten Variablen / Anweisungen erzeugen. Was glaubst du kompiliert in mehr?
Gavan Woolery

2
Während einige der Punkte hier gut sind, insbesondere in Bezug auf Caching, Threading und Minimierung der GPU-Übertragung, sind einige davon furchtbar ungenau. 5: Verwenden Sie IMMER VBOs / VAOs anstelle von Anzeigelisten. 6: Mehr RAM benötigt nur mehr Bandbreite. Mit führt zu 12: Das genaue Gegenteil gilt für modernen Speicher, bei dem jedes gespeicherte Byte die Wahrscheinlichkeit erhöht, dass mehr Daten in den Cache passen. 14: In Minecraft gibt es mehr Scheitelpunkte als Pixel (alle diese entfernten Würfel). Verschieben Sie daher die Berechnung in den Pixel-Shader, nicht in den FROM-Bereich, vorzugsweise mit verzögerter Schattierung.

7

Es gibt eine Menge Dinge, die Minecraft effizienter machen könnte. Minecraft lädt beispielsweise ganze vertikale Pfeiler mit einer Größe von ca. 16 x 16 Kacheln und rendert sie. Ich finde es sehr ineffizient, so viele Kacheln unnötig zu verschicken und zu rendern. Aber ich glaube nicht, dass die Wahl der Sprache wichtig ist.

Java kann recht schnell sein, aber für etwas, das sich an diesen Daten orientiert, hat C ++ einen großen Vorteil mit deutlich geringerem Overhead für den Zugriff auf Arrays und die Arbeit innerhalb von Bytes. Auf der anderen Seite ist es viel einfacher, das Threading auf allen Plattformen in Java durchzuführen. Wenn Sie nicht vorhaben, OpenMP oder OpenCL zu verwenden, werden Sie diese Bequemlichkeit in C ++ nicht finden.

Mein ideales System wäre eine etwas komplexere Hierarchie.

Tile ist eine Einheit, in der wahrscheinlich 4 Byte Informationen wie Materialtyp und Beleuchtung gespeichert sind.

Das Segment wäre ein 32x32x32-Kachelblock.

  1. Flags würden für jede der sechs Seiten gesetzt, wenn die gesamte Seite aus massiven Blöcken besteht. Dies würde es dem Renderer ermöglichen, Segmente hinter diesem Segment zu verschließen. Minecraft scheint derzeit keine Okklusionstests durchzuführen. Es wurde jedoch erwähnt, dass Hardware-Okklusions-Culling verfügbar ist, das teuer, aber besser sein kann, als massive Mengen von Polygonen auf Low-End-Karten zu rendern.
  2. Segmente werden nur während der Aktivität in den Speicher geladen (Spieler, NPCs, Wasserphysik, Baumwachstum usw.). Andernfalls würden sie direkt und trotzdem komprimiert von der Festplatte an die Clients gesendet.

Sektoren wären 16x16x8 Segmentblöcke.

  1. Sektoren würden das höchste Segment für jede vertikale Spalte verfolgen, so dass höhere Segmente schnell als leer bestimmt werden können.
  2. Es würde auch das unterste verdeckte Segment verfolgen, so dass jedes Segment, das von der Oberfläche gerendert werden muss, schnell erfasst werden könnte.
  3. Die Sektoren würden auch verfolgen, wann jedes Segment das nächste Mal aktualisiert werden muss (Wasserphysik, Baumwachstum usw.). Auf diese Weise würde das Laden in jedem Sektor ausreichen, um die Welt am Leben zu erhalten, und nur in Segmenten, die lang genug sind, um ihre Aufgaben zu erfüllen.
  4. Alle Unternehmenspositionen würden in Bezug auf den Sektor verfolgt. Dies würde die in Minecraft vorhandenen Gleitkommafehler verhindern, wenn Sie sehr weit vom Kartenmittelpunkt entfernt fahren.

Die Welt wäre eine unendliche Karte von Sektoren.

  1. Die Welt wäre für die Verwaltung der Sektoren und ihrer nächsten Aktualisierungen verantwortlich.
  2. Die Welt würde präventiv Segmente an die Spieler auf ihren potenziellen Pfaden senden. Minecraft sendet reaktiv Segmente, die der Client anfordert, was zu einer Verzögerung führt.

Ich mag diese Idee im Allgemeinen, aber wie würden Sie intern die Sektoren der Welt abbilden ?
Clashsoft

Während ein Array die beste Lösung für Kacheln in Segmenten und Segmenten in Sektoren wäre, würden Sektoren in der Welt etwas anderes benötigen, um eine unendliche Kartengröße zu ermöglichen. Mein Vorschlag wäre, eine Hash-Tabelle (Pseudo Dictionary <Vector2i, Sector>) zu verwenden, die XY-Koordinaten für den Hash verwendet. Dann kann die Welt einfach einen Sektor an vorgegebenen Koordinaten nachschlagen.
Josh Brown

6

Minecraft ist ziemlich schnell, auch auf meinem 2-Core. Java scheint hier kein einschränkender Faktor zu sein, obwohl es ein wenig Serververzögerung gibt. Lokale Spiele scheinen besser zu laufen, daher gehe ich hier von Ineffizienzen aus.

In Bezug auf Ihre Frage hat Notch (Minecraft-Autor) ausführlich über die Technologie gebloggt. Insbesondere wird die Welt in "Chunks" gespeichert (diese werden manchmal angezeigt, insbesondere wenn einer fehlt, da die Welt noch nicht ausgefüllt ist.). Daher besteht die erste Optimierung darin, zu entscheiden, ob ein Chunk angezeigt werden kann oder nicht .

Wie Sie vermutet haben, muss die App innerhalb eines Blocks entscheiden, ob ein Block sichtbar ist oder nicht, basierend darauf, ob er von anderen Blöcken verdeckt wird oder nicht.

Beachten Sie auch, dass es Block-GESICHTER gibt, von denen angenommen werden kann, dass sie nicht zu sehen sind, da sie entweder verdeckt sind (dh ein anderer Block bedeckt das Gesicht) oder in welche Richtung die Kamera zeigt (wenn die Kamera nach Norden zeigt, können Sie dies tun) sehe die Nordwand von KEINEN Blöcken!)

Gängige Techniken würden auch beinhalten, keine separaten Blockobjekte zu behalten, sondern einen "Block" von Blocktypen mit einem einzelnen Prototypblock für jeden Block zusammen mit einem minimalen Satz von Daten, um zu beschreiben, wie dieser Block benutzerdefiniert sein kann. Zum Beispiel gibt es keine benutzerdefinierten Granitblöcke (die ich kenne), aber Wasser hat Daten, die angeben, wie tief es entlang jeder Seitenfläche ist, aus denen man seine Fließrichtung berechnen kann.

Ihre Frage ist nicht klar, ob Sie die Rendergeschwindigkeit, die Datengröße oder was optimieren möchten. Klarstellung wäre da hilfreich.


4
"Klumpen" werden üblicherweise als Brocken bezeichnet.
Marco

Guter Fang (+1); Antwort aktualisiert. (War ursprünglich für die Erinnerung zu tun, und das richtige Wort vergessen.)
Olie

Die Ineffizienzen, auf die Sie sich beziehen, werden auch als "das Netzwerk" bezeichnet, das selbst bei der Kommunikation mit denselben Endpunkten niemals zweimal auf die gleiche Weise agiert.
Edwin Buck

4

Hier sind nur ein paar allgemeine Informationen und Ratschläge, die ich als, ähm, übererfahrener Minecraft-Modder geben kann (der Ihnen vielleicht zumindest teilweise eine Anleitung gibt).

Der Grund, warum Minecraft langsam ist, hat viel mit fragwürdigen, einfachen Entwurfsentscheidungen zu tun. Wenn beispielsweise ein Block durch Positionieren referenziert wird, validiert das Spiel die Koordinaten mit etwa 7 if-Anweisungen, um sicherzustellen, dass er nicht außerhalb der Grenzen liegt . Darüber hinaus gibt es keine Möglichkeit, einen 'Block' (eine 16x16x256-Einheit, mit der das Spiel arbeitet) zu erfassen und dann direkt auf Blöcke zu verweisen, um Cache-Lookups und ähm, alberne Validierungsprobleme zu umgehen (iow, jede Blockreferenz beinhaltet auch) Unter anderem ein Chunk-Lookup.) In meinem Mod habe ich eine Möglichkeit geschaffen, das Array von Blöcken direkt zu greifen und zu ändern, was die massive Dungeon-Generierung von unspielbar verzögert auf unbemerkt schnell erhöht hat.

BEARBEITEN: Behauptung entfernt, dass das Deklarieren von Variablen in einem anderen Bereich zu Leistungsverbesserungen führte. Dies scheint jedoch nicht der Fall zu sein. Ich glaube damals, dass ich dieses Ergebnis mit etwas anderem in Verbindung gebracht habe, mit dem ich experimentiert habe (insbesondere das Entfernen von Casts zwischen Doubles und Floats in explosionsbezogenem Code durch Konsolidieren zu Doubles ... das hatte verständlicherweise einen enormen Einfluss!)

Auch wenn es nicht der Bereich ist, in dem ich viel Zeit verbringe, ist der Großteil der Leistungsdrosselung in Minecraft ein Problem beim Rendern (ungefähr 75% der Spielzeit ist auf meinem System dafür vorgesehen). Natürlich ist es Ihnen egal, ob das Problem darin besteht, mehr Spieler im Mehrspielermodus zu unterstützen (Server rendert nichts), aber es ist wichtig, inwieweit alle Computer überhaupt spielen können.

Egal für welche Sprache Sie sich entscheiden, versuchen Sie, sich mit den Implementierungs- / Low-Level-Details vertraut zu machen, denn selbst ein kleines Detail in einem solchen Projekt könnte den Unterschied ausmachen (ein Beispiel für mich in C ++ war "Kann der Compiler statisch inline arbeiten?" Zeiger? "Ja, das ist möglich! Hat einen unglaublichen Unterschied in einem der Projekte bewirkt, an denen ich gearbeitet habe, da ich weniger Code und den Vorteil von Inlining hatte.)

Ich mag diese Antwort wirklich nicht, weil sie das High-Level-Design schwierig macht, aber es ist die schmerzhafte Wahrheit, wenn es um Leistung geht. Hoffe, Sie fanden das hilfreich!

Außerdem enthält Gavins Antwort einige Details, die ich nicht wiederholen wollte (und noch viel mehr! Er ist in diesem Thema eindeutig sachkundiger als ich), und ich stimme ihm größtenteils zu. Ich muss mit seinem Kommentar zu Prozessoren und kürzeren variablen Größen experimentieren. Davon habe ich noch nie gehört. Ich möchte mir selbst beweisen, dass es wahr ist!


2

Die Sache ist zu überlegen, wie Sie zuerst die Daten laden würden. Wenn Sie Ihre Kartendaten bei Bedarf in den Speicher streamen, gibt es eine natürliche Grenze für das Rendern. Dies ist bereits eine Leistungsverbesserung beim Rendern.

Was Sie mit diesen Daten anfangen, liegt dann bei Ihnen. Für die GFX-Leistung können Sie dann mithilfe von Ausschneiden ausgeblendete Objekte, Objekte, die zu klein sind, um sichtbar zu sein, usw. ausschneiden.

Wenn Sie dann nur nach Grafikleistungstechniken suchen, finden Sie bestimmt eine Menge Dinge im Internet.


1

Sehenswert ist das Flyweight- Designmuster. Ich glaube, die meisten Antworten hier beziehen sich auf die eine oder andere Weise auf dieses Entwurfsmuster.

Ich kenne die genaue Methode, mit der Minecraft den Speicher für jeden Blocktyp minimiert, nicht. Dies ist jedoch eine mögliche Methode für Ihr Spiel. Die Idee ist, dass nur ein Objekt wie ein Prototypobjekt Informationen zu allen Blöcken enthält. Der einzige Unterschied wäre die Position jedes Blocks.

Aber auch der Standort kann minimiert werden: Wenn Sie wissen, dass es sich bei einem Landblock um einen Typ handelt, können Sie die Abmessungen dieses Landes als einen riesigen Block mit einem Satz von Standortdaten speichern.

Die einzige Möglichkeit, dies zu wissen, besteht offensichtlich darin, mit der Implementierung Ihrer eigenen Software zu beginnen und einige Speichertests für die Leistung durchzuführen. Lass uns wissen, wie es geht!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.