Benutzerdefinierte Beleuchtung im Shader Graph: Erweitern Sie Ihre Diagramme im Jahr 2019

Mit der Veröffentlichung von Unity Editor 2019.1 ist das Shader Graph-Paket offiziell aus der Vorschau herausgekommen! Jetzt, in 2019.2, bringen wir noch mehr Features und Funktionen in Shader Graph.
Um benutzerdefinierten Code innerhalb Ihres Shader-Graphen zu pflegen, können Sie jetzt unseren neuen Knoten Benutzerdefinierte Funktion verwenden. Mit diesem Knoten können Sie Ihre eigenen benutzerdefinierten Ein- und Ausgänge definieren, sie neu anordnen und benutzerdefinierte Funktionen entweder direkt in den Knoten selbst oder durch Verweis auf eine externe Datei einfügen.
Subgraphen haben ebenfalls ein Upgrade erhalten: Sie können jetzt Ihre eigenen Ausgänge für Subgraphen definieren, mit verschiedenen Typen, benutzerdefinierten Namen und neu sortierbaren Anschlüssen. Außerdem unterstützt das Blackboard für Unterdiagramme jetzt alle Datentypen, die auch das Hauptdiagramm unterstützt.
Die Verwendung des Shader-Graphen zur Erstellung leistungsstarker und optimierter Shader ist jetzt noch einfacher. In 2019.2 können Sie nun die Genauigkeit der Berechnungen in Ihrem Diagramm manuell einstellen, entweder graphenweit oder auf einer Knotenbasis. Mit unseren neuen Farbmodi können Sie schnell und einfach den Präzisionsfluss und die Kategorie der Knoten visualisieren oder benutzerdefinierte Farben für Ihren eigenen Gebrauch anzeigen!
Weitere Informationen zu diesen neuen Funktionen finden Sie in der Shader Graph-Dokumentation.
Um Ihnen den Einstieg in den neuen Workflow für benutzerdefinierte Funktionen zu erleichtern, haben wir ein Beispielprojekt mit einer schrittweisen Anleitung erstellt. Laden Sie das Projekt aus unserem Repository herunter und folgen Sie ihm! Dieses Projekt zeigt Ihnen, wie Sie den Knoten Custom Function verwenden, um eigene Beleuchtungs-Shader für die Lightweight Render Pipeline (LWRP) zu schreiben. Wenn Sie mit einem neuen Projekt weitermachen wollen, stellen Sie sicher, dass Sie den Editor 2019.2 und das LWRP-Paket Version 6.9.1 oder höher verwenden.
Um zu beginnen, müssen wir Informationen von der Hauptleuchte in unserer Szene erhalten. Wählen Sie zunächst Erstellen > Shader > Unbeleuchteter Graph, um einen neuen unbeleuchteten Shader-Graphen zu erstellen. Suchen Sie im Menü Knoten erstellen den neuen Knoten Benutzerdefinierte Funktion, und klicken Sie auf das Zahnradsymbol oben rechts, um das Knotenmenü zu öffnen.
In diesem Menü können Sie Eingänge und Ausgänge hinzufügen. Fügen Sie zwei Ausgänge für Richtung und Farbe hinzu, und wählen Sie für beide den Vektor 3. Wenn Sie eine Fehlermeldung "undeklarierter Bezeichner" sehen, machen Sie sich keine Sorgen; dies wird verschwinden, wenn wir anfangen, unseren Code hinzuzufügen. Wählen Sie im Dropdown-Menü Typ die Option String. Aktualisieren Sie Ihren Funktionsnamen - in diesem Beispiel verwenden wir "MainLight". Jetzt können wir damit beginnen, unseren eigenen Code in das Textfeld einzufügen.

Zuerst werden wir ein Flag namens `#ifdef SHADERGRAPH_PREVIEW` verwenden. Da die Vorschauboxen auf den Knoten keinen Zugriff auf die Lichtdaten haben, müssen wir dem Knoten mitteilen, was auf den Vorschauboxen im Diagramm angezeigt werden soll. Mit "#ifdef" wird der Compiler angewiesen, in verschiedenen Situationen unterschiedlichen Code zu verwenden. Beginnen Sie mit der Festlegung der Ausweichwerte für die Ausgangsports.
Als nächstes verwenden wir `#else`, um dem Compiler mitzuteilen, was er tun soll, wenn keine Vorschau vorliegt. Hier erhalten wir unsere eigentlichen Lichtdaten. Verwenden Sie die eingebaute Funktion `GetMainLight()` aus dem LWRP-Paket. Mit diesen Informationen können wir die Ausgänge Richtung und Farbe zuweisen. Ihre benutzerdefinierte Funktion sollte nun wie folgt aussehen:
Nun ist es ratsam, diesen Knoten einer Gruppe hinzuzufügen, damit Sie sich merken können, was er tut. Klicken Sie mit der rechten Maustaste auf den Knoten, wählen Sie Gruppe aus Auswahl erstellen und benennen Sie den Gruppentitel um, um die Funktion des Knotens zu beschreiben. Hier haben wir "Get Main Light" eingegeben.

Jetzt, da wir unsere Lichtdaten haben, können wir einige Schattierungen berechnen. Wir beginnen mit einer Standard-Lambertianischen Beleuchtung, also nehmen wir das Punktprodukt aus dem Normalenvektor der Welt und der Lichtrichtung. Übergeben Sie ihn an einen Sättigungsknoten und multiplizieren Sie ihn mit der Lichtfarbe. Verbinden Sie dies mit dem Color-Port des Unlit-Master-Knotens, und Ihre Vorschau sollte mit einer benutzerdefinierten Schattierung aktualisiert werden!

Da wir nun wissen, wie wir mit dem Knoten Benutzerdefinierte Funktion Lichtdaten abrufen können, können wir unsere Funktion erweitern. Unsere nächste Funktion erhält neben der Richtung und der Farbe auch die Dämpfungswerte des Hauptlichts. Da es sich um eine kompliziertere Funktion handelt, wechseln wir in den Dateimodus und verwenden eine HLSL-Include-Datei. So können Sie kompliziertere Funktionen in einem richtigen Code-Editor schreiben, bevor Sie sie in das Diagramm einfügen. Das bedeutet auch, dass wir eine einheitliche Stelle haben, von der aus wir den Code debuggen können. Öffnen Sie zunächst die Include-Datei `CustomLighting` im Ordner Assets > Include des Projekts. Im Moment werden wir uns nur auf die Funktion "MainLight_half" konzentrieren. Die Funktion sieht wie folgt aus:
Diese Funktion enthält einige neue Eingabe- und Ausgabedaten, also gehen wir zurück zu unserem Knoten Benutzerdefinierte Funktion und fügen sie hinzu. Fügen Sie zwei neue Ausgänge für DistanceAtten (Abstandsdämpfung) und ShadowAtten (Schattendämpfung) hinzu. Fügen Sie dann die neue Eingabe für WorldPos (Weltposition ) hinzu. Da wir nun unsere Ein- und Ausgänge haben, können wir auf die Include-Datei verweisen. Ändern Sie das Dropdown-Menü Typ in Datei. Navigieren Sie in der Quelleingabe zu der Include-Datei und wählen Sie das zu referenzierende Asset aus. Nun müssen wir dem Knoten mitteilen, welche Funktion er verwenden soll. In das Feld Name haben wir "MainLight" eingegeben.

Sie werden feststellen, dass die Include-Datei `_half` am Ende des Funktionsnamens hat, unsere Namensoption aber nicht. Das liegt daran, dass der Shader Graph Compiler das Präzisionsformat an jeden Funktionsnamen anhängt. Da wir unsere eigene Funktion definieren, brauchen wir den Quellcode, um dem Compiler mitzuteilen, welches Präzisionsformat unsere Funktion verwendet. Im Knoten brauchen wir jedoch nur auf den Namen der Hauptfunktion zu verweisen. Sie können ein Duplikat der Funktion erstellen, das "Float"-Werte verwendet, um im Float-Präzisionsmodus zu kompilieren. Mit dem Farbmodus "Präzision" können Sie die für jeden Knoten im Diagramm eingestellte Präzision leicht nachverfolgen, wobei Blau für Fließen und Rot für die Hälfte steht.
Wir werden diese Funktion wahrscheinlich an anderer Stelle wieder verwenden wollen, und der einfachste Weg, diese benutzerdefinierte Funktion wiederverwendbar zu machen, ist, sie in einen Untergraphen zu verpacken. Wählen Sie den Knoten und seine Gruppe aus, und klicken Sie dann mit der rechten Maustaste auf In Untergrafik konvertieren. Wir haben unsere "Get Main Light" genannt. Fügen Sie dem Subgraphen-Ausgangsknoten einfach die erforderlichen Ausgangsanschlüsse hinzu, und schließen Sie den Ausgang des Knotens an den Subgraphen-Ausgang an. Als Nächstes fügen wir einen Weltpositionsknoten hinzu, der mit dem Eingang verbunden wird.

Speichern Sie das Unterdiagramm und kehren Sie zu unserem unbeleuchteten Diagramm zurück. Wir werden zwei neue Multiplikationsknoten zu unserer bestehenden Logik hinzufügen. Multiplizieren Sie zunächst die beiden Dämpfungsausgänge miteinander. Dann multiplizieren Sie diese Leistung mit der Lichtfarbe. Wir können dies mit NdotL von früher multiplizieren, um die Dämpfung in unserer Grundschattierung richtig zu berechnen.

Der Shader, den wir gemacht haben, ist großartig für matte Objekte, aber was, wenn wir etwas Glanz wollen? Wir können unserem Shader unsere eigenen Spiegelungsberechnungen hinzufügen! Für diesen Schritt verwenden wir einen weiteren benutzerdefinierten Funktionsknoten, der in einen Untergraphen eingewickelt ist und Direct Specular heißt. Schauen Sie sich die Include-Datei "CustomLighting" noch einmal an, und Sie werden sehen, dass wir jetzt auf eine andere Funktion aus derselben Datei verweisen:
Diese Funktion führt einige einfache Spiegelungsberechnungen durch, und wenn Sie neugierig sind, können Sie hier mehr darüber lesen. Das Unterdiagramm für diese Funktion enthält auch einige Eingaben auf der Tafel:

Vergewissern Sie sich, dass Ihr neuer Knoten über alle geeigneten Ein- und Ausgangsanschlüsse verfügt, die der Funktion entsprechen. Das Hinzufügen von Eigenschaften zum Blackboard ist ganz einfach: Klicken Sie einfach auf das Symbol Hinzufügen (+) oben rechts und wählen Sie den Datentyp aus. Doppelklicken Sie auf die Pille, um die Eingabe umzubenennen, und ziehen Sie die Pille per Drag & Drop, um sie dem Diagramm hinzuzufügen. Aktualisieren Sie abschließend den Ausgangsanschluss für Ihren Untergraphen und speichern Sie ihn.
Nun, da die Spiegelungsberechnung eingerichtet ist, können wir zum unbeleuchteten Diagramm zurückkehren und es über das Menü Knoten erstellen hinzufügen. Verbinden Sie den Ausgang Attenuation mit dem Eingang Color des Direct Specular Sub Graph. Verbinden Sie dann den Ausgang Richtung der Funktion Hauptlicht abrufen mit dem Eingang Richtung des Spiegelungsuntergraphen. Fügen Sie das Ergebnis von NdotL*Attenuation zur Ausgabe des Direct Specular Sub Graph hinzu und schließen Sie es an die Farbausgabe an.

Jetzt haben wir ein bisschen Glanz!
Das Hauptlicht des LWRP bezieht sich auf das hellste gerichtete Licht in Bezug auf das Objekt, das in der Regel die Sonne ist. Um die Leistung auf weniger leistungsfähiger Hardware zu verbessern, berechnet das LWRP das Hauptlicht und alle zusätzlichen Lichter separat. Um sicherzustellen, dass unser Shader alle Lichter in der Szene korrekt berechnet und nicht nur das hellste gerichtete Licht, müssen Sie eine Schleife in Ihrer Funktion erstellen. Um die zusätzlichen Beleuchtungsdaten zu erhalten, haben wir einen neuen Untergraphen verwendet, um einen neuen benutzerdefinierten Funktionsknoten zu umhüllen. Schauen Sie sich die Funktion `AdditionalLight_float` in der Include-Datei `CustomLighting` an:
Verwenden Sie wie zuvor die Funktion "AdditionalLights" in der Dateireferenz des Knotens "Custom Function", und stellen Sie sicher, dass Sie alle richtigen Ein- und Ausgänge erstellt haben. Vergewissern Sie sich, dass Specular Color und Specular Smoothness auf dem Blackboard des Sub-Graphen, in dem der Knoten verpackt ist, angezeigt werden. Verwenden Sie die Knoten Position, Normalenvektor und Blickrichtung, um die Weltposition, die Weltnormale und die Blickrichtung im Weltraum in den Untergraphen einzufügen.
Nachdem Sie die Funktion eingerichtet haben, verwenden Sie sie! Nehmen Sie zunächst Ihr Hauptdiagramm "Unbeleuchtet" aus dem vorherigen Schritt und klappen Sie es zu einem Unterdiagramm zusammen. Wählen Sie die Knoten aus, und klicken Sie mit der rechten Maustaste auf In Untergraphen umwandeln. Entfernen Sie den letzten Add-Knoten, und schließen Sie die Ausgänge an die Ausgangsanschlüsse des Subgraphen an. Wir empfehlen Ihnen, auch Eingabeeigenschaften für Glanz und Glätte zu erstellen.

Jetzt können Sie Ihre Hauptlichtberechnungen und Ihre zusätzlichen Lichtberechnungen miteinander kombinieren. Erstellen Sie im Hauptdiagramm "Unbeleuchtet" einen neuen Knoten für die Berechnungen "Zusätzliches Licht", der neben die Berechnungen "Hauptlicht" tritt. Addieren Sie die diffusen und spiegelnden Ausgänge des Hauptlichts und der Zusatzlichter zusammen. Ziemlich einfach!


Jetzt wissen Sie, wie Sie die Daten aller Leuchten in einer Szene für ein LWRP-Projekt erhalten, aber was können Sie damit tun? Eine der häufigsten Anwendungen für benutzerdefinierte Beleuchtung in Shadern ist ein klassischer Toon-Shader!
Mit all den Lichtdaten ist es ziemlich einfach, einen Toon-Shader zu erstellen. Nehmen Sie zunächst alle Lichtberechnungen, die Sie bisher durchgeführt haben, und verpacken Sie sie noch einmal in einen Untergraphen. Dadurch wird die Lesbarkeit im endgültigen Shader verbessert. Vergessen Sie nicht, den abschließenden Add-Knoten zu entfernen und Diffuse und Specular in getrennte Ausgabeanschlüsse am Subgraphen-Ausgangsknoten einzugeben.
Es gibt viele Methoden zur Erstellung von Toon-Schattierungen, aber in diesem Beispiel verwenden wir die Lichtintensität, um Farben aus einer Rampentextur zu ermitteln. Diese Technik wird gewöhnlich als Rampenbeleuchtung bezeichnet. Wir haben einige Beispiele für die Art von Textur-Asset, die für Rampenbeleuchtung benötigt wird, in das Beispielprojekt aufgenommen. Sie können auch einen Farbverlauf abtasten, um dynamische Rampen in der Rampenbeleuchtung zu verwenden. Der erste Schritt besteht darin, die Intensität von Diffus und Specular von RGB-Werten in HSV-Werte umzuwandeln. Auf diese Weise können wir die Intensität der Lichtfarbe (die HSV-Werte) verwenden, um die Helligkeit im Shader zu bestimmen, und wir können die Textur an verschiedenen Stellen entlang der horizontalen Achse des Assets abtasten. Verwenden Sie einen statischen Wert für den Y-Kanal der UV, um von oben nach unten festzulegen, welcher Teil des Bildes abgetastet werden soll. Sie können diesen statischen Wert als Index verwenden, um mehrere Beleuchtungsrampen für das Projekt in einem einzigen Textur-Asset zu referenzieren.

Sobald Sie die UV-Werte festgelegt haben, verwenden Sie einen Sample Texture 2D LOD-Knoten, um die Rampentextur zu sampeln. Die Sample-LOD ist wichtig; wenn wir einen regulären 2D-Sample-Textur-Knoten verwenden, wird die Rampe automatisch in einer Szene gemischt, und weiter entfernte Objekte haben ein anderes Beleuchtungsverhalten. Die Verwendung eines 2D-LOD-Knotens für Mustertexturen ermöglicht die manuelle Bestimmung des Mip-Levels. Da die Rampentextur nur 2 Pixel hoch ist, haben wir außerdem einen eigenen Sampler-Status für die Texturen erstellt. Um sicherzustellen, dass die Textur korrekt abgetastet wird, setzen wir den Filter auf Punkt und den Wrap auf Klammer. Wir haben dies als eine Eigenschaft auf der Tafel dargestellt, damit Sie die Einstellungen ändern können, wenn sich das Textur-Asset ändert.

Schließlich multiplizieren wir das Rampenmuster aus den Diffusberechnungen mit der Farbeigenschaft Diffus, so dass wir die Farben des Objekts ändern können. Fügen Sie das Rampenmuster aus den Spiegelungsberechnungen zur Diffus-Ausgabe hinzu, und schließen Sie die endgültige Farbe an den Masterknoten an.


Diese einfache benutzerdefinierte Beleuchtungseinrichtung kann erweitert und auf eine Vielzahl von Anwendungsfällen in allen Arten von Szenen angewendet werden. In unserem Beispielprojekt haben wir eine vollständige Szene mit Shadern konfiguriert, die unser benutzerdefiniertes Beleuchtungssetup verwenden. Es enthält auch Scheitelpunktanimation, eine einfache Annäherung an die Streuung unter der Oberfläche sowie Brechungen und Färbungen, die die Tiefe nutzen. Laden Sie das Projekt herunter, und sehen Sie sich unsere Beispiel-Assets an, um fortgeschrittenere Methoden zu erkunden!
Wenn ihr über Shader Graph und die Shader, die ihr damit erstellen könnt, diskutieren wollt, kommt in unser brandneues Forum! Sie können auch Community-Mitglieder und (manchmal) ein paar Entwickler in der Community Discord finden!
Vergessen Sie nicht, nach Aufzeichnungen unserer SIGGRAPH 2019 Sessions Ausschau zu halten, in denen wir noch detaillierter auf die Verwendung von Shader Graph für benutzerdefinierte Beleuchtung eingehen!
