Nachdem ich neulich ein neues Farbschichtenschema für Fordite gebaut habe, habe ich mich letzte Woche an eine neue Höhenkarte gesetzt.
Kurze Erinnerung: Fordite ist ein Programm, das nach der Inspiration von echtem Fordit Bilder erstellt. Für nähere Informationen bitte die verlinkten Blogposts lesen, durch die Tags stöbern und den Code anschauen. Für mehr Beispielkonfigurationen gibt es ein eigenes Repo.
Fraktale Landschaften
Es fing damit an, dass mir die bisherigen Höhenkarten zu gleichmäßig waren. Paraboloide, Sinuskurven, schiefe Ebenen… damit kann man schöne Bilder machen, aber die Linien zwischen zwei Farben sind immer sehr ordentlich. Saubere Kurven, geraden, etc. Echtes Fordit hat mehr Unregelmäßigkeiten.
Nun, wie erstellt man zufällige Höhenkarten? Einfach zufällige Werte nehmen, nach welcher Verteilung auch immer, gibt keine natürlich aussehende Höhenkarte und würde im Bild hinterher einfach Farben durcheinander werfen.
Glücklicherweise bin ich nicht der erste, der so ein Problem hat. In der Computerspieleentwicklung gibt es schon seit Jahrzehnten das Konzept von fractal landscapes um halbwegs natürlich aussehende Landschaften zu generieren.
Ich habe mir auf Gutdünken einen Algorithmus herausgegriffen, den diamond-square-Algorithmus. Nach einem ganzen Haufen off-by-one-Fehlern hat das auch wunderbar funktioniert:
So weit, so gut. Ich habe ein paar Bilder damit erstellt, war aber nicht wirklich zufrieden. Warum? Darauf komme ich später zurück.
Breaking Changes: Farbschichten
Um besser darstellen zu können, warum ich nicht zufrieden war, wollte ich Bilder mit verschiedenen Farbschichten bauen, die alle dieselbe diamond-square-Algorithmus-Höhenkarte verwenden. Für manche war das kein Problem, andere hatten aber bis dato keine Möglichkeit, die Dicke der Schichten einzustellen.
Die Dicke war standardmäßig dünn, für einfache, ebene Schichten war es nur 1
. Wenn man da jetzt eine zufällige Höhenkarte draufpackt, hat man sehr viele Farbwechsel, denn obwohl die Höhenkarte nicht komplett wild hin- und herspringt unterscheidet sich jeder Punkt mit hoher Wahrscheinlichkeit von seinem Nachbar.
Also mussten diese Konfigurationen angepasst werden. Ich habe mir erst einmal auf die waagerechten Ebenen (horizontal_planes
in den config-Dateien) und die schrägen Ebenen (inclined_planes
in den config-Dateien) konzentriert. Für die anderen kommt das vielleicht später.
Ich hatte gehofft es so einrichten zu können, dass, wenn man den neuen Parameter weglässt, alles weiterhin funktioniert, so dass alte Konfigurationen rückwärtskompatibel sind. Das habe ich mit serde aber nicht hingekriegt. Also gibt muss man jetzt alte configs anpassen, damit sie wieder laufen.
Beispiele dafür, wie die neuen configs aussehen müssen, findet man hier. Wenn man möchte, dass die Bilder so aussehen wie bisher, empfehle ich für horizontal_planes
den Wert 1
für den Parameter layer_thickness
. Für inclined_planes
ist es unterschiedlich, weil die Dicke bisher von der maximalen Höhe der Höhenkarte abhing. Da muss man ein bisschen herumprobieren.
Langweilige Bilder
Warum war ich also nicht zufrieden mit den diamond-square-Bildern? Hier zur Veranschaulichung mal Bilder mit verschiedenen Farbschichtenschemata und derselben Höhenkarte:
Versucht mal zu raten, welches Bild mit welchem Farbschichtenschema erzeugt wurde. Die Höhenkarte ist überall gleich. Die Lösung steht im Titel des Bilders (darüberhovern auf Geräten mit Maus, antippen für Mobile Browser, wenn ich mich recht entsinne).
Worauf ich hinaus will: Man kann zwar Unterschiede zwischen den Farbschichtenschemata erkennen (besonders die schiefen Ebenen stechen wegen des große-einfarbige-Fläche-quirks heraus), aber ansonsten sehen die sich vom Prinzip her alle recht ähnlich.
Breaking Changes: Die Rettung naht, doch vorher noch ein paar Umbauten
Um die Lösung, die mir vorschwebte zu bauen, musste ich ein bisschen Umstrukturieren. Insbesondere habe ich die Höhenkarten-Transformation (bisher Verschiebung und Rotation) aus der allgemeinen Konfiguration in die einzelnen Höhenkartenkonfigurationen verschoben.
Das zerstört mal wieder bestehende Konfigurationsdateien. Die zu reparieren ist nervig, aber nicht sehr kompliziert. Damit waren nun alle Voraussetzungen geschaffen für das:
Addieren von Höhenkarten
Meine Idee war: Vielleicht ist es zu viel, die ganze Höhenkarte zufällig zu machen. Vielleicht kann man ja mit einer regelmäßigen Höhenkarte anfangen, und dann eine zufällige Höhenkarte dazuaddieren.
Mit den im vorherigen Abschnitt erwähnten Änderungen an den Konfigurationsdateien war das sehr einfach umzusetzen. Dieses Mal macht es sogar keine configs kaputt! Es gibt einen neuen Höhenkartentyp, sum
, der als Parameter eine Liste von Höhenkarten und Gewichten entgegennimmt.
Das Addieren funktioniert wie folgt:
- generiere alle Höhenkarten
- normalisiere alle Höhenkarten auf denselben Wertebereich
- multipliziere alle Höhenkarten mit ihren Gewichten
- addiere die Höhenkarten
- skaliere das Ergebnis auf die gewünschte Höhe
Und damit habe ich endlich das, was ich will. Und nicht nur das: Ich habe mir direkt einen ganzen Haufen zusätzlicher Optionen geschaffen, schließlich kann man beliebige Höhenkarten addieren.
Hier ein Beispiel, wie das aussehen könnte. Zuerst ein Bild, dass ich früher schon einmal geposted habe (bevor ich die Höhenkarten auf float umgestellt habe, deswegen hier eine neue Version):
Im Vergleich dazu: die gleiche Konfiguration, aber mit 10% Gewichtung eine zufällige Höhenkarte draufaddiert:
Ich will nicht zwingend sagen, dass eine Version besser ist als die andere, aber es ist definitiv ein Unterschied.
Breaking changes: two_sine
Zu guter Letzt gibt es noch einen dritten breaking change: Die two_sine
-Höhenkarte fällt weg. Ihren Effekt kann man jetzt schließlich mit dem Addieren von zwei sine
-Höhenkarten erreichen.
Schlusswort
Ich werde jetzt erst einmal mit den neuen Optionen herumspielen und vielleicht auch noch ein Video erzeugen. Mir ist auch die Idee gekommen, dass ich vielleicht Höhenkarten nicht nur addieren, sondern auch multiplizieren kann. Mir sind auch schon ein paar Ideen gekommen, wozu das nützlich sein kann.
Was außerdem noch ansteht: Ich möchte separate Zufalls-seeds für die Farbschichten anbieten. Ich habe schon für den diamond-square-Algorithmus einen (optionalen) eigenen seed eingeführt. Hätte ich das nicht, wären die Farbschichten anders als ohne diamond-square. Aber all das drängt nicht.