Helfen Sie beim Verständnis von Simplex Noise

7

Einführung

Dies ist weniger eine Anleitung zur Verwendung von 2D-Simplex-Rauschen als vielmehr eine Suche nach einem Verständnis dafür, was sowohl in der Mathematik als auch visuell geschieht. Ich möchte den gefundenen Code lieber nicht kopieren und einfügen. Ich würde es wirklich gerne verstehen.

Ich habe meine Hausaufgaben zu diesem Thema gemacht und Stefan Gustavsons Artikel und andere Quellen viele Male gelesen . Ich habe das Gefühl, dass ich ungefähr 70% von dem verstehe, was meine Quellen sagen, aber wenn ich versuche, meiner Codeschleife manuell zu folgen, um mein Verständnis zu überprüfen, werde ich entweder daran gehängt, was die Variablen darstellen, oder die Mathematik passt einfach nicht zusammen.

Ich habe begonnen, die Variablen in dem Code, den ich gefunden habe, umzubenennen, um besser zu verstehen, was los ist. Wenn jemand mit etwas Wissen darüber, was tatsächlich passiert, den Originalcode (im Grunde Gustavsons Code) mit meinem Code vergleichen kann , wäre das am besten. Ich bin mit dem Umbenennen der Variablen jedoch noch nicht fertig, auch weil ich nicht weiterkomme.

Mein Verständnis

Angenommen, ich übergebe Pixel (2, 3) in meine Rauschfunktion. An diesem Punkt arbeite ich mit einem normalen Gitter und mein Ziel ist es, dieses normale Gitter in einen Simplex zu übersetzen. Dieser Simplex hat die Form vieler Dreiecke, da wir in 2D arbeiten, und angeblich ist dies aus verschiedenen Gründen besser.

Um diesen Punkt vom normalen Gitter in das Simplex-Gitter zu übersetzen, muss ich den Punkt entlang der diagonalen Hauptlinie skalieren. Danach interessieren mich die Dezimalstellen anscheinend nicht mehr, da ich dann die Ergebnisse auf den Boden lege und sie wieder in die Nähe des nächsten vorherigen Gitterpunkts bringe.

Jetzt werde ich die Simplex-Zelle am normalen Raster ausrichten, indem ich die Simplex-Zelle abschiebe. Warum ich all diese Arbeit mache, um zur Simplex-Zelle zu gelangen, und sie dann rückgängig mache, ist mir ziemlich unklar. Im Ernst, ich denke, der beste Weg für mich, dies zu verstehen, ist, wenn jemand in Schritten herausfinden könnte, was zum Teufel hier vor sich geht. Super verloren.

Ich bin aber nicht mit leeren Händen weggegangen. Ich habe jetzt, wie ich glaube, den Abstand zwischen dem ursprünglichen Gitterpunkt, den ich passiert habe, und dem Simplex-Punkt, den ich von diesem ursprünglichen Gitterpunkt übersetzt habe. Woo.

Ich denke, ich werde hier vorerst aufhören, nur weil ich nicht möchte, dass daraus eine riesige Textwand wird. Ich bin mir ziemlich sicher, dass der Rest davon klicken wird, sobald ich den Anfangsteil verstanden habe.

Seltsame Mathematik

Mit dem Punkt (2, 3) von früher erhalte ich Folgendes, wenn ich meinen eigenen Code auf Papier durchlaufe:

x = 2
y = 3

skewfactor = 1.830
unskewFactor = 1.479
unskewed_x = 1.520
unskewed_y = 2.520
simplexCell_i = 3
simplexCell_j = 4
x_distance0 = -1.520
y_distance0 = -1.520
i1 = 0
j1 = 1
x1 = -1.309
y1 = -2.309
x2 = -2.5207
y2 = -2.5207
ii = no idea why this isn't i2 or something like that. No idea what this is.
jj = same

Ich bin mir nicht sicher, ob das Plotten in einem Diagramm funktionieren würde, aber ich habe es versucht und es sieht so schlecht aus. Ich habe die ursprünglichen Parameter (2, 3) verwendet, simplexCell_i & j, i1, j1, x1, y1, x2 und y2. Macht visuell keinen Sinn.

Nicht nur visuell, sondern auch mathematisch. Was ist mit (2, 3), die negative Zahlen zurückgeben, die vom Chart abweichen? Was mache ich hier falsch?

Stradigos
quelle

Antworten:

10

Ich denke, es hilft, es Seite an Seite mit normalem Perlin-Rauschen zu vergleichen. Wie im Gustavson-Artikel erläutert, weist Perlin-Rauschen jeder Ecke eines quadratischen Gitters Pseudozufallswerte (Gradientenvektoren) zu und führt dann eine Interpolation für Punkte im Inneren einer Gitterzelle durch. Der erste Schritt bei der Bewertung des Perlin-Rauschens besteht darin, herauszufinden, in welcher Gitterzelle Sie sich befinden. Genau das macht die floorFunktion: Alle Werte zwischen 0 und 1 befinden sich in der Gitterzelle 0, zwischen 1 und 2 in der Gitterzelle 1, usw. entlang jeder Achse. Im klassischen Perlin-Rauschen sehen Sie also Code wie

int i = int(floor(x));
int j = int(floor(y));

Sobald Sie wissen, in welcher Gitterzelle Sie sich befinden, kann der Rest des Algorithmus fortgesetzt werden.

Bei Simplex-Rauschen müssen Sie nach wie vor zunächst herausfinden, in welcher Gitterzelle Sie sich befinden. Jetzt handelt es sich jedoch um ein Simplex-Gitter, das auf so einfache Weise nicht mit den Eingaben x und y zusammenhängt als quadratisches Gitter. Wie Perlin feststellte und wie Gustavson im Bild auf Seite 6 zeigt, haben die gequetschten Gitterzellen jetzt eine Form, die aus mehreren Vereinfachungen bestehen kann, wenn Sie ein quadratisches Gitter entlang seiner Diagonale um genau den richtigen Faktor skalieren. (In 2D ist das gequetschte Quadrat beispielsweise eine Raute, die aus zwei gleichseitigen Dreiecken bestehen kann. In höheren Dimensionen hätten Sie mehr als zwei Vereinfachungen in jeder Gitterzelle, aber es ist dieselbe Idee.)

Dies bietet also eine Möglichkeit, die Lücke zwischen dem kartesischen Koordinatensystem und dem Simplex-Gitter zu schließen. Zuerst finden Sie heraus, in welcher Gitterzelle Sie sich in Bezug auf das gequetschte quadratische Gitter befinden. Dann finden Sie heraus, in welchem ​​Simplex Sie sich in dieser gequetschten quadratischen Gitterzelle befinden.

Um herauszufinden, in welcher Zelle des gequetschten quadratischen Gitters Sie sich befinden, führen Sie zunächst eine lineare Transformation in ein Koordinatensystem aus, dessen Achsen mit dem gequetschten Gitter ausgerichtet sind. Perlin schreibt es so etwas wie:

float F2 = 0.5*(sqrt(3.0)-1.0);
float s = (xin+yin)*F2;
float xSquashed = x + s;
float ySquashed = y + s;

Hier xSquashed, ySquashedsind Koordinaten in dem lokalen Raum des gequetschten grid. Wenn Sie eine Minute lang darauf starren, werden Sie feststellen, dass dies der Matrixtransformation entspricht:

[ xSquashed, ySquashed ] = [ xIn, yIn ] * [ 1+F2 F2   ]
                                          [ F2   1+F2 ]

Es handelt sich also nur um eine lineare Änderung der Koordinaten, obwohl die Matrixmultiplikation so ausgeschrieben ist, dass dies nicht offensichtlich ist.

Das Berechnen der Gitterzelle, in der Sie sich befinden, ist wie zuvor einfach ein floorAufruf dieser lokalen Koordinaten. Das gibt Ihnen den Gitterzellenindex für eine Ecke des Simplex. Wenn Sie noch mehr an den Koordinaten herumspielen, bestimmen Sie, in welchem ​​Simplex Sie sich in der gequetschten Gitterzelle befinden. Von dort aus können Sie die Gitterzellenindizes für die anderen Ecken des Simplex ermitteln.

Warum wandeln Sie sich schließlich wieder in das reguläre, nicht gequetschte Koordinatensystem um? Dies liegt daran, dass Sie in der letzten Phase des Algorithmus, wenn Sie die Beiträge aller Ecken addieren, mit der normalen Distanz arbeiten möchten, nicht mit der Pseudo-Distanz im gequetschten Raum. Das ist so, dass das Rauschen sauber interpoliert und nicht mit einem gequetschten Aussehen herauskommt. Beachten Sie, dass das, was Sie wieder in reguläre Koordinaten umwandeln, nicht der ursprüngliche Eingabepunkt ist, sondern die erste Ecke des Simplex. Sobald Sie das haben, können Sie die Positionen der anderen Ecken finden und mit dem Rest des Algorithmus fortfahren.

Wie zuvor ist diese Transformation in einer seltsam aussehenden Form geschrieben, entspricht jedoch der Matrixtransformation:

float G2 = (3.0 - sqrt(3.0))/6.0;
[ X0, Y0 ] = [ i, j ] * [ 1-G2 -G2   ]
                        [ -G2   1-G2 ]

Wenn Sie es berechnen, werden Sie sehen, dass diese Matrix und die oben erwähnte Umkehrung voneinander sind.

Nathan Reed
quelle
Super Antwort! Ich verdaue es gerade. Sind i, j also repräsentativ für die Simplex-Gitterkoordinaten? X, y und sind offensichtlich die normalen Gitterkoordinaten. Also für (2, 3) ist die erste einfache Ecke, die ich bekomme, (3, 4), richtig? Haben diese beiden Punkte eine Beziehung? Wie, (2, 3) ist nicht die erste Kurve, oder? (3,4) ist, und von dort aus verwenden wir den Abstand, um festzustellen, in welche Richtung der zweite Simplexpunkt (entweder oben oder rechts) ist, richtig?
Stradigos
Nun, in diesem Fall ist der Abstand gleich, so dass er in den ELSE-Fall der IF-Anweisung fällt, wobei der zweite Punkt rechts vom ersten Simplex-Punkt liegt. Das heißt also, dass der zweite Simplexpunkt dann (3, 5) ist? Dann nehmen wir diesen Punkt und schieben ihn ab. Was ist mit dem letzten Punkt los? Warum x_distance0 - 1.0f + 2.0f * unskew? Betrachtet man insbesondere den Teil 1.0f - 2.0f.
Stradigos
Vielleicht müssen wir nicht herausfinden, was die letzte Ecke in normalen Gitterkoordinaten ist, bevor wir sie verzerren? Ist das, wofür der 1.0f + 2.0f ist? Weil wir wissen, was wir vorher wissen, gehen wir einfach vorbei und sammeln unsere 200 Dollar? Meine x_distance und y_distance sind beide .4793. Dies gibt mir x1 = .6906 und x2 = -.3094. Ich hatte eine Art Ganzzahl erwartet, da wir an diesem Punkt (x, y) mit normalen Gitterkoordinaten arbeiten. Aber vielleicht nicht? x1 und y1 sind wirklich Simplex-Koordinaten? Ich werde hier anhalten und darauf warten, dass Sie aufholen. Entschuldigung für den dreifachen Beitrag.
Stradigos
1
Das x_distance0 - 1.0f + 2.0f * unskewTeil tritt nur um eine Einheit im gequetschten Gitter vor. Aufgrund der Art und Weise, wie die Vereinfachungen angeordnet sind, sind ihre Ecken in gequetschten Gitterkoordinaten wie (i, j), entweder (i + 1, j) oder (i, j + 1) und schließlich (i + 1, j) +1). Der Versatz von (1,1) im gequetschten Gitter wird in einen Versatz von -1.0f + 2.0f * unskewin regulären Koordinaten übersetzt. (Es ist minus, weil Sie den Vektor versetzen, der von der Ecke zum ursprünglichen Eingabepunkt zeigt. Wenn Sie also die Ecke verschieben, erhalten Sie einen negativen Versatz zu diesem Vektor.)
Nathan Reed