Dieser Effekt wurde von der 
Episode von Powerpuff Girls inspiriert. Ich wollte den Effekt der Ausbreitung von Farben in einer Schwarz-Weiß-Welt erzeugen, ihn aber 
in den Koordinaten des Weltraums implementieren , um zu sehen, wie 
Farbe Objekte malt und nicht wie in einem Cartoon flach auf dem Bildschirm verteilt.
Ich habe den Effekt in der neuen 
Lightweight-Rendering-Pipeline der Unity-Engine erstellt, einem integrierten Beispiel für die Pipeline der Scriptable Rendering-Pipeline. Alle Konzepte gelten für andere Pipelines, aber einige integrierte Funktionen oder Matrizen können unterschiedliche Namen haben. Ich habe auch den neuen Nachbearbeitungsstapel verwendet, aber im Tutorial werde ich eine detaillierte Beschreibung seiner Einstellungen weglassen, da er in anderen Handbüchern, beispielsweise in 
diesem Video, recht gut beschrieben ist.
Der Effekt der Nachbearbeitung in Graustufen
Nur als Referenz: So sieht eine Szene ohne Nachbearbeitungseffekte aus.
Für diesen Effekt habe ich das neue Unity 2018-Nachbearbeitungspaket verwendet, das vom Paketmanager heruntergeladen werden kann. Wenn Sie nicht wissen, wie man es benutzt, empfehle ich 
dieses Tutorial .
Ich habe meinen eigenen Effekt geschrieben, indem ich die in C # geschriebenen Klassen PostProcessingEffectSettings und PostProcessEffectRenderer erweitert habe, deren Quellcode 
hier zu sehen 
ist . Tatsächlich habe ich mit diesen Effekten auf der CPU-Seite (in C # -Code) nichts besonders Interessantes gemacht, außer dass ich dem Inspektor eine Gruppe allgemeiner Eigenschaften hinzugefügt habe, sodass ich im Tutorial nicht erklären werde, wie dies zu tun ist. Ich hoffe mein Code spricht für sich.
Fahren wir mit dem Shader-Code fort und beginnen mit dem Graustufeneffekt. Im Tutorial werden die Shaderlab-Datei, die Eingabestrukturen und der Vertex-Shader nicht geändert, sodass Sie den Quellcode 
hier sehen können . Stattdessen kümmern wir uns um den Fragment-Shader.
Um eine Farbe in eine Graustufe umzuwandeln, 
reduzieren wir 
den Wert jedes Pixels auf einen Luminanzwert , der seine 
Helligkeit beschreibt. Dies kann erreicht werden, indem das Skalarprodukt des 
Farbwerts der Kameratextur und des 
gewichteten Vektors genommen wird , der den Beitrag jedes Farbkanals zur Gesamtfarbhelligkeit beschreibt.
Warum verwenden wir Skalarprodukte? Vergessen Sie nicht, dass Skalarprodukte wie folgt berechnet werden:
dot(a, b) = a x * b x + a y * b y + a z * b zIn diesem Fall multiplizieren wir jeden Kanal des 
Farbwerts mit dem 
Gewicht . Dann fügen wir diese Produkte hinzu, um sie auf einen einzigen Skalarwert zu reduzieren. Wenn die RGB-Farbe in den Kanälen R, G und B dieselben Werte hat, wird die Farbe grau.
So sieht der Shader-Code aus:
 float4 fullColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.screenPos); float3 weight = float3(0.299, 0.587, 0.114); float luminance = dot(fullColor.rgb, weight); float3 greyscale = luminance.xxx; return float4(greyscale, 1.0); 
Wenn der Basis-Shader korrekt konfiguriert ist, sollte der Nachbearbeitungseffekt den gesamten Bildschirm in Graustufen färben.
Farbwiedergabe im Weltraum
Da dies ein Nachbearbeitungseffekt ist, haben 
wir im Vertex-Shader 
keine Informationen zur Szenengeometrie. In der Nachbearbeitungsphase haben wir nur das 
von der Kamera gerenderte Bild und den 
Abstand der abgeschnittenen Koordinaten zum Abtasten. Wir möchten jedoch, dass sich der Farbeffekt über Objekte verteilt, als ob er auf der Welt stattfinden würde, und nicht nur auf einem Flachbildschirm.
Um diesen Effekt in die Geometrie der Szene zu zeichnen, benötigen wir die 
Koordinaten des Weltraums jedes Pixels. Um von den 
Koordinaten des Raums der abgeschnittenen Koordinaten zu den 
Koordinaten des Weltraums zu gelangen , müssen wir eine 
Transformation des Koordinatenraums durchführen .
Um von einem Koordinatenraum in einen anderen zu gelangen, wird normalerweise eine Matrix benötigt, die die Transformation vom Koordinatenraum A in den Raum B definiert. Um von A nach B zu gelangen, multiplizieren wir den Vektor im Koordinatenraum A mit dieser Transformationsmatrix. In unserem Fall führen wir den folgenden Übergang durch: den 
Raum der abgeschnittenen Koordinaten (Clipraum) -> 
Ansichtsraum (Ansichtsraum) -> 
Weltraum (Weltraum) . Das heißt, wir benötigen die Clip-to-View-Space-Matrix und die View-to-World-Space-Matrix, die Unity bereitstellt.
Die 
Einheitskoordinaten des abgeschnittenen Koordinatenraums haben jedoch keinen z-Wert , der die Tiefe des Pixels oder den Abstand zur Kamera bestimmt. Wir brauchen diesen Wert, um vom Raum der abgeschnittenen Koordinaten in den Artenraum zu gelangen. Beginnen wir damit!
Tiefenpufferwert abrufen
Wenn die Rendering-Pipeline aktiviert ist, zeichnet sie im 
Ansichtsfenster eine Textur, in der 
die z-Werte in einer Struktur 
gespeichert sind , 
die als 
Tiefenpuffer bezeichnet wird . Wir können diesen Puffer abtasten, um den fehlenden 
z-Wert unseres Koordinatenraums von abgeschnittenen Koordinaten zu erhalten!
Stellen Sie zunächst sicher, dass der 
Tiefenpuffer tatsächlich gerendert wird, indem Sie im Inspektor auf den Abschnitt „Zusätzliche Daten hinzufügen“ der Kamera klicken und aktivieren, dass das Kontrollkästchen „Tiefenstruktur erforderlich“ aktiviert ist. Stellen Sie außerdem sicher, dass die Option MSAA zulassen für die Kamera aktiviert ist. Ich weiß nicht, warum dieser Effekt überprüft werden muss, aber es ist so. Wenn der 
Tiefenpuffer gezeichnet ist, sollte im 
Frame-Debugger die Stufe 
„Depth Prepass“ angezeigt werden .
Erstellen Sie einen _CameraDepthTexture-Sampler in der 
hlsl-Datei TEXTURE2D_SAMPLER2D(_CameraDepthTexture, sampler_CameraDepthTexture); 
Schreiben wir nun die GetWorldFromViewPosition-Funktion und überprüfen damit zunächst 
den Tiefenpuffer. (Später werden wir es erweitern, um eine Position in der Welt zu bekommen.)
 float3 GetWorldFromViewPosition (VertexOutput i) { float z = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, i.screenPos).r; return z.xxx; } 
Zeichnen Sie im Fragment-Shader den Wert des Tiefen-Textur-Samples.
 float3 depth = GetWorldFromViewPosition(i); return float4(depth, 1.0); 
So sehen meine Ergebnisse aus, wenn es nur eine hügelige Ebene in der Szene gibt (ich habe alle Bäume ausgeschaltet, um das Testen der Werte des Weltraums weiter zu vereinfachen). Ihr Ergebnis sollte ähnlich aussehen. Schwarz-Weiß-Werte beschreiben den Abstand von der Geometrie zur Kamera.
Hier sind einige Schritte, die Sie ausführen können, wenn Sie auf Probleme stoßen:
- Stellen Sie sicher, dass für die Kamera die Tiefenwiedergabe aktiviert ist.
- Stellen Sie sicher, dass für die Kamera MSAA aktiviert ist.
- Versuchen Sie, die nahe und ferne Ebene der Kamera zu ändern.
- Stellen Sie sicher, dass die Objekte, die Sie im Tiefenpuffer sehen möchten, einen Shader mit einem Tiefenpass verwenden. Dies stellt sicher, dass das Objekt in den Tiefenpuffer zeichnet. Alle Standard-Shader in LWRP tun dies.
Wertschöpfung im Weltraum
Nachdem wir nun alle Informationen haben, die für den 
Raum der abgeschnittenen Koordinaten erforderlich sind, wollen wir uns in den 
Artenraum und dann in den 
Weltraum verwandeln.
Beachten Sie, dass sich die für diese Operationen erforderlichen Transformationsmatrizen bereits in der SRP-Bibliothek befinden. Sie sind jedoch in der C # -Bibliothek der Unity-Engine enthalten, daher habe ich sie in den 
Rader in der Render-Funktion des 
ColorSpreadRenderer- Skripts 
eingefügt :
 sheet.properties.SetMatrix("unity_ViewToWorldMatrix", context.camera.cameraToWorldMatrix); sheet.properties.SetMatrix("unity_InverseProjectionMatrix", projectionMatrix.inverse); 
Erweitern wir nun unsere GetWorldFromViewPosition-Funktion.
Zuerst müssen wir die Position im Ansichtsfenster ermitteln, indem wir 
die Position im abgeschnittenen Koordinatenraum mit der InverseProjectionMatrix multiplizieren . Wir müssen auch etwas mehr Voodoo-Magie mit einer Bildschirmposition machen, die damit zusammenhängt, wie Unity seine Position im Raum abgeschnittener Koordinaten speichert.
Schließlich können wir 
die Position im Ansichtsfenster mit ViewToWorldMatrix multiplizieren , um die Position im 
Weltraum zu erhalten .
 float3 GetWorldFromViewPosition (VertexOutput i) {  
Lassen Sie uns überprüfen, ob die Positionen im globalen Raum korrekt sind. Zu diesem Zweck habe ich einen 
Shader geschrieben , der nur die Position eines Objekts im 
Weltraum zurückgibt. Dies ist eine ziemlich einfache Berechnung, die auf einem regulären Shader basiert, dessen Richtigkeit vertrauenswürdig ist. Deaktivieren Sie den Effekt der Nachbearbeitung und machen Sie einen Screenshot dieses Test-Shaders für 
den Weltraum . Mein nach dem Anwenden des Shaders auf die Erdoberfläche in der Szene sieht so aus:
(Beachten Sie, dass die Werte im Weltraum viel größer als 1,0 sind. Machen Sie sich also keine Sorgen, dass diese Farben sinnvoll sind. Stellen Sie stattdessen sicher, dass die Ergebnisse für die „wahren“ und „berechneten“ Antworten gleich sind.) Kehren wir als Nächstes zum Test zurück Das Objekt ist gewöhnliches Material (und nicht das Testmaterial des Weltraums) und schaltet dann den Nachbearbeitungseffekt wieder ein. Meine Ergebnisse sehen folgendermaßen aus:
Dies ist dem Test-Shader, den ich geschrieben habe, völlig ähnlich, dh die Berechnungen des Weltraums sind höchstwahrscheinlich korrekt!
Zeichnen eines Kreises im Weltraum
Jetzt, wo wir 
Positionen im Weltraum haben , können wir einen Farbkreis in der Szene zeichnen! Wir müssen den 
Radius einstellen, innerhalb dessen der Effekt Farbe zeichnet. Draußen wird das Bild durch den Effekt in Graustufen gerendert. Um es einzustellen, müssen Sie die Werte für 
den Effektradius ( 
_MaxSize ) und den Mittelpunkt des Kreises (_Center) anpassen. Ich habe diese Werte in der C # 
ColorSpread- Klasse so festgelegt, dass sie im Inspektor sichtbar sind. Erweitern wir unseren Fragment-Shader, indem wir ihn zwingen, 
zu überprüfen, ob sich das aktuelle Pixel innerhalb des Kreisradius befindet :
 float4 Frag(VertexOutput i) : SV_Target { float3 worldPos = GetWorldFromViewPosition(i);  
Schließlich können wir die Farbe basierend darauf zeichnen, ob sie sich innerhalb eines 
Radius im 
Weltraum befindet . So sieht der Basiseffekt aus!
Hinzufügen von Spezialeffekten
Ich werde ein paar weitere Techniken betrachten, mit denen die Farbe über den Boden verteilt wird. Die volle Wirkung ist viel mehr, aber das Tutorial ist bereits zu umfangreich geworden, sodass wir uns auf das Wichtigste beschränken werden.
Kreisvergrößerungsanimation
Wir wollen, dass sich der Effekt auf der ganzen Welt ausbreitet, das heißt, als würde er wachsen. Dazu müssen Sie den 
Radius je nach Zeit ändern.
_StartTime gibt den Zeitpunkt an, zu dem der Kreis wachsen soll. In meinem Projekt habe ich ein zusätzliches Skript verwendet, mit dem Sie auf eine beliebige Stelle auf dem Bildschirm klicken können, um das Wachstum des Kreises zu starten. In diesem Fall entspricht die Startzeit der Zeit, zu der die Maus gedrückt wurde.
_GrowthSpeed legt die Geschwindigkeit fest, mit der der Kreis vergrößert wird.
 
Wir müssen auch die Entfernungsprüfung aktualisieren, um die aktuelle Entfernung mit dem zunehmenden 
Radius des Effekts zu vergleichen , und nicht mit _MaxSize.
 
So sollte das Ergebnis aussehen:
Hinzufügen zum Rauschradius
Ich wollte, dass der Effekt eher einer Farbunschärfe ähnelt, nicht nur einem wachsenden Kreis. 
Fügen Sie dazu dem Radius des Effekts Rauschen hinzu, damit die Verteilung ungleichmäßig ist.
Zuerst müssen wir die Textur im 
Weltraum abtasten . Die UV-Koordinaten von i.screenPos befinden sich im 
Bildschirmbereich. Wenn wir darauf basierend abtasten, bewegt sich die Form des Effekts mit der Kamera. Verwenden wir also die Koordinaten im 
Weltraum . Ich habe den Parameter 
_NoiseTexScale hinzugefügt, um die 
Skalierung des Rauschtextur-Samples zu steuern, da die Koordinaten im Weltraum ziemlich groß sind.
 
Lassen Sie uns nun die Rauschtextur abtasten und diesen Wert zum Radius des Effekts hinzufügen. Ich habe die _NoiseSize-Skala verwendet, um die Rauschgröße besser steuern zu können.
 
So sehen die Ergebnisse nach einigen Optimierungen aus:
Abschließend
Sie können die Aktualisierungen der Tutorials auf meinem 
Twitter verfolgen, und auf 
Twitch gebe ich Codierungs-Streams aus! (Außerdem streame ich von Zeit zu Zeit Spiele. Seien Sie also nicht überrascht, wenn Sie mich in meinem Pyjama sitzen und Kingdom Hearts 3 spielen sehen.)
Danksagung: