Minecraft-Klon, Leistung inkrementieren

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 28 Antworten in diesem Thema. Der letzte Beitrag () ist von jvbsl.

    Minecraft-Klon, Leistung inkrementieren

    Guten Abend,
    ich suche Rat bezüglich der Frage , wie ich das Aktualisieren der Objekte(Cubes) meines Minecraft-Klons beschleunigen kann.

    Zunächst einmal mein Rendering():

    Ich habe ein 16x16x256 großen Chunk.
    In der Update()-Methode, wird entschieden, welcher Würfel gezeichnet werden muss:

    C#-Quellcode

    1. Draw = !((Top != null && Top.ID != "Air") && (Bottom != null && Bottom.ID != "Air") && (Left != null && Left.ID != "Air") && (Right != null && Right.ID != "Air") && (Down != null && Down.ID != "Air") && (High != null && High.ID != "Air"));


    Wenn "Draw=true" ist, wird dieser gezeichnet.. dadurch habe ich ganze 300 FPS..
    Das Rendering() geschieht über Hardware-Instancing(leicht modifizierte Version, um divergente Texturen darzustellen)..

    Das Problem geht mit dem Update() einher.
    Ich prüfe in der Update()-Methode, ob ein Cube im View-Frustum liegt.. wenn ja, dann führe Test aus.
    Die Render-Distanz ermittel ich aus der Distanz eines Rays zu einem Würfel.

    Dadurch schöpf ich meines Erachtens das größte Leistungspotenzial in einem Chunk.
    Problematisch wird dies bei 5 Chunks..

    denn ich muss 16x16x256 * 5 abzüglich den Cubes, die nicht im View-Frustum sind, aktualisieren.
    Ich habe es mit Parallel-For versucht.. war akzeptabel.. bei 20 Chunks wird es jedoch kritisch.

    Wie implementiert Minecraft bloß diese Chunks?

    Ich las das Minecraft eine Instanz eines Objektes habe, welcher dann transformiert, rotiert und gerendert werde... wie würdet ihr vorgehen?

    Liebe Grüße.
    Und Gott alleine weiß alles am allerbesten und besser.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „φConst“ ()

    φConst schrieb:

    Wie implementiert Minecraft bloß diese Chunks?

    Was hindert dich daran, dir das MCP zu schnappen und einfach mal einen Blick in die Sources von Minecraft zu werfen, um zu erfahren, wie die das machen?
    Also ich würde Chunks gar nicht erst so benutzen. Du brauchst doch nicht immer die gesamte Höhe, von 256 Blöcken. Ich würde mich da nicht so sehr an Minecraft orientieren, sondern die Chunks auch Horizontal teilen, oder mir direkt eine andere Art der Speicherung überlegen wo z.B. nur die Blöcke unmittelbar an der Luft überhaupt geladen werden.

    Wenn du die Chunks auch Horizontal teilst, kannst du auch direkt eine Variable festlegen ob einer überhaupt irgendwie sichtbar ist dir so viele Blöcke im Boden sparen. Nur bei Veränderungen an Außenkanten musst du das dann aktualisieren.
    Tut mein Programm auch nicht.
    Das Problem ist nicht das Rendern(), sondern das Updating().

    Gegeben sei ein Chunk.. 16x16x256 ... die Applikation iteriert durch ein Array und prüft, ob jeweils das Element i im View-Fustum des Spielers liegt...(bei 20 Chunks.. ) Wie update() ich denn die Würfel, die ich sehe?
    Ich habe mir gedacht immer ein Teil des Chunks zu aktualisieren, wenn ein Würfel gesetzt oder gelöscht wird.

    Ich zitiere mal aus Minecraft-Forum.net:
    "[...] How Minecraft codes their blocks[...]: Basically there is one block class/object/instance and then there is basically a "map" containing all the locations that block is used. For many blocks, that is all there is, but for some blocks that can change a bit or have different types or have different rotations there is also a tiny bit (4 bits) of extra data also stored. This is called metadata. So you have one class (no need to make it static, just only make it a singleton) that is marked as being used in multiple locations in the world, possibly with a little bit of additional data.The key is to understand how massive things get when trying to create a big world. There are 65,536 block positions in every chunk and by default there are 441 chunks loaded!!! So you have to be super efficient and having an object instance per block does in fact hurt performance. I'm not sure it is so much an issue of total memory used but rather the time it takes for Java to traverse the code and manage memory. In particular the garbage collection becomes more complicated as it tries to figure out if it can free up memory or not."(minecraftforum.net/forums/mapp…-render-stuff-in-1-8-read)

    Habe ich etwa richtig gelesen?
    Es sei notwendig ein Objekt einer Klasse zu haben(exemplarisch Cube), dessen Position, Größe, ID, Texture et cetera variiert , sodass dieser eine Würfel alle Würfel der Welt repräsentiert?
    Ist es divergent, die Eigenschaften eines Objektes zu verändern, statt verschiedene Objekte zu haben?

    Ich meine.. dann bräuchte ich ja ein Array für die Weltmatrix, ein Array für die Texture und ein Array dafür, ob der Würfel bei Position (X;Y;Z) gerendert werden soll oder nicht..(bool-array).
    Oder habe ich etwas missverstanden?

    Liebe Grüße.
    Und Gott alleine weiß alles am allerbesten und besser.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „φConst“ ()

    Yay!
    Funktioniert wunderbar!
    Debug-Model ~ Konstant 60 FPS...

    Auf Nachfrage bin ich gewillt den Source zu publizieren.
    Und Gott alleine weiß alles am allerbesten und besser.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „φConst“ ()

    Engine verwendet? Falls nein, gute Arbeit ;)
    Aber auch so nicht schlecht. Ich würde vorschlagen du veröffentlichst den Source hier einfach für jeden, der sichs ansehen will, oder kloppst es in den SourceCode-Austausch. Wenns nicht super-ranzig programmiert ist kann man da als Einstieg in Spieleprogrammierung vermutlich einiges lernen ^^

    Grüße
    "Life isn't about winning the race. Life is about finishing the race and how many people we can help finish the race." ~Marc Mero

    Nun bin ich also auch soweit: Keine VB-Fragen per PM! Es gibt hier ein Forum, verdammt!
    ein Chunk ist im Prinzip ein 3D array nach möglichkeiten aus primitiven. Wenn du etwas rendern willst, dann renderst du einen Chunk in einem DrawCall, d.h. ein Chunk besteht aus einem einzigen VertexBuffer/VBO, welcher im GPU Mem gespeichert ist, änderst du nun einen einzelnen Block dieses Chunks, so generierst du einen neuen VBO und sendest die neuen Vertexdaten an die GPU, aber solange sich nichts an den Chunks ändert werden auch die VertexBuffer nur ein einziges mal erzeugt.
    Edit: Natürlich ergibt es Sinn auch noch einen IndexBuffer/IBO zu verwenden, dieser muss auch theoretisch nicht verändert werden, solange die VertexAnzahl nicht steigt.
    Instancing mag da schön und gut sein, aber die bessere Idee ist erstmal mit den VBOs und später erzeugt man dann auch die VBOs direkt auf der GPU mittels eines GeometryShaders.
    Zum erzeugen des VertexBuffers erzeugst du nur die Triangles(2 per Quad), die die Oberfläche eines Chunks aufzeigt.
    Und noch etwas Werbung:
    github.com/tomwendel/octoawesome
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Ich verwendete XNA.. nur XNA, habe mir auch die Source von Minecraft nicht angesehen.. nur beobachtet, was Minecraft tut.. es updated Chunks, wenn ein Würfel entfernt oder neu gesetzt wird.(oder: Redstone, Gravitation et cetera)

    "Wenns nicht super-ranzig programmiert ist" , jetzt machst du mir Angst :P .. ich werde mal schauen und bereinigen, was es zu bereinigen gibt.

    Jetzt fehlt noch die Generierung eines 'äonenweiten' Terrains und noch die Optimierung der Update-Methode.. dann lade ich es _ hoch.

    Liebe Grüße.
    @jvbsl
    EDIT: Ich danke dir für die Information in deinem Edit ( =.
    Und Gott alleine weiß alles am allerbesten und besser.
    XNA ist inzwischen ur ur alt, verwend dann doch lieber MonoGame, musst auch im Prinzip nichts verändern.

    Custom Shader gehen zwar, jedoch gibt es Probleme wenn du OGL verwenden möchtest und OGL ist auch eher suboptimal implementiert.
    Leider ist es auch nicht ganz auf dem aktuellen Shader stand, hat nur VertexBuffer und Pixel/Fragmentshader, aber mehr hat XNA ja auch nicht^^
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    @jvbsl scheint da Ahnung von zu haben. Klopp ihm den Code lieber um die Ohren und lass ihn drüberschauen, bevor dus hier reinstellst :D

    Grüße
    "Life isn't about winning the race. Life is about finishing the race and how many people we can help finish the race." ~Marc Mero

    Nun bin ich also auch soweit: Keine VB-Fragen per PM! Es gibt hier ein Forum, verdammt!
    Terraingenerierung:
    webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
    webstaff.itn.liu.se/~stegu/simplexnoise/SimplexNoise.java
    Die hier gezeigte Implementierung kann noch etwas optimiert werden(Java programmierer eben :D)
    Außerdem ist hier noch kein Seed implementiert, was aber ganz einfach Möglich ist:
    Die Arrays perm/permMod12 werden erzeugt aus dem Array p, welches die eigentliche Permutationen enthält und sind nur zu Optimierungszwecken.
    Das Array p ist einfach die Zahlen von 0-255 random angeordnet und kann mit einem shuffle Algorithmus+Random-Objekt+jeweiligem Seed erzeugt werden.

    Diese Noise ist auch noch auf mehr Dimensionen erweiterbar, nachdem man den Algorithmus besser verstanden hat. Was z.B. für OctoAwesome nötig ist, da diese Welten nicht endlos, sondern "rund" sind. Wenn du genaueres über die Weltgenerierung in OctoAwesome erfahren möchtest kannst du mich anschreiben.

    Edit: Achtung, das Projekt ist nicht von mir, sondern von nem Kumpel, ich habe nur bei ein paar Kleinigkeiten mitgeholfen
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Guten Abend,
    ich bin gerade perplex...

    Um ein Würfel in Position {X,Y,Z} zu generieren nutzte ich die Formel:

    Quellcode

    1. int Index = (z * Map3D.LocalWidth * Map3D.LocalHeight) + (y * Map3D.LocalWidth) + x;

    um den Index zu für X,Y und Z zu berechnen(1D-Array).

    Ich probierte es einmal mit einem 3D-Array; zu meinem Erstaunen: Es war um ein Vielfaches schneller.

    Seltsam, nicht wahr?
    Ist das Berechnen des Index' jener Grund?
    Und Gott alleine weiß alles am allerbesten und besser.

    Quellcode

    1. ((z*Map.LocalHeight) + y)*Map.LocalWidth + x


    1D dürfte wenn richtig gemacht schneller gehen, denn schließlich macht .Net eben dieselbe Index berechnung, jedoch hast du den Vorteil, dass wenn du einmal durch alle Elemente durchiterieren musst, dass du dann mit einem einzelnen Index+Increment arbeiten kannst ohne die Multiplikationen...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Guten Abend,
    ich benutze heute das erste Mal den SimplexPerl-Algorithmus

    Folgende Klasse fand ich auf github:



    Die Methode noise() liefert mir für x,y,z folgende Werte :

    Quellcode

    1. 0
    2. -0,08152764
    3. 0,08152775
    4. 0
    5. -0,08152764
    6. 0,0815277
    7. 0
    8. -0,08152749
    9. 0,08152775
    10. 0
    11. -0,08152749
    12. 0,0815277
    13. 0
    14. -0,08152764
    15. 0,0815277
    16. 0
    17. -0,08152764
    18. 0,08152775
    19. 0


    - negative Werte .. ^^

    Fordert die Parameterliste etwa spezifische Koordinaten?

    Vielen Dank!
    Und Gott alleine weiß alles am allerbesten und besser.
    wenn du ganzzahlen für x/y/z nimmst, dann wird natürlich nicht interpoliert und von einem zum nächsten Punkt kann von -1 auf 1 gesprungen werden. Also z.B. einfach mal deine ganzzahlkoordinaten durch 100.0f teilen(testweiße).

    Und das Ergebnis sollte zwischen -1 und 1 sein, also wenn du 0 bis 1 willst, dann kannst du z.B. /2+0.5f rechnen, dann haste das umgerechnet
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Kleines Update:
    eine 13x13 große Map
    (Ein Chunk = 16*16*256 ) * 169 wird in unter 3 Sekunden geladen 8o .

    Einziges Problem ist die Terrain -Generierung.

    Ich habe die Klasse aus OctoAwesome verwendet -
    Ergebnisse im Anhang.

    (Anhang 1: Sin )
    (Anhang 2: SimplexNoise 2D)

    //https://github.com/tomwendel/octoawesome/blob/master/OctoAwesome/OctoAwesome.Basics/Noise/SimplexNoiseGenerator.cs

    Wieso setzt denn die Methode GetNoise2D nur integer Werte voraus?
    Und Gott alleine weiß alles am allerbesten und besser.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „φConst“ ()

    Da du die Skalierung ja bereits mit der Frequency im Konstruktor angibst und danach alles nur noch auf einem Drahtgitter basiert.
    Verwende außerdem lieber GetNoiseMap2D aus Performancegründen.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---