In diesem Tutorial werde ich darüber sprechen, wie Sie mit den Shadern den beliebten Sprite-Doodle-Effekt in Unity wiederherstellen können. Wenn Ihr Stil diesen Stil erfordert, erfahren Sie in diesem Artikel, wie Sie ihn erreichen, ohne eine Reihe zusätzlicher Bilder zu rendern.
In den letzten Jahren ist dieser Stil immer beliebter geworden und wird in Spielen wie
GoNNER und
Baba is You aktiv eingesetzt.
Dieses Tutorial behandelt alles, was Sie brauchen, von den Grundlagen der Shader-Codierung bis zur verwendeten Mathematik. Am Ende des Artikels befindet sich ein Link zum Herunterladen des vollständigen Unity-Pakets.
Der Erfolg von
Doodle Studio 95 hat mich dazu inspiriert, dieses Tutorial zu erstellen
! .
Einführung
In meinem Blog beschäftige ich mich mit recht komplexen Themen, von der
Mathematik der inversen Kinematik bis zur
atmosphärischen Rayleigh-Streuung . Ich mag es wirklich, solch schwierige Themen einem breiten Publikum verständlich zu machen. Aber die Zahl der Menschen, die sich für sie interessieren und über ein ausreichendes technisches Niveau verfügen, ist nicht so groß. Daher sollten Sie sich nicht wundern, dass die beliebtesten Artikel die einfachsten sind. Dies gilt auch für einen kürzlich
veröffentlichten Tweet von Nick Caman, in dem er zeigte, wie man in Unity
einen Doodle-Effekt erzeugt .
Nach 1000 Likes und 4000 Retweets wurde deutlich, dass eine starke Nachfrage nach einfacheren Tutorials besteht, die auch von Leuten studiert werden können, die fast keine Kenntnisse über das Erstellen von Shadern haben.
Wenn Sie nach einer professionellen und effektiven Möglichkeit suchen, 2D-Sprites mit einem hohen Maß an künstlerischer Kontrolle zu animieren, empfehle ich
Doodle Studio 95! (siehe GIF unten).
Hier können Sie sich einige Spiele ansehen, die dieses Tool verwenden.
Doodle-Effekt-Anatomie
Um den Doodle-Effekt wiederherzustellen, müssen wir zunächst verstehen, wie er funktioniert und welche Techniken darin verwendet werden.
Shader-Effekt. Erstens möchten wir, dass dieser Effekt so einfach wie möglich ist und keine zusätzlichen Skripte erforderlich sind. Dies ist dank der Verwendung von
Shadern möglich , die Unity mitteilen, wie 3D-Modelle (einschließlich flacher!) Auf dem Bildschirm gerendert werden sollen. Wenn Sie mit der Welt
der Shader-Codierung nicht vertraut sind, sollten Sie meinen Artikel
Eine sanfte Einführung in Shader lesen .
Sprite-Shader. Unity kommt mit vielen Arten von Shadern. Wenn Sie die mitgelieferten Unity 2D-Werkzeuge verwenden, arbeiten Sie höchstwahrscheinlich mit
Sprites . In diesem
SpriteRenderer
benötigen Sie
Sprite Shader - einen speziellen Shader-Typ, der mit
SpriteRenderer
Unity kompatibel ist. Oder Sie können mit dem traditionelleren
Unlit-Shader beginnen .
Scheitelpunktversatz. Wenn Sie Sprites manuell zeichnen, stimmt kein Rahmen vollständig mit den anderen überein. Wir möchten das Sprite auf irgendeine Weise „wackeln“ lassen, um diesen Effekt zu simulieren. Shader haben eine sehr effektive Möglichkeit, dies zu tun, indem sie den
Scheitelpunktversatz verwenden . Dies ist eine Technik zum Ändern der Position der Scheitelpunkte eines 3D-Objekts. Wenn wir sie zufällig verschieben, erhalten wir den gewünschten Effekt.
Reisezeit. Freihandanimationen haben normalerweise eine niedrige Bildrate. Wenn wir beispielsweise fünf Bilder pro Sekunde simulieren möchten, müssen wir die Position der Sprite-Scheitelpunkte fünfmal pro Sekunde ändern. Es ist jedoch wahrscheinlich, dass Unity das Spiel mit einer viel höheren Aktualisierungsrate ausführt. vielleicht mit 30 oder sogar 60 Bildern pro Sekunde. Damit sich das Sprite nicht 60 Mal pro Sekunde ändert, müssen Sie an der Animations-Timing-Komponente arbeiten.
Schritt 1: Fertigstellen des Sprite-Shaders
Wenn Sie in Unity einen neuen Shader erstellen möchten, ist die Auswahl sehr begrenzt. Der nächste Shader, mit dem wir beginnen können, ist
Unlit Shader , obwohl er für unsere Zwecke nicht unbedingt am besten geeignet ist.
Wenn der Doodle-Shader vollständig mit
SpriteRenderer
Unity kompatibel sein
SpriteRenderer
, müssen wir den vorhandenen
Sprite-Shader ergänzen. Leider können wir nicht direkt von Unity selbst darauf zugreifen.
Sie können darauf zugreifen, indem Sie auf die
Unity-Download-Archivseite gehen und das
Build in Shader- Paket für die Version von Unity herunterladen, mit der Sie arbeiten. Dies ist eine Zip-Datei, die den Quellcode aller Shader enthält, die mit Ihrem Unity-Build geliefert werden.
Entpacken Sie es nach dem Herunterladen und suchen Sie die Datei
Sprites-Diffuse.shader
im
Sprites-Diffuse.shader
builtin_shaders-2018.1.6f1\DefaultResourcesExtra
. Dies ist die Datei, die wir im Tutorial verwenden werden.
Sprites-Diffuse ist kein Standard-Sprite-Shader!Beim Erstellen eines neuen Sprites verwendet das Standardmaterial einen Shader namens Sprites-Default.shader
, nicht Sprites-Diffuse.shader
.
Der Unterschied zwischen den beiden besteht darin, dass der erste keine Beleuchtung verwendet und der zweite auf die Beleuchtung in der Szene reagiert. Aufgrund der Art der Unity-Implementierung ist die diffuse Version viel einfacher zu bearbeiten als die Version ohne Beleuchtung.
Am Ende dieses Tutorials befindet sich ein Link zum Herunterladen der Doodle-Shader mit und ohne Beleuchtung.
Schritt 2: Scheitelpunkte versetzen
In
Sprites-Diffuse.shader
gibt es eine Funktion namens
vert
, die die
Vertex-Funktion ist , über die wir oben gesprochen haben. Sein Name ist nicht wichtig, die Hauptsache ist, dass er mit dem im
vertex:
Abschnitt der
#pragma
angegebenen
#pragma
:
#pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing
Kurz gesagt, die Scheitelpunktfunktion wird für jeden Scheitelpunkt des 3D-Modells aufgerufen und entscheidet, wie er dem zweidimensionalen Bildschirmraum überlagert wird. In diesem Tutorial interessiert uns nur, wie ein Objekt verschoben wird.
Der Parameter
appdata_full v
enthält das
vertex
, das die 3D-Position jedes Scheitelpunkts im
Objektraum enthält . Wenn sich der Wert ändert, verschiebt sich der Scheitelpunkt. Das heißt, der unten gezeigte Code überträgt das Objekt mit seinem Shader eine Einheit entlang der X-Achse.
void vert (inout appdata_full v, out Input o) { v.vertex = UnityFlipSprite(v.vertex, _Flip); v.vertex.x += 1; #if defined(PIXELSNAP_ON) v.vertex = UnityPixelSnap (v.vertex); #endif UNITY_INITIALIZE_OUTPUT(Input, o); o.color = v.color * _Color * _RendererColor; }
Standardmäßig arbeiten in Unity erstellte 2D-Spiele nur mit der X- und Y-Achse.
v.vertex.xy
müssen wir
v.vertex.xy
ändern, um das Sprite auf einer zweidimensionalen Ebene zu verschieben.
Was ist Objektraum?Das vertex
der Struktur appdata_full
enthält die Position des aktuellen Scheitelpunkts, der vom Shader im Objektbereich verarbeitet wird. Dies ist die Position des Scheitelpunkts unter der Annahme, dass sich das Objekt im Zentrum der Welt befindet (0,0,0), ohne den Maßstab zu ändern und ohne Drehung.
Im Weltraum ausgedrückte Gipfel spiegeln ihre reale Position in der Unity-Szene wider.
Warum bewegt sich das Objekt nicht mit einer Geschwindigkeit von einem Meter pro Bild?Wenn wir der Komponente x
des transform.position
Werts in der Update
Methode eines C # -Skripts +1 hinzufügen, sehen wir, wie das Objekt mit einer Geschwindigkeit von 1 Meter pro Frame nach rechts fliegt, dh ungefähr 216 Kilometer pro Stunde.
Dies liegt daran, dass die Änderungen, die C # vornimmt, die Position selbst ändern. In der Scheitelpunktfunktion geschieht dies nicht. Der Shader ändert nur die visuelle Darstellung des Modells, aktualisiert oder ändert jedoch nicht die gespeicherten Scheitelpunkte des Modells. Aus diesem Grund verschiebt das Hinzufügen von +1 zu v.vertex.x
ein Objekt nur einmal um einen Meter.
Denken Sie daran, das Sprite als Tight zu importieren!Dieser Effekt verschiebt die Oberseite des Sprites. Traditionell werden Sprites als Vierecke in Unity importiert (siehe Abbildung links). Dies bedeutet, dass sie nur vier Spitzen haben. Wenn ja, können nur diese Punkte verschoben werden, was die Stärke des Doodle-Effekts verringert.
Für eine stärkere und realistischere Verzerrung müssen Sie Sprites importieren, indem Sie für den Parameter
Mesh Type die
Option Tight auswählen, wodurch sie in eine konvexe Form umgewandelt werden (siehe Abbildung rechts).
Dies erhöht die Anzahl der Eckpunkte. Das ist nicht immer wünschenswert, aber genau das brauchen wir jetzt.
Zufälliger Versatz
Der Doodle-Effekt verschiebt zufällig die Position jedes Scheitelpunkts. Das Abtasten von
Zufallszahlen in einem Shader war schon immer eine schwierige Aufgabe. Dies wird hauptsächlich durch die verteilte GPU-Architektur verursacht, die die Effizienz der Neuerstellung des in den meisten Bibliotheken (einschließlich
Mathf.Random
) verwendeten Algorithmus verkompliziert und verringert.
Nick Camans Post verwendete eine Rauschstruktur, die beim Abtasten die Illusion von Zufälligkeit vermittelt. Im Kontext Ihres Projekts ist dies möglicherweise nicht der effektivste Ansatz, da sich in diesem Fall die Anzahl der vom Shader ausgeführten Textur-Suchvorgänge verdoppelt.
Daher werden in den meisten Shadern eher verwirrende und chaotische Funktionen verwendet, die uns trotz ihres Determinismus keine Regelmäßigkeiten zu haben scheinen. Und da sie verteilt werden müssen, muss jede Zufallszahl mit einem eigenen Startwert generiert werden. Dies ist großartig für uns, da die Position jedes Scheitelpunkts eindeutig sein muss. Wir können dies verwenden, um eine Zufallszahl an jeden Scheitelpunkt zu binden. Wir werden die Implementierung dieser Zufallsfunktion später diskutieren; Nennen
random3
.
Wir können
random3
, um einen zufälligen Versatz für jeden Scheitelpunkt zu generieren. Im folgenden Beispiel werden Zufallszahlen mit der Eigenschaft
_NoiseScale
skaliert, mit der Sie die Verschiebungskraft steuern können.
void vert (inout appdata_full v, out Input o) { ... float2 noise = random3(v.vertex.xyz).xy * _NoiseScale; v.vertex.xy += noise; ... }
Jetzt müssen wir
random3
Code
random3
schreiben.
Zufälligkeit im Shader
Eine der häufigsten und bekanntesten Pseudozufallsfunktionen, die in Shadern verwendet werden, stammt aus einem Artikel von W. Ray aus dem Jahr 1998 unter dem Titel "
Über die Erzeugung von Zufallszahlen mit Hilfe von y = [(a + x) sin (bx)] mod 1 ".
float rand(float2 co) { return fract(sin(dot(co.xy ,float2(12.9898,78.233))) * 43758.5453); }
Diese Funktion ist deterministisch (dh sie ist nicht
wirklich zufällig), verhält sich jedoch so zufällig, dass sie völlig zufällig aussieht. Solche Funktionen werden als
Pseudozufallsfunktionen bezeichnet . Für mein Tutorial habe ich eine komplexere Funktion gewählt, die von
Nikita Miropolsky erfunden wurde
.Das Generieren einer Pseudozufallszahl in einem Shader ist ein sehr komplexes Thema. Wenn Sie mehr über sie erfahren
möchten , dann hat
The Book of Shaders ein gutes Kapitel über sie. Darüber hinaus hat
Patricio Gonzales Vivo ein großes Repository mit Pseudozufallsfunktionen namens
GLSL-Rauschen erstellt , das in Shadern verwendet werden kann.
Schritt 3: Zeit hinzufügen
Dank des von uns geschriebenen Codes wird jeder Punkt in jedem Frame um den gleichen Betrag verschoben. Wir erhalten also ein verzerrtes Sprite, keinen Doodle-Effekt. Um dies zu beheben, müssen Sie einen Weg finden, um den Effekt im Laufe der Zeit zu ändern. Eine der einfachsten Möglichkeiten, dies zu tun, besteht darin, die Scheitelpunktposition und die aktuelle Zeit zu verwenden, um eine Zufallszahl zu generieren.
In unserem Fall habe ich gerade die aktuelle Zeit in Sekunden
_Time.y
zur Scheitelpunktposition hinzugefügt.
float time = float3(_Time.y, 0, 0); float2 noise = random3(v.vertex.xyz + time).xy * _NoiseScale; v.vertex.xy += noise;
Komplexere Effekte erfordern möglicherweise komplexere Methoden, um der Gleichung Zeit hinzuzufügen. Da wir aber nur an einem intermittierenden Zufallseffekt interessiert sind, sind zwei Werte mehr als genug.
Zeitumschaltung
Das Hauptproblem beim Hinzufügen von
_Time.y
besteht darin, dass das Sprite in jedem Frame animiert wird. Dies ist für uns unerwünscht, da die meisten handgezeichneten Animationen eine niedrige Bildrate haben. Die Zeitkomponente sollte nicht kontinuierlich, sondern diskret sein. Das heißt, wenn wir fünf Bilder pro Sekunde anzeigen möchten, sollte sich dies nur fünfmal pro Sekunde ändern. Das heißt, die Zeit sollte
an eine Fünftelsekunde
gebunden sein . Die einzig gültigen Werte sollten sein
,
,
,
,
,
mit und so weiter ...
Ich habe Snap bereits in meinem Blog in
How To Snap To Grid behandelt . In diesem Artikel habe ich eine Lösung für das Problem vorgeschlagen, die Position eines Objekts auf einem räumlichen Gitter zu binden. Wenn wir die Zeit an ein Zeitraster binden müssen, ist die Mathematik und damit der Code dieselbe.
Die unten gezeigte Funktion nimmt die Zahl
x
und bindet sie an ganzzahlige Werte, die ein Vielfaches von
snap
.
inline float snap (float x, float snap) { return snap * round(x / snap); }
Das heißt, unser Code wird folgendermaßen:
float time = snap(_Time.y, _NoiseSnap); float2 noise = random3(v.vertex.xyz + float3(time, 0.0, 0.0) ).xy * _NoiseScale; v.vertex.xy += noise;
Fazit
Das Unity-Paket für diesen Effekt kann kostenlos
auf Patreon heruntergeladen werden.
Zusätzliche Ressourcen
In den letzten Monaten ist eine große Anzahl von Spielen im Stil von Kritzeleien erschienen. Es scheint mir, dass der Grund dafür der Erfolg von
Doodle Studio 95 war! - Ein von
Fernando Ramallo entwickeltes Tool für Unity. Wenn dieser Stil für Ihr Spiel geeignet ist, empfehle ich den Kauf dieses erstaunlichen Tools.