Ich versuche, eine kleine Voxel-Engine zu schreiben, weil es Spaß macht, aber ich habe Mühe, den besten Weg zu finden, um die tatsächlichen Voxel zu speichern. Ich bin mir bewusst, dass ich Blöcke benötigen werde, damit ich nicht die ganze Welt im Gedächtnis haben muss, und ich bin mir bewusst, dass ich sie mit angemessener Leistung rendern muss.
Ich lese über Oktrees und nach meinem Verständnis beginnt es mit 1 Würfel, und in diesem Würfel können 8 weitere Würfel sein, und in all diesen 8 Würfeln können weitere 8 Würfel usw. sein. Aber ich denke nicht, dass dies zu meiner Voxel-Engine passt, weil Meine Voxelwürfel / -gegenstände haben alle genau die gleiche Größe.
Eine andere Möglichkeit besteht darin, einfach ein Array mit einer Größe von 16 * 16 * 16 zu erstellen und dies zu einem Block zu machen, und Sie füllen es mit Elementen. Und Teile, in denen keine Elemente vorhanden sind, haben den Wert 0 (0 = Luft). Aber ich fürchte, das wird viel Speicher verschwenden und nicht sehr schnell sein.
Eine andere Option ist ein Vektor für jeden Block, und füllen Sie ihn mit Würfeln. Und der Würfel hält seine Position im Stück. Dies spart Speicher (keine Luftblöcke), verlangsamt jedoch die Suche nach einem Würfel an einer bestimmten Stelle erheblich.
Ich kann also keine wirklich gute Lösung finden und hoffe, dass mir jemand dabei helfen kann. Was würden Sie verwenden und warum?
Ein weiteres Problem ist das Rendern. Es ist einfach, aber sehr langsam, jeden Block zu lesen und ihn mit OpenGL an die GPU zu senden. Es wäre besser, ein Netz pro Block zu generieren, aber das bedeutet, dass ich jedes Mal, wenn ich einen Block breche, den gesamten Block neu erstellen muss, was einige Zeit in Anspruch nehmen kann und einen kleinen, aber spürbaren Schluckauf verursacht, den ich natürlich auch nicht möchte. Das wäre also schwieriger. Wie würde ich die Würfel rendern? Erstellen Sie einfach alle Cubes in einem Vertex-Puffer pro Chunk und rendern Sie das. Versuchen Sie vielleicht, das in einen anderen Thread einzufügen, oder gibt es einen anderen Weg?
Vielen Dank!
Antworten:
Das Speichern der Blöcke als Positionen und Werte ist tatsächlich sehr ineffizient. Auch ohne Overhead, der durch die von Ihnen verwendete Struktur oder das verwendete Objekt verursacht wird, müssen Sie 4 verschiedene Werte pro Block speichern. Es wäre nur dann sinnvoll, es über die Methode "Speichern von Blöcken in festen Arrays" (die zuvor beschriebene) zu verwenden, wenn nur ein Viertel der Blöcke fest ist und Sie auf diese Weise nicht einmal andere Optimierungsmethoden berücksichtigen Konto.
Octrees eignen sich hervorragend für voxelbasierte Spiele, da sie sich auf das Speichern von Daten mit größeren Funktionen (z. B. Patches desselben Blocks) spezialisiert haben. Um dies zu veranschaulichen, habe ich einen Quadtree verwendet (im Grunde Octrees in 2d):
Dies ist mein Startsatz mit 32x32 Kacheln, was 1024 Werten entspricht:
Das Speichern als 1024 separate Werte scheint nicht so ineffizient zu sein, aber sobald Sie Kartengrößen erreicht haben, die Spielen wie Terraria ähneln, würde das Laden von Bildschirmen mehrere Sekunden dauern. Wenn Sie es auf die dritte Dimension erhöhen, wird der gesamte Speicherplatz im System belegt.
Quadtrees (oder Octrees in 3d) können die Situation verbessern. Um eine zu erstellen, können Sie entweder von Kacheln gehen und sie gruppieren oder von einer riesigen Zelle aus gehen und sie teilen, bis Sie die Kacheln erreichen. Ich werde den ersten Ansatz verwenden, da es einfacher zu visualisieren ist.
In der ersten Iteration gruppieren Sie also alles in 2x2-Zellen. Wenn eine Zelle nur Kacheln desselben Typs enthält, legen Sie die Kacheln ab und speichern nur den Typ. Nach einer Iteration sieht unsere Karte folgendermaßen aus:
Die roten Linien markieren, was wir speichern. Jedes Quadrat ist nur 1 Wert. Dies reduzierte die Größe von 1024 auf 439, was einem Rückgang von 57% entspricht.
Aber du kennst das Mantra . Gehen wir noch einen Schritt weiter und gruppieren diese in Zellen:
Dadurch wurde die Anzahl der gespeicherten Werte auf 367 reduziert. Das sind nur 36% der ursprünglichen Größe.
Sie müssen diese Teilung offensichtlich durchführen, bis alle 4 benachbarten Zellen (8 benachbarte Blöcke in 3d) in einem Block in einer Zelle gespeichert sind und im Wesentlichen einen Block in eine große Zelle konvertieren.
Dies hat auch einige andere Vorteile, vor allem bei Kollisionen. Möglicherweise möchten Sie jedoch einen separaten Octree dafür erstellen, der sich nur darum kümmert, ob ein einzelner Block fest ist oder nicht. Auf diese Weise können Sie die Kollision nicht für jeden Block in einem Block überprüfen, sondern nur gegen die Zellen.
quelle
Es gibt Oktrees, um genau das von Ihnen beschriebene Problem zu lösen und eine dichte Speicherung von Daten mit geringer Dichte ohne große Suchzeiten zu ermöglichen.
Die Tatsache, dass Ihre Voxel die gleiche Größe haben, bedeutet nur, dass Ihr Octree eine feste Tiefe hat. z.B. Für einen 16x16x16-Block benötigen Sie höchstens 5 Baumstufen:
Dies bedeutet, dass Sie höchstens 5 Schritte ausführen müssen, um herauszufinden, ob sich an einer bestimmten Position im Block ein Voxel befindet:
Viel kürzer als das Scannen von 1% des Weges durch ein Array von bis zu 4096 Voxeln!
Beachten Sie, dass wir damit die Daten überall dort komprimieren können, wo ein vollständiger Oktant mit demselben Wert vorhanden ist - unabhängig davon, ob es sich bei diesem Wert ausschließlich um Luft oder nur um Gestein oder etwas anderes handelt. Nur dort, wo Oktanten gemischte Werte enthalten, müssen wir bis zur Grenze der Einzelvoxel-Blattknoten weiter unterteilen.
Um die Kinder eines Stücks anzusprechen, gehen wir normalerweise in der Reihenfolge Morton vor , ungefähr so:
Unsere Octree-Knotennavigation könnte also ungefähr so aussehen:
quelle