Ich wollte einen Nebel in meinem Spiel
The Last Boundary . Sie sehen fantastisch aus und Raum ohne sie ist kein Raum, sondern einfach weiße Pixel, die über den Hintergrund verstreut sind. Aber da ich das Spiel im Stil von "Pixel Art" mache, musste ich meine Rauschbibliothek irgendwie dazu bringen, pixelige Bilder zu erzeugen.
Hier einige Beispiele:
In einfarbigen Beispielen werden 8 Farben und in anderen 16 Farben verwendet. In diesem Artikel werde ich darüber sprechen, wie ich einen pixeligen Nebel für The Last Boundary erstellt habe.
Wenn wir mit einer
Rauschbibliothek wie
LibNoise arbeiten , werden die Werte normalerweise im Bereich von
-1
bis
1
verteilt,
unabhängig davon, welche Engine Sie verwenden (oder Ihre eigene schreiben). Es ist theoretisch wahrscheinlicher, dass 2D-Rauschen im Bereich von
-0.7
bis
0.7
, aber einige Implementierungen skalieren das Ergebnis und übersetzen es in das Intervall von
-1
bis
1
. Um mit 2D-Texturen zu arbeiten, wird es normalerweise in ein Intervall von
0
bis
1
konvertiert und
RGB(255,255,255)
in den Bereich von
RGB(0,0,0)
bis
RGB(255,255,255)
.
Perlin-Rauschen, das aus der x,y
Koordinate jedes auf 0.3f
skalierten Pixels 0.3f
Dann können Sie eine
gebrochene Brownsche Bewegung verwenden , um dem Bild ein Gefühl von Wolkenpracht zu verleihen.
Perlin-Rauschen wurde einer fraktionierten Brownschen Bewegung mit 8
Oktaven, einer Frequenz von 0.01
, einer Regelmäßigkeit von 0.5
und einer Lakunarität von 2.0
ausgesetzt.Ich habe festgestellt, dass es im Internet viele falsche Implementierungen von Perlin-Rauschen, Simplex-Rauschen und fraktionierter Brownscher Bewegung (fBm) gibt. Es scheint viel Verwirrung darüber zu geben, was was ist. Stellen Sie sicher, dass Sie die richtige Implementierung verwenden. Wenn Sie die oben beschriebene Kette erstellen möchten, erhalten Sie bei einer falschen Implementierung möglicherweise nicht die erforderlichen Ergebnisse.
Stellen wir uns vor, wir wollen einen Raucheffekt erzeugen, das heißt, eine solche Lösung passt zu uns. Aber unser Pixelkunstspiel würde seltsam aussehen, wenn eine ganze Reihe neuer Farben von
RGB(0,0,0)
bis
RGB(255,255,255)
. Plötzlich erscheinen 255 neue Graustufen im Spiel.
Wir müssen sie in eine begrenzte Anzahl von Farben konvertieren. Das werden wir später tun. In der Zwischenzeit ...
Zufälligen Nebel erzeugen
Ich wiederholte dies für vorgefertigte Tutorials zum Erzeugen zufälliger Nebel, fügte jedoch einige meiner Schritte hinzu und wandte meine eigene Geräuschbibliothek an. Ich habe es vor einigen Jahren geschrieben, weil ich ein gutes Verständnis für Perlins Rauschen haben wollte und wie Sie es zusammen mit anderen Konzepten verwenden können, um Texturen und dergleichen zu erstellen.
Vielleicht können Sie nach mir Schritt für Schritt wiederholen, oder Sie müssen den Code ergänzen, der sich auf Ihr Rauschen auswirkt. Ich werde alles außer der anfänglichen Rauschgenerierung und fBm erklären, damit Sie den Code selbst schreiben können. Ich denke, es kann davon ausgegangen werden, dass Sie bereits in der Lage sind, Rauschen und fBm zu erzeugen.
Zu Beginn werde ich das Ergebnis der Erzeugung des Nebels zeigen:
Fertiges ErgebnisEs ist wichtig zu beachten, dass es noch nicht pixelig ist. Es hat eine breite Palette von Farben mit einem pixeligen Sternenhimmel. Den Nebel werden wir später pixelig machen.
Als erstes müssen fünf verschiedene Texturen generiert werden: Rot, Grün, Blau, Alpha und Maske. Die roten, grünen und blauen Texturen werden für die entsprechenden endgültigen Farbkanäle benötigt. Tatsächlich generiere ich nur einen oder zwei Farbkanäle, weil sich herausstellte, dass die Verwendung aller drei einen unglaublich bunten Nebel erzeugt, der hässlich aussieht. Jede einzelne Farbe oder eine Kombination aus zwei Farben ist gut geeignet.
Der Alpha-Kanal ist wichtig, da es davon abhängt, ob die unteren Sterne durch den Nebel scheinen. Ich werde dies veranschaulichen, indem ich den Alpha-Kanal des oben gezeigten Beispiels anzeige.
Bereit Alpha-Kanal aus unserem BeispielJe weißer die Fläche ist, desto näher liegt der Wert an
1.0
, was einen Alpha-Wert von
255
ergibt. Je schwärzer der Bereich, desto transparenter ist er. Wenn Sie sich ein Beispiel ansehen, können Sie sehen, dass die schwarzen Bereiche Bereichen entsprechen, in denen der Sternenhimmel sichtbar ist.
Sternenhimmel BeispielDies sind nicht die gleichen Sterne wie im Beispiel, da sie in jedem Screenshot zufällig generiert werden. Ich hoffe, dies hindert Sie nicht daran zu verstehen, wie der Nebel erzeugt wird.
Meine Rauschbibliothek besteht aus Modulen, die dem Beispiel von
Lib Noise folgen. Alles in dieser Bibliothek sind „Module“, die miteinander verkettet werden können. Einige Module generieren neue Werte (Perlin-Modul, konstanter Wert), andere verbinden sie (Multiplizieren, Addieren) und einige führen einfach Operationen an dem Wert aus (Lerp, Clamp).
Farbkanäle
Es spielt keine Rolle, ob wir mit einer, zwei oder drei Farben arbeiten - die Kanäle Rot, Grün und Blau werden auf dieselbe Weise generiert. Ich benutze nur einen anderen Startwert für sie. Meine Startwerte hängen von der aktuellen Systemzeit ab.
Im Folgenden werden sie alle in Graustufen dargestellt, aber theoretisch sind sie einfach Werte für einen der drei Kanäle. Graustufen dienen nur zur Veranschaulichung der Ergebnisse.
1. Perlins Lärm
Wie oben wird Perlins Lärm der Ausgangspunkt sein. Wenn Sie möchten, können Sie Simplex-Rauschen verwenden. Es scheint, dass die 2D-Implementierung nicht Ken Perlin gehört, aber ich könnte mich irren. Aus mathematischer Sicht benötigt Simplex-Rauschen weniger Anweisungen, sodass die Erzeugung eines ähnlichen Nebels schneller erfolgt. Da es Simplexe anstelle eines Gitters verwendet, erzeugt es ein etwas schöneres Rauschen, aber wir werden nicht viel damit arbeiten, daher ist dies nicht besonders wichtig.
Der reale Code wird unten nicht gezeigt, da in realen Quellen die
x,y
Werte in Schritt 3 um fBm geändert wurden. Dies ist nur die
x,y
Koordinate des Bildes, multipliziert mit dem statischen Skalierungsfaktor.
Perlin-Rauschen, das aus der x,y
Koordinate jedes auf 0.3f
skalierten Pixels 0.3f
. Das heißt, PixelValue = PerlinNoise(x * 0.3f, y * 0.3f)
Die durch Perlin-Rauschen erzeugten Werte liegen ungefähr im Bereich von
-1
bis
1
Um das oben gezeigte übliche Graustufenbild zu erstellen, konvertieren wir sie in das Intervall von
0
bis
1
. Ich habe den Umfang der Werte getestet, damit die Konvertierung den größten Kontrast erzeugt (der niedrigste Wert entspricht
0
, der größte -
1
).
2. Multiplikation
Das nächste verwendete Modul multipliziert das erzeugte Rauschen mit
5
. Dies kann als Kontrastanpassung angesehen werden. Negative Werte sind dunkler, positive Werte sind heller.
Ich habe hier nichts zu zeigen, da sich beim Konvertieren von Werten aus dem Intervall von
-5
bis
5
in das Intervall von
0
nach
1
Ergebnis nicht ändert.
3. Fractional Brownian Motion (fBM)
Diese Phase verwandelt Lärm in das, was viele Menschen als echten „Lärm-Effekt“ betrachten. Hier führen wir Oktaven von immer kleineren Samples aus der Rauschfunktion aus (in unserem Fall ist die Funktion
perlin(x,y)
), um
perlin(x,y)
hinzuzufügen.
Fraktionierte Brownsche Bewegung des oben gezeigten Perlin-Rauschens. 8
Oktaven, Frequenz .01f
, Regelmäßigkeit .5f
und 2.5f
Sie können bereits den Ursprung von etwas Interessantem erkennen. Das oben gezeigte Bild wird nicht durch Skalieren der
x,y
Koordinaten der Pixel erzeugt, fBM tut dies. Wiederum werden diese Werte umgekehrt in ein Intervall von
0
bis
1
in ein mögliches Intervall von
-5
bis
5
.
4. Einschränkung (Klemme)
Jetzt werde ich die Werte auf einen Bereich von
-1
bis
1
. Alles außerhalb dieses Intervalls wird vollständig verworfen.
Das gleiche fBm, begrenzt auf -1
bis 1
Die Aufgabe dieser Operation besteht darin, die Werte in ein kürzeres Intervall umzuwandeln, während schärfere Verläufe erzeugt und der Bereich in vollem Weiß oder Schwarz vergrößert wird. Diese toten oder leeren Bereiche sind wichtig für den Nebeleffekt, auf den wir später noch eingehen werden. Wenn wir zuerst nicht mit
5
multipliziert hätten, hätte die Klemme nichts geändert.
5. Fügen Sie 1 hinzu
Jetzt nehmen wir die Werte aus der Klemme und addieren 1. zu ihnen. Somit übertragen wir die Werte in das Intervall von
0
bis
2
. Nach der Konvertierung sehen die Ergebnisse genauso aus wie zuvor.
6. Teilen Sie durch 2
Sie wissen wahrscheinlich, was passieren wird, wenn ich das Ergebnis durch
2
dividiere (multipliziere mit
.5
). Im Bild ändert sich nichts mehr.
Die Schritte 5 und 6 konvertieren die Werte in einen Bereich von
0
bis
1
.
7. Erstellen Sie eine Verzerrungstextur
Der nächste Schritt besteht darin, eine Verzerrungstextur zu erstellen. Ich werde dies mit Perlin-Rauschen (mit dem neuen Startwert) tun> mit 4 multiplizieren> fBm ausführen. In diesem Fall verwendet fBm
5
Oktaven, eine Frequenz von
0.025
, eine Regelmäßigkeit von
0.5
und eine Lücke von
1.5
.
VerzerrungstexturDiese Textur wird benötigt, um mehr Details als in der vorhandenen Textur des Nebels zu erzeugen. Der Nebel ist eine ziemlich große Wellenwolke, und diese Textur wird kleine Änderungen daran vornehmen. Dadurch wird die Gitternatur von Perlins Lärm sichtbar.
8. Versetzen Sie die Farbtextur mit der versetzten Textur
Als nächstes nehme ich diese beiden Texturen und verwende eine, um die Koordinaten der anderen um einen Faktor zu versetzen. In unserem Fall sieht die Kombination folgendermaßen aus:
VorspannungsergebnisDie Verzerrungstextur wird verwendet, um die
x,y
Koordinaten zu ändern
x,y
wir in den Quellrauschdaten suchen.
Denken Sie daran, dass die oben gezeigten Bilder nur zu Illustrationszwecken dienen. In jeder Phase haben wir eigentlich nur eine Rauschfunktion. Wir übergeben ihm den Wert
x,y
und es wird eine Zahl zurückgegeben. In bestimmten Phasen kann das Intervall dieser Zahl unterschiedlich sein, aber oben haben wir es wieder in Graustufen konvertiert, um ein Bild zu erstellen. Das Bild wird erstellt, indem jede
x,y
Koordinate des Bildes als
x,y
, die von der Rauschfunktion übertragen wird.
Das heißt, wenn wir sagen:
Geben Sie mir den Wert für das Pixel der oberen linken Ecke mit X = 0 und Y = 0
Funktion gibt uns eine Nummer zurück. Wenn wir Perlin danach fragen, wissen wir, dass es zwischen
-1
und
1
wird. Wenn wir wie oben Clamp, Addition und Multiplikation anwenden, erhalten wir einen Wert zwischen
0
und
1
.
Nachdem wir dies verstanden haben, lernen wir, dass die Verzerrungsrauschfunktion Werte im Bereich von
-1
bis
1
. Um die Verzerrung durchzuführen, wenn wir sagen:
Geben Sie mir den Wert für das Pixel in der oberen linken Ecke mit Pixel X = 0 und Y = 0
Das Offset-Modul fragt zuerst die Offset-Funktion nach den
x,y
Koordinaten. Das Ergebnis liegt zwischen
-1
und
1
(wie oben). Dann wird es mit
40
multipliziert (dies ist der
Koeffizient, den ich ausgewählt habe). Das Ergebnis ist ein Wert zwischen
-40
und
40
.
Dann nehmen wir diesen Wert und addieren ihn zu den Koordinaten des gesuchten
x,y
und verwenden dieses Ergebnis, um die Farbtextur zu durchsuchen. Wir schneiden negative Werte mit der Klemme auf 0 ab, da es unmöglich ist, negative
x,y
Koordinaten in Rauschfunktionen zu suchen (zumindest in meiner Rauschbibliothek).
Das heißt, im Allgemeinen sieht es so aus:
ColourFunction(x,y) = 0 1 DisplaceFunction(x,y) = -1 1 DoDisplace(x,y) = { v = DisplaceFunction(x,y) * factor clamp(v,0,40) x = x + v; y = y + v; if x < 0 then x = 0 if y < 0 then y = 0 return ColourFunction(x,y) }
Ich hoffe du verstehst das. Tatsächlich betrachten wir nicht das
x,y
, in dem wir uns befanden, sondern den Versatz. Und da die
Größe auch ein glatter Gradient ist, verschiebt sie sich reibungslos.
Es gibt andere Möglichkeiten, den Offset durchzuführen. Meine Rauschbibliothek hat ein Modul, das eine Spiralverschiebung erzeugt. Es kann verwendet werden, um Texturen zu zeichnen, die allmählich auf eine Anzahl von Punkten abnehmen.
Hier ist ein Beispiel .
Das ist alles. Wir wiederholen die obigen Vorgänge dreimal und verwenden für jeden Farbkanal neue Startwerte. Sie können einen oder zwei Kanäle erstellen. Ich denke nicht, dass es sich lohnt, ein drittes zu schaffen.
Alpha-Kanal
Ein Alphakanal wird ähnlich wie Farbkanäle erstellt:
- Wir beginnen mit Perlins Lärm
- Mit
5
multiplizieren - fBM mit
8
Oktaven, Frequenz 0.005
, Regelmäßigkeit 0.5
und Lakunarität 2.5
- Wir begrenzen die Ergebnisse mit Clamp auf das Intervall von
-1
bis 1
, addieren 1
, dividieren durch 2
(d. H. Wir verschieben das Intervall von -1
bis 1
auf das Intervall von 0
bis 1
. - Wir verschieben das Ergebnis um einen kleinen Betrag in die negative Richtung. Ich versetze um
0.4
. Dadurch wird alles etwas dunkler. - Wir beschränken die Ergebnisse auf ein Intervall von
0
bis 1
. Da wir alles verschoben haben, wodurch es etwas dunkler wurde, haben wir tatsächlich mehr Bereiche mit 0
, und einige Bereiche haben negative Werte angenommen.
Das Ergebnis ist eine Alpha-Kanal-Textur.
Alpha-TexturWie gesagt, schwarze Bereiche sind transparent und weiße Bereiche undurchsichtig.
Kanalmasken
Dies ist die letzte Textur, die verwendet wird, um Schatten zu erzeugen, die über allem anderen liegen. Es beginnt wie alle anderen Texturen:
- Noise Perlin
- Mit
5
multiplizieren - Wir spielen fBm,
5
Oktaven, Frequenz 0.01
, Regelmäßigkeit 0.1
, Lakunarität 0.1
. Die Regelmäßigkeit ist gering, daher ist die Wolke weniger dicht - Führen Sie eine Intervallverschiebung von
-1
nach 1
zu einem Intervall von 0
nach 1
Wir erstellen jedoch zwei solche Texturen:
Maske aMaske B.Wir setzen diese beiden Texturen dem so genannten
Select- Modul aus. Tatsächlich verwenden wir den Wert aus Modul A oder Modul B. Die Auswahl hängt vom Wert von Modul C ab. Es sind zwei weitere Werte erforderlich -
Select Point und
Falloff .
Wenn der Wert am Punkt
x,y
Modul C größer oder gleich
SelectPoint
, verwenden wir den Wert an Punkt
x,y
Modul B. Wenn der Wert kleiner oder gleich
SelectPoint - Falloff
, verwenden wir den Wert an
x,y
Modul A.
Wenn es zwischen
SelectPoint - Falloff
und
SelectPoint
, führen wir eine lineare Interpolation zwischen den
x,y
Werten von Modul A und Modul B durch.
float select(x, y, moduleA, moduleB, moduleC, selectPoint, falloff) { float s = moduleC(x,y); if(s >= selectPoint) return moduleB(x,y); else if(s <= selectPoint - falloff) return moduleA(x,y); else { float a = moduleA(x,y); float b = moduleB(x,y); return lerp(a, b, (1.0 / ((selectPoint - (selectPoint-falloff)) / (selectPoint - s))); } }
In unserem Fall ist Modul A ein
Konstantenmodul mit dem Wert
0
. Modul B ist die erste Textur von Maske A und
Selector (Modul C) ist die zweite Maske von B.
SelectPoint
ist
0.4
und
Falloff
ist
0.1
. Als Ergebnis erhalten wir:
Ultimative MaskeDurch Erhöhen oder Verringern von
SelectPoint
verringern oder erhöhen wir die Schwarzmenge in der Maske. Durch Erhöhen oder Verringern des
falloff
erhöhen oder verringern wir die weichen Kanten der Masken. Anstelle einer der Masken könnte ich das
Konstantenmodul mit dem Wert
1
, aber ich wollte den "nicht maskierten" Bereichen etwas Zufälligkeit hinzufügen.
Farbkanal und Maske mischen
Jetzt müssen wir auf jeden der Farbkanäle eine Maske anwenden. Dies erfolgt über das
Mischmodul . Es kombiniert die Prozentsätze der Werte aus zwei Modulen, sodass die Summe der Werte 100% beträgt.
Das heißt, wir können 50% des Wertes in
x,y
Modul A und 50% des Wertes in
x,y
Modul B oder 75% und 25% usw. nehmen. Der Prozentsatz, den wir von jedem Modul nehmen, hängt von einem anderen Modul ab - Modul C. Wenn der Wert in
x,y
Modul C
0
, nehmen wir 100% von Modul A und 0% von Modul B. Wenn es
1
, nehmen wir inverse Werte.
Kombinieren Sie für jede Farbtextur.
- Modul A - Konstanter Wert 0
- Modul B ist der Farbkanal, den wir bereits gesehen haben
- Modul C - Maskenergebnis
Dies bedeutet, dass das Rauschen des Farbkanals nur dort angezeigt wird, wo die Maske Werte über
0
(Bereiche, die näher an Weiß liegen), und die Größe ihrer Sichtbarkeit vom Wert der Maske abhängt.
Hier ist das Ergebnis für unser Beispiel:
EndergebnisVergleichen Sie dies mit dem Original, bevor Sie eine Mischung mit einer Maske auftragen.
Vor dem Mischen mit einer MaskeVielleicht ist dieses Beispiel nicht sehr offensichtlich, aber aufgrund des Zufalls ist es schwierig, ein gutes Beispiel auszuwählen. Durch die Maske werden dunklere Bereiche erzeugt. Natürlich können Sie die Maske so anpassen, dass sie stärker ausgeprägt ist.
Hierbei ist es wichtig, dass dieselbe Maske auf den gesamten Farbkanal angewendet wird, dh, dieselben Bereiche erscheinen im Schatten.
Wir kombinieren alles miteinander
Unser erstes fertiges Beispiel:
Bereites BeispielEs verwendet die Kanäle Rot, Grün und Alpha:
Roter KanalGrüner KanalAlpha-KanalUnd dann legen wir sie einfach über unseren Sternenhimmel.
Alles sieht jetzt ziemlich gut aus, ist aber für ein Pixel-Art-Spiel nicht sehr geeignet. Wir müssen die Anzahl der Farben reduzieren ...
Medianschnitt
Dieser Teil des Artikels kann auf alles angewendet werden. Angenommen, Sie erzeugen eine Marmortextur und möchten die Anzahl der Farben reduzieren. Hier bietet sich der Median-Cut-Algorithmus an. Wir werden es verwenden, um die Anzahl der Farben im oben gezeigten Nebel zu reduzieren.
Dies geschieht,
bevor es dem Sternenhimmel überlagert wird. Die Anzahl der Farben ist völlig beliebig.
Der Median Cut-Algorithmus wie in Wikipedia beschrieben:
Angenommen, wir haben ein Bild mit einer beliebigen Anzahl von Pixeln und möchten eine Palette mit 16 Farben erzeugen. Legen Sie alle Pixel im Bild (d. H. Ihre RGB-Werte ) in den Papierkorb . Finden Sie heraus, welcher Farbkanal (rot, grün oder blau) unter allen Pixeln im Warenkorb den größten Wertebereich aufweist, und sortieren Sie die Pixel dann nach den Werten dieses Kanals. Wenn der blaue Kanal beispielsweise den größten Wertebereich hat, ist das Pixel mit dem RGB-Wert (32, 8, 16) kleiner als das Pixel mit dem RGB-Wert (1, 2, 24), da 16 <24. Platzieren Sie nach dem Sortieren des Korbs die obere Hälfte der Pixel in einen neuen Korb. (Dieser Schritt gab dem Medianschnitt-Algorithmus den Namen. Körbe werden durch den Median der Pixelliste in zwei Hälften geteilt.) Wiederholen Sie den Vorgang für beide Körbe, wodurch wir 4 Körbe erhalten. Wiederholen Sie diesen Vorgang für alle 4 Körbe, erhalten Sie 8 Körbe und wiederholen Sie für 8 Körbe. Wir erhalten 16 Körbe. Wir mitteln die Pixel in jedem der Körbe und erhalten eine Palette von 16 Farben. Da sich die Anzahl der Körbe bei jeder Iteration verdoppelt, kann der Algorithmus nur solche Paletten erzeugen, wobei die Anzahl der Farben eine Zweierpotenz ist . Um beispielsweise eine 12-Farben-Palette zu erstellen, müssen Sie zuerst eine 16-Farben-Palette erstellen und dann einige Farben irgendwie kombinieren.
Quelle: https://en.wikipedia.org/wiki/Median_cut
Diese Erklärung erschien mir eher schlecht und nicht besonders nützlich. Bei der Implementierung des Algorithmus werden auf diese Weise ziemlich hässliche Bilder erhalten. Ich habe es mit einigen Änderungen implementiert:
- Wir speichern den
boxes
zusammen mit dem Wert, der das Intervall angibt (mehr dazu weiter unten). In der box
einfach eine dynamische Anzahl von Pixeln aus dem Originalbild gespeichert. - Fügen Sie alle Pixel des Originalbilds als erstes
und verwenden Sie das Intervall 0
- Während die Gesamtzahl der
geringer ist als die erforderliche Anzahl von Farben, fahren wir mit den folgenden Schritten fort. - Wenn der Intervallwert
0
, bestimmen wir für jedes aktuelle Feld den Hauptfarbkanal dieses box
und sortieren dann die Pixel in diesem box
nach dieser Farbe. — Red, Green, Blue Alpha, . , redRange = Max(Red) - Min(Red)
. , . box
boxes
. , box
.- , 4 5
box
, boxes
. , , , . , , . box
( == ) boxes
. 0
( ). , , , — . .
, , , , . , , .
, . RGB, .
.
Das OriginalMedian Cut 16, 16 . , -, . , . median cut, ( ), .
16 2box
, . . , . , , . 1, 2 3 . 16 , 13 .
, . . .
, (dithering), -. , , .
- . . . :
Das Original16 :
16-: