Erkundung des Sandshaders des Journey-Spiels

Unter den vielen Indie-Spielen, die in den letzten 10 Jahren veröffentlicht wurden, ist eines meiner Lieblingsspiele definitiv Journey . Dank seiner atemberaubenden Ästhetik und seines wunderschönen Soundtracks ist Journey in nahezu allen Aspekten der Entwicklung zu einem hervorragenden Beispiel geworden.

Ich bin ein Spieleentwickler und technischer KĂŒnstler, daher war ich von der Art und Weise, wie der Sand gerendert wurde, am meisten fasziniert. Es ist nicht nur schön, sondern steht auch in direktem Zusammenhang mit dem grundlegenden Gameplay und dem gesamten Gameplay. Journey ist buchstĂ€blich aus Sand gebaut, und ohne solch einen erstaunlichen Effekt könnte das Spiel selbst einfach nicht existieren.


In diesem Artikel, der in zwei BeitrĂ€ge unterteilt ist, möchte ich dem Journey- Erbe meine Anerkennung zollen, indem ich Ihnen beibringe, wie Sie mit Shadern genau dasselbe Sand-Rendering erstellen. UnabhĂ€ngig davon, ob SanddĂŒnen in Ihrem Spiel benötigt werden, können Sie in dieser Reihe von Tutorials lernen, wie Sie eine bestimmte Ästhetik in Ihrem eigenen Spiel wiederherstellen. Wenn Sie den in Journey verwendeten schönen Sand-Shader neu erstellen möchten, mĂŒssen Sie zunĂ€chst verstehen, wie er erstellt wurde. Und obwohl es Ă€ußerst komplex aussieht, besteht es tatsĂ€chlich aus mehreren relativ einfachen Effekten. Dieser Ansatz, Shader zu schreiben, ist notwendig, um ein erfolgreicher technischer KĂŒnstler zu werden. Daher hoffe ich, dass Sie diese Reise mit mir machen, auf der wir nicht nur die Erstellung von Shadern erforschen, sondern auch lernen, wie man Ästhetik und Gameplay kombiniert.

Sandanalyse in Journey


Dieser Artikel basiert, wie viele andere Versuche, das Sand-Rendering von Journey nachzubilden, auf einem Bericht der GDC, den der leitende Ingenieur des Unternehmens, John Edwards, mit dem Titel " Sand-Rendering in Journey " verfasst hat. In diesem Vortrag spricht John Edwards ĂŒber alle Effektebenen, die Journey zu den SanddĂŒnen hinzugefĂŒgt wurden, um den richtigen Look zu erzielen.


Der Bericht ist sehr nĂŒtzlich, aber im Kontext dieses Tutorials sind viele der von John Edwards getroffenen EinschrĂ€nkungen und Entscheidungen nicht wichtig. Wir werden versuchen, die Sand-Shader, die an den Journey- Shader erinnern, hauptsĂ€chlich durch visuelle Referenzen nachzubilden.

Beginnen wir mit einem einfachen 3D-Netz einer perfekt glatten DĂŒne. Die GlaubwĂŒrdigkeit der Sandverarbeitung hĂ€ngt von zwei Aspekten ab: Beleuchtung und Körnung. Eine interessante Möglichkeit, das Licht von Sand zu reflektieren, bietet ein modifiziertes Beleuchtungsmodell . Im Kontext der Shader-Codierung bestimmt das Beleuchtungsmodell Schatten und Lichter basierend auf den Eigenschaften des Modells und den Beleuchtungsbedingungen der Szene.

All dies reicht jedoch nicht aus, um die Illusion eines Realismus zu erzeugen. Das Problem ist, dass Sand einfach nicht mit flachen OberflĂ€chen modelliert werden kann. Sandkorn sollte berĂŒcksichtigt werden. Aus diesem Grund gibt es zwei separate Effekte, die direkt mit der Normalen auf der OberflĂ€che zusammenwirken und dazu verwendet werden können, kleine Sandpartikel auf der OberflĂ€che der DĂŒne zu simulieren.

Das folgende Diagramm zeigt alle Effekte, die wir in diesem Tutorial lernen werden. Aus technischer Sicht werden normale Berechnungen durchgefĂŒhrt, bevor die Beleuchtung verarbeitet wird. Zur Erleichterung der Untersuchung werden die Wirkungen in einer anderen Reihenfolge beschrieben.


Diffuse Farbe

Der einfachste Sand Shader-Effekt ist seine diffuse Farbe , die grob die matte Komponente des Gesamterscheinungsbildes beschreibt. Die diffuse Farbe wird basierend auf der tatsĂ€chlichen Farbe des Objekts und den Lichtbedingungen berechnet. Eine weiße Kugel ist nicht ĂŒberall perfekt weiß, da die diffuse Farbe vom einfallenden Licht abhĂ€ngt. Diffuse Farben werden mithilfe eines mathematischen Modells berechnet, das die Reflexion von Licht von einer OberflĂ€che approximiert. Dank eines Berichts von John Edwards mit der GDC kennen wir genau die verwendete Gleichung, die er diffuses Kontrastreflexionsvermögen nennt; es basiert auf dem bekannten Lambert- Modell der Reflexionen .



Vor und nach der Anwendung der Gleichung

Sand normal

Die ursprĂŒngliche Geometrie ist völlig glatt. Um dies zu kompensieren, wird die OberflĂ€chennormale des Modells mit einer Technik namens Bump Mapping geĂ€ndert. Sie können eine Textur verwenden, um komplexere Geometrien zu simulieren.



Kantenbeleuchtung

Jedes Reise- Level verwendet eine begrenzte Farbpalette. Aus diesem Grund ist es ziemlich schwierig zu verstehen, wo eine DĂŒne endet und eine andere beginnt. Um die Lesbarkeit zu verbessern, wird die Technik der kleinen Hervorhebung des Sichtbaren nur entlang des DĂŒnenrandes angewendet. Es wird als Randbeleuchtung bezeichnet , und es gibt viele Möglichkeiten, dies umzusetzen. FĂŒr dieses Tutorial habe ich eine auf Fresnel- Reflexionen basierende Methode gewĂ€hlt, die Reflexionen auf polierten OberflĂ€chen unter sogenannten Einfallswinkeln modelliert.



Spiegelbild des Ozeans

Einer der unterhaltsamsten Aspekte im Gameplay von Journey ist das „Surfen“ in den SanddĂŒnen. Dies ist wahrscheinlich der Grund, warum die Spielefirma wollte, dass sich der Sand eher wie eine FlĂŒssigkeit als wie ein Feststoff anfĂŒhlt. HierfĂŒr wurde eine starke Reflexion verwendet, die hĂ€ufig in Wasserschattierungen zu finden ist. John Edwards nennt diesen Effekt Ocean Specular , und im Tutorial implementieren wir ihn mithilfe der Blinn-Fong-Reflexion .



Blendung Reflexion

Indem Sie dem Sand Shader eine ozeanische Spiegelkomponente hinzufĂŒgen, wirkt er flĂŒssiger. Dennoch lĂ€sst sich einer der wichtigsten visuellen Aspekte des Sandes nicht vermitteln: zufĂ€llig auftretende Reflexionen. In echten DĂŒnen tritt dieser Effekt auf, weil jedes Sandkorn Licht in seine Richtung reflektiert und sehr oft einer dieser reflektierten Strahlen in unser Auge gelangt. Eine solche Glitzerreflexion (Reflexion von Reflexionen) tritt auch an Orten auf, an denen direktes Sonnenlicht nicht einfĂ€llt; es ergĂ€nzt das ozeanische Spiegelbild und stĂ€rkt das GefĂŒhl der GlaubwĂŒrdigkeit.



Sandwellen

Durch Ändern der Normalen konnten wir den Effekt kleiner Sandkörner auf der DĂŒnenoberflĂ€che simulieren. In den DĂŒnen der realen Welt treten hĂ€ufig vom Wind verursachte Wellen auf. Ihre Form variiert je nach Neigung und Position der DĂŒnen im VerhĂ€ltnis zur Windrichtung. Möglicherweise können solche Muster durch eine Höckertextur erzeugt werden, aber in diesem Fall ist es unmöglich, die Form der DĂŒnen in Echtzeit zu Ă€ndern. Die von John Edwards vorgeschlagene Lösung Ă€hnelt einer Technik namens Triplanar Shading : Es werden vier verschiedene Texturen verwendet, die je nach Position und Neigung der DĂŒnen gemischt werden.



Reise Sand Shader Anatomie


Unity bietet viele Shader-Vorlagen, mit denen Sie loslegen können. Da wir an Materialien interessiert sind, die Licht empfangen und Schatten werfen können, mĂŒssen wir mit dem Surface Shader (Surface Shader) beginnen.

Alle Surface Shader werden in zwei Schritten ausgefĂŒhrt. ZunĂ€chst wird eine OberflĂ€chenfunktion aufgerufen, die die Eigenschaften der zu rendernden OberflĂ€che erfasst, z. B. Albedo , Rauheit , Metalleigenschaften , Transparenz und Normalenrichtung . Dann werden alle diese Eigenschaften auf die Beleuchtungsfunktion ĂŒbertragen , die den Einfluss externer Lichtquellen berĂŒcksichtigt und die Verschattung und Beleuchtung berechnet.

OberflÀchenfunktion


Beginnen wir mit dem Kern unserer OberflĂ€chenfunktion, der im folgenden surf wird. Die einzigen Eigenschaften, die wir einstellen mĂŒssen, sind die Farbe des Sandes und die Normale zur OberflĂ€che . Die Normale eines 3D-Modells ist ein Vektor, der die Position der OberflĂ€che angibt. Normale Vektoren werden von der Beleuchtungsfunktion verwendet, um zu berechnen, wie Licht reflektiert wird. Sie werden normalerweise beim Import des Netzes berechnet. Sie können jedoch geĂ€ndert werden, um eine komplexere Geometrie zu simulieren. Hier verzerren die normalen Sand- und Sandwelleneffekte die Sandnorm, um ihre Rauheit zu simulieren.

 void surf (Input IN, inout SurfaceOutput o) { o.Albedo = _SandColor; o.Alpha = 1; float3 N = float3(0, 0, 1); N = RipplesNormal(N); N = SandNormal (N); o.Normal = N; } 

Wenn Sie Normalen nach o.Normal mĂŒssen sie im Tangentialraum ausgedrĂŒckt werden . Dies bedeutet, dass der Vektor relativ zur OberflĂ€che des 3D-Modells ausgewĂ€hlt wird. float3(0, 0, 1) bedeutet also, dass das normale 3D-Modell tatsĂ€chlich nicht geĂ€ndert wird.

Beide Funktionen, RipplesNormal und SandNormal empfangen den normalen Vektor und modifizieren ihn. Wie das geht, werden wir spÀter sehen.

Beleuchtung funktion


In der Beleuchtungsfunktion werden alle anderen Effekte implementiert. Der folgende Code zeigt, wie jede einzelne Komponente in separaten Funktionen berechnet wird (diffuse Farbe, Randbeleuchtung, Ozeanspiegelung und Glitzerreflexion). Dann werden sie alle kombiniert.

 #pragma surface surf Journey fullforwardshadows float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi) { float3 diffuseColor = DiffuseColor (); float3 rimColor = RimLighting (); float3 oceanColor = OceanSpecular (); float3 glitterColor = GlitterSpecular (); float3 specularColor = saturate(max(rimColor, oceanColor)); float3 color = diffuseColor + specularColor + glitterColor; return float4(color * s.Albedo, 1); } 

Die Methode zum Kombinieren von Komponenten ist ziemlich willkĂŒrlich und ermöglicht es uns, sie zu Ă€ndern, um die kĂŒnstlerischen Möglichkeiten zu untersuchen.

Typischerweise stapeln sich Spiegelreflexionen ĂŒber diffuse Farben. Da wir hier nicht eine, sondern drei Spiegelreflexionen haben ( Randlicht , Ozeanspiegel und Glitzerspiegel ), mĂŒssen wir vorsichtiger sein, damit der Sand nicht zu flackert. Da Rim Light und Ocean Specular Teil desselben Effekts sind, können wir nur den Maximalwert auswĂ€hlen. Glitzerspiegel werden separat hinzugefĂŒgt, da diese Komponente flackernden Sand erzeugt.

Teil 2. Diffuse Farbe


Im zweiten Teil des Beitrags konzentrieren wir uns auf das im Spiel verwendete Beleuchtungsmodell und darauf. Wie erstelle ich es in Unity neu?

Im vorigen Teil haben wir den Grundstein dafĂŒr gelegt, was sich allmĂ€hlich in unsere Version des Sandshaders Journey verwandeln wird. Wie bereits erwĂ€hnt, wird die Beleuchtungsfunktion in OberflĂ€chen-Shadern verwendet , um den Effekt der Beleuchtung zu berechnen, sodass Schatten und Lichter auf der OberflĂ€che erscheinen. Wir haben herausgefunden, dass Journey mehrere Effekte hat, die in diese Kategorie fallen. Wir werden mit dem grundlegendsten (und einfachsten) Effekt beginnen, der im Kern dieses Shaders zu finden ist: seiner diffusen Beleuchtung (diffuse / diffuse Beleuchtung).


Im Moment lassen wir alle anderen Effekte und Komponenten aus und konzentrieren uns auf das Beleuchten des Sandes .

Die Beleuchtungsfunktion, die wir im vorherigen Teil des Beitrags mit dem Namen LightingJourney , delegiert die Berechnung der diffusen Farbe von Sand einfach an eine Funktion mit dem Namen DiffuseColor .

 float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi) { // Lighting properties float3 L = gi.light.dir; float3 N = s.Normal; // Lighting calculation float3 diffuseColor = DiffuseColor(N, L); // Final color return float4(diffuseColor, 1); } 

Da jeder Effekt in sich geschlossen und in seiner eigenen Funktion gespeichert ist, ist unser Code modularer und ĂŒbersichtlicher.

Lambert-Reflexion


Bevor Sie diffuses Licht „wie in Journey“ erstellen, sollten Sie sich ansehen, wie die „grundlegende“ Funktion fĂŒr diffuses Licht aussieht. Die einfachste Schattierungstechnik fĂŒr matte Materialien heißt Lambertsches Reflexionsvermögen . Dieses Modell entspricht in etwa dem Aussehen der meisten nicht glĂ€nzenden und nicht metallischen OberflĂ€chen. Es ist nach dem Schweizer EnzyklopĂ€disten Johann Heinrich Lambert benannt , der sein Konzept 1760 vorschlug.

Lamberts Reflexionskonzept basiert auf einer einfachen Idee: Die Helligkeit einer OberflĂ€che hĂ€ngt von der Menge des auf sie einfallenden Lichts ab . Geometrisch kann dies in der folgenden Abbildung gezeigt werden, in der die Kugel von einer entfernten Lichtquelle beleuchtet wird. Obwohl die roten und grĂŒnen Bereiche der Kugel gleich stark beleuchtet werden, unterscheiden sich ihre OberflĂ€chen erheblich. Wenn das Licht im roten Bereich ĂŒber eine grĂ¶ĂŸere FlĂ€che verteilt ist, bedeutet dies, dass jede Einheit des roten Quadrats weniger Licht als grĂŒn empfĂ€ngt.


Theoretisch hĂ€ngt die Lambert-Reflexion vom relativen Winkel zwischen der OberflĂ€che und dem einfallenden Licht ab . Aus mathematischer Sicht ist dies eine Funktion von der Normalen zur OberflĂ€che und der Beleuchtungsrichtung . Diese GrĂ¶ĂŸen werden unter Verwendung von zwei EinheitslĂ€ngenvektoren ( Einheitsvektoren genannt ) ausgedrĂŒckt. N und L . Einzelne Vektoren sind eine Standardmethode, um Richtungen im Kontext der Shader-Codierung anzugeben.

Der Wert von N und L
Normal zur OberflÀche N Ist ein Einheitsvektor, der von der OberflÀche selbst weg gerichtet ist.

Analog können wir davon ausgehen, dass die Richtung der Beleuchtung L zeigt von der Lichtquelle und folgt in die Richtung, in die sich das Licht bewegt. Dies ist jedoch nicht der Fall: Die Beleuchtungsrichtung ist ein einzelner Vektor, der in die Richtung zeigt, aus der das Licht kam.

Dies kann verwirrend sein, insbesondere wenn Sie mit dem Erstellen von Shadern noch nicht vertraut sind. Dank dieser Notation werden Gleichungen jedoch einfacher.

Lambert-Spiegelbild in der Einheit
Vor Unity 5 Standard Shader war die Lambert-Reflexion das Standardmodell fĂŒr das Abschatten beleuchteter OberflĂ€chen.

Sie können weiterhin im Materialinspektor darauf zugreifen: Im Legacy-Shader heißt es Diffuse .

Wenn Sie Ihren eigenen OberflÀchen-Shader schreiben, steht die Lambert-Reflexion als Beleuchtungsfunktion mit dem Namen Lambert :

 #pragma surface surf Lambert fullforwardshadows 

Ihre Implementierung finden Sie in der LightingLambert Funktion, die in der Datei CGIncludes\Lighting.cginc .

Lambert Reflexion und Klima
Lambert-Reflexion ist ein relativ altes Modell, bietet jedoch ein VerstĂ€ndnis fĂŒr komplexe Konzepte wie OberflĂ€chenschattierung. Es kann auch verwendet werden, um viele andere PhĂ€nomene zu erklĂ€ren. Zum Beispiel erklĂ€rt dasselbe Diagramm, warum es an den Polen des Planeten kĂ€lter ist als am Äquator.

Bei genauerer Betrachtung können wir feststellen, dass die OberflÀche die maximale BeleuchtungsstÀrke erhÀlt, wenn ihre Normale parallel zur Beleuchtungsrichtung verlÀuft. Und umgekehrt: Es gibt kein Licht, wenn zwei Einheitsvektoren senkrecht zueinander stehen.


Offensichtlich ist der Winkel zwischen N und L kritisch fĂŒr die Reflexion nach Lambert. DarĂŒber hinaus ist die Helligkeit maximal und gleich 100% wenn der Winkel ist 0 und minimal ( 0% ), wenn der Winkel dazu neigt 90 circ . Wenn Sie mit der Vektoralgebra vertraut sind, können Sie verstehen, dass eine GrĂ¶ĂŸe Lamberts Reflexion darstellt I ist gleich N cdotL Wo ist der Betreiber?  cdot Skalarprodukt genannt .

(1)

$$ display $$ \ begin {equation *} I = N \ cdot L \ end {equation *} $$ display $$


Das Skalarprodukt ist ein Maß fĂŒr die "Übereinstimmung" zweier Vektoren relativ zueinander und variiert im Intervall von +1 (fĂŒr zwei identische Vektoren) bis −1 (fĂŒr zwei entgegengesetzte Vektoren). Ein Skalarprodukt ist die Grundlage fĂŒr die Schattierung, die ich im Tutorial Physikalisch basiertes Rendern und Beleuchtungsmodelle ausfĂŒhrlich untersucht habe.

Implementierung


Und zu N und zu L Sie können leicht auf die Surface Shader-Beleuchtungsfunktionen ĂŒber s.Normal und gi.light.dirin . Der Einfachheit halber werden wir sie im Shader-Code in N und L umbenennen.

 float3 DiffuseColor(float3 N, float3 L) { float NdotL = saturate( dot(N, L) ); return NdotL; } 

saturate begrenzt den Wert von 0 vorher 1 . Da sich das Skalarprodukt jedoch im Bereich von −1 vorher +1 Wir mĂŒssen nur mit den negativen Werten arbeiten. Aus diesem Grund wird die Lambert-Reflexion hĂ€ufig wie folgt implementiert:

 float NdotL = max(0, dot(N, L) ); 

Kontrastreflexion des Umgebungslichts


Obwohl Lamberts Reflexion die meisten Materialien gut abschattet, ist sie weder physikalisch genau noch fotorealistisch. In Ă€lteren Spielen wurden Lambert-Shader hĂ€ufig verwendet. Spiele, die diese Technik verwenden, scheinen oft alt zu sein, weil sie versehentlich die Ästhetik alter Spiele reproduzieren können. Wenn Sie dies nicht anstreben, sollten Sie Lambert-Reflexionen vermeiden und modernere Technologien verwenden.

Ein solches Modell ist das Oren-Nayyar-Reflexionsmodell , das ursprĂŒnglich in dem 1994 von Michael Oren und Sri C. Nayyar veröffentlichten Artikel Generalization of Lambert's Reflectance Model beschrieben wurde. Das Oren-Nayyar-Modell ist eine Verallgemeinerung der Lambert-Reflexion und wurde speziell fĂŒr raue OberflĂ€chen entwickelt. AnfĂ€nglich wollten die Entwickler von Journey die Oren-Nayyar-Reflexion als Grundlage fĂŒr ihren Sandshader verwenden. Diese Idee wurde jedoch aufgrund der hohen Rechenkosten aufgegeben.

In seinem Bericht von 2013 erklĂ€rt der technische KĂŒnstler John Edwards, dass das fĂŒr den Sand Journey erstellte Reflexionsmodell auf einer Reihe von Versuchen und IrrtĂŒmern basiert. Die Entwickler wollten nicht die fotorealistische Wiedergabe der WĂŒste nachbilden, sondern einer konkreten, sofort erkennbaren Ästhetik Leben einhauchen.

Das resultierende Schattierungsmodell entspricht seiner Meinung nach dieser Gleichung:

(2)

$$ display $$ \ begin {equation *} I = 4 * \ left (\ left (N \ odot \ left [1, 0.3, 1 \ right] \ right) \ cdot L \ right) \ end {equation *} $$ display $$


wo  odot - Elementweises Produkt zweier Vektoren.

 float3 DiffuseColor(float3 N, float3 L) { Ny *= 0.3; float NdotL = saturate(4 * dot(N, L)); return NdotL; } 

Reflexionsmodell (2) Da John Edwards diffusen Kontrast nennt, werden wir diesen Namen im gesamten Lernprogramm verwenden.

Die Animation unten zeigt den Unterschied zwischen Lambert-Schattierung (links) und diffusem Kontrast gegenĂŒber Journey (rechts).



Was ist die Bedeutung von 4 und 0,3?
Obwohl der diffuse Kontrast nicht auf physikalische Genauigkeit ausgelegt ist, können wir dennoch versuchen zu verstehen, was er bewirkt.

Im Kern wird immer noch Lambert-Reflexion verwendet. Der erste offensichtliche Unterschied ist, dass das Gesamtergebnis mit multipliziert wird 4 . Dies bedeutet, dass alle Pixel, die normalerweise empfangen wurden 25 % Die Beleuchtung leuchtet jetzt wie beim Empfang 100 % beleuchtung. Indem man alles mit multipliziert 4 Eine schwache Schattierung nach Lambert wird viel stĂ€rker und der Übergangsbereich zwischen Dunkelheit und Licht ist kleiner. In diesem Fall wird der Schatten schĂ€rfer.

Auswirkung der Multiplikation der y Komponente auf die Normalenrichtung 0,3 erklĂ€ren ist viel schwieriger. Wenn sich die Komponenten des Vektors Ă€ndern, Ă€ndert sich die allgemeine Richtung, in die er zeigt. Reduzieren Sie den Wert der y Komponente auf alles 30 % Die Reflexion des diffusen Kontrasts bewirkt, dass die Schatten vom ursprĂŒnglichen Wert vertikaler werden.

Hinweis: Ein Skalarprodukt misst den Winkel zwischen zwei Vektoren nur dann direkt, wenn beide eine LĂ€nge haben 1 . Die vorgenommene Änderung verringert die normale LĂ€nge N Das ist kein Einheitsvektor mehr.

Von Grautönen zu Farbe


Alle oben gezeigten Animationen haben Graustufen, da sie die Werte ihres Reflexionsmodells anzeigen, die im Intervall von variieren 0 vorher 1 ". Wir können einfach Farben hinzufĂŒgen, indem NdotL als Interpolationskoeffizienten zwischen zwei Farben verwenden: eine fĂŒr vollstĂ€ndig schattierten und eine fĂŒr vollstĂ€ndig beleuchteten Sand.

 float3 _TerrainColor; float3 _ShadowColor; float3 DiffuseColor(float3 N, float3 L) { Ny *= 0.3; float NdotL = saturate(4 * dot(N, L)); float3 color = lerp(_ShadowColor, _TerrainColor, NdotL); return color; } 

Teil 3. Normaler Sand


Im dritten Teil werden wir uns auf die Erstellung normaler Karten konzentrieren, die glatte 3D-Modelle in SanddĂŒnen verwandeln.

Im vorherigen Teil des Tutorials haben wir die diffuse Beleuchtung von Journey Sand implementiert. Wenn Sie nur diesen Effekt verwenden, wirken die WĂŒstendĂŒnen eher flach und langweilig.


Einer der faszinierendsten Effekte von Journey ist die Körnigkeit des Sandes. Wenn wir uns einen Screenshot ansehen, sehen wir, dass die DĂŒnen nicht glatt und homogen sind, sondern aus Millionen mikroskopisch kleinen Sandkörnern bestehen.


Dieser Effekt kann mit einer Technik namens Bump Mapping erzielt werden, bei der Licht von einer ebenen OberflÀche reflektiert wird, als wÀre es komplexer. Sehen Sie, wie dieser Effekt das Erscheinungsbild des Renderings Àndert:



Kleine Unterschiede zeigen sich mit zunehmender:



Wir beschÀftigen uns mit normalen Karten


Sand besteht aus unzĂ€hligen Sandkörnern, von denen jedes seine eigene Form und Zusammensetzung hat (siehe unten). Jedes einzelne Partikel reflektiert das Licht in eine möglicherweise zufĂ€llige Richtung. Ein Weg, diesen Effekt zu realisieren, besteht darin, ein 3D-Modell zu erstellen, das all diese mikroskopischen Sandkörner enthĂ€lt. Aufgrund der unglaublichen Anzahl erforderlicher Polygone ist dieser Ansatz jedoch nicht durchfĂŒhrbar.

Es gibt jedoch eine andere Lösung, mit der hĂ€ufig eine komplexere Geometrie im Vergleich zu einem echten 3D-Modell simuliert wird. Jeder Scheitelpunkt oder jede FlĂ€che des 3D-Modells ist einem Parameter zugeordnet, der als Normalenrichtung bezeichnet wird . Dies ist ein LĂ€ngeneinheitsvektor, der zur Berechnung der Lichtreflexion auf der OberflĂ€che eines 3D-Modells verwendet wird. Das heißt, um Sand zu simulieren, mĂŒssen Sie diese scheinbar zufĂ€llige Verteilung der Sandkörner simulieren und damit, wie sie die OberflĂ€chennormalen beeinflussen.


Dies kann auf unzĂ€hlige Arten geschehen. Am einfachsten ist es, eine Textur zu erstellen, die die Richtung der ursprĂŒnglichen Normalen des DĂŒnenmodells Ă€ndert.

Normal zur OberflÀche N im allgemeinen Fall wird es anhand der Geometrie des 3D-Modells berechnet. Sie können es jedoch mit der normalen Karte Àndern. Normale Karten sind Texturen, mit denen Sie komplexere Geometrien simulieren können, indem Sie die lokale Ausrichtung der Normalen zur OberflÀche Àndern. Diese Technik wird oft als Bump-Mapping bezeichnet .

Das Ändern der Normalen ist eine relativ einfache Aufgabe, die in der surf Funktion des Surface Shader ausgefĂŒhrt werden kann . Diese Funktion SurfaceOutput zwei Parameter, von denen einer eine struct namens SurfaceOutput . Es enthĂ€lt alle Eigenschaften, die zum Rendern eines Teils eines 3D-Modells erforderlich sind, von der Farbe ( o.Albedo ) bis zur Transparenz ( o.Alpha ). Ein weiterer Parameter, den es enthĂ€lt, ist die Normalenrichtung ( o.Normal ), die umgeschrieben werden kann, um die Art und Weise zu Ă€ndern, in der Licht auf das Modell reflektiert wird.

GemĂ€ĂŸ der Unity-Dokumentation zu Surface Shadern mĂŒssen alle Normalen, die in die o.Normal Struktur geschrieben werden, im Tangentialraum ausgedrĂŒckt werden :

 struct SurfaceOutput { fixed3 Albedo; // diffuse color fixed3 Normal; // tangent space normal, if written fixed3 Emission; half Specular; // specular power in 0..1 range fixed Gloss; // specular intensity fixed Alpha; // alpha for transparencies }; 

Somit können wir berichten, dass Einheitsvektoren im Koordinatensystem relativ zur Netznormalen ausgedrĂŒckt werden mĂŒssen. Wenn Sie beispielsweise in o.Normal schreiben, o.Normal Werte von float3(0, 0, 1) normal unverĂ€ndert.

 void surf (Input IN, inout SurfaceOutput o) { o.Albedo = _SandColor; o.Alpha = 1; o.Normal = float3(0, 0, 1); } 

Dies liegt daran, dass der Vektor float3(0, 0, 1) tatsĂ€chlich ein normaler Vektor ist, der relativ zur Geometrie des 3D-Modells ausgedrĂŒckt wird.

o.Normal die Normalen zur OberflĂ€che im OberflĂ€chen-Shader zu Ă€ndern, mĂŒssen wir in o.Normal nur einen neuen Vektor in die OberflĂ€chenfunktion o.Normal :

 void surf (Input IN, inout SurfaceOutput o) { o.Albedo = _SandColor; o.Alpha = 1; o.Normal = ... // change the normal here } 

Im Rest des Beitrags werden wir die anfÀngliche AnnÀherung erstellen, die wir im sechsten Teil des Tutorials verkomplizieren werden.

Sand normal


Am problematischsten ist es zu verstehen, wie sich die Sandkörner normal zur OberflĂ€che verĂ€ndern. Obwohl jedes Sandkorn einzeln Licht in jede Richtung streuen kann, passiert im Großen und Ganzen etwas anderes. Jeder physikalisch genaue Ansatz sollte die Verteilung normaler Vektoren auf der SandoberflĂ€che untersuchen und mathematisch modellieren. Solche Modelle gibt es tatsĂ€chlich, aber die in unserem Tutorial vorgestellte Lösung ist viel einfacher und gleichzeitig sehr effektiv.

An jedem Punkt im Modell wird ein zufĂ€lliger Einheitsvektor aus der Textur abgetastet. Dann neigt sich die Normale zur OberflĂ€che um einen bestimmten Betrag in Richtung dieses Vektors. Mit der richtigen Erzeugung einer zufĂ€lligen Textur und der Auswahl einer geeigneten Menge an Mischung können wir die Normale so zur OberflĂ€che verschieben, dass ein GefĂŒhl der Körnigkeit entsteht, ohne die GesamtkrĂŒmmung der DĂŒnen zu verlieren.

ZufĂ€llige Werte können mit einer Textur aus zufĂ€lligen Farben abgetastet werden. Die Komponenten R, G und B jedes Pixels werden als Komponenten X, Y und Z des Normalenvektors verwendet. Farbkomponenten sind im Sortiment  left[0,1 right] Sie mĂŒssen also in ein Intervall konvertiert werden  left[−1,+1 right] . Dann wird der resultierende Vektor normalisiert, so dass seine LĂ€nge gleich ist 1 .


Erstelle zufÀllige Texturen
Es gibt viele Möglichkeiten, zufĂ€llige Texturen zu erzeugen. Um den gewĂŒnschten Effekt zu erzielen, ist die allgemeine Verteilung der Zufallsvektoren, die aus der Textur abgetastet werden können, das Wichtigste.

Im obigen Bild ist jedes Pixel völlig zufÀllig. Es gibt keine allgemeine Richtung (Farbe) in der Textur, da jeder Wert dieselbe Wahrscheinlichkeit hat wie alle anderen. Diese Textur gibt uns eine Art Sand, der Licht in alle Richtungen streut.

WĂ€hrend eines GDC-Vortrags machte John Edwards klar, dass die zufĂ€llige Textur, die fĂŒr den Sand in Journey verwendet wurde, aus einer Gaußschen Verteilung generiert wurde. Dies stellt sicher, dass die vorherrschende Richtung mit der Normalen auf der OberflĂ€che ĂŒbereinstimmt.

MĂŒssen zufĂ€llige Vektoren normalisiert werden?
Das Bild, mit dem ich Zufallsvektoren abgetastet habe, wurde nach einem völlig zufĂ€lligen Verfahren erzeugt. Nicht nur jedes Pixel wird einzeln erzeugt: Die Komponenten R, G und B eines Pixels sind auch unabhĂ€ngig voneinander. Das heißt, im allgemeinen Fall wird fĂŒr die aus dieser Textur abgetasteten Vektoren nicht garantiert, dass sie eine LĂ€nge von gleich haben 1 .

NatĂŒrlich können Sie eine Textur erzeugen, bei der jedes Pixel beim Konvertieren aus  l e f t [ 0 , 1 r i g h t ]  in  l e f t [ - 1 , + 1 r i g h t ]  und in der Tat muss eine LĂ€nge haben 1 . Hierbei treten jedoch zwei Probleme auf.

, . -, mip-, .

, .

Implementierung


Im vorherigen Teil des Tutorials haben wir das Konzept der „normalen Karten“ kennengelernt, als es in der allerersten Gliederung der OberflĂ€chenfunktion auftauchte surf. Wenn Sie sich an das Diagramm am Anfang des Artikels erinnern, sehen Sie, dass zwei Effekte erforderlich sind, um das Rendern von Journey Sand wiederherzustellen. Die erste ( Sandnormalen ), die wir in diesem Teil des Artikels betrachten, und die zweite ( Sandwellen ) werden wir im sechsten Teil untersuchen.

 void surf (Input IN, inout SurfaceOutput o) { o.Albedo = _SandColor; o.Alpha = 1; float3 N = float3(0, 0, 1); N = RipplesNormal(N); // Covered in Journey Sand Shader #6 N = SandNormal (N); // Covered in this article o.Normal = N; } 

Im vorherigen Abschnitt haben wir das Konzept des Bump-Mappings eingefĂŒhrt, das uns gezeigt hat, dass fĂŒr einen Teil des Effekts die Textur abgetastet werden muss (dies wird im Code aufgerufen uv_SandTex).

Das Problem mit dem obigen Code ist, dass Sie fĂŒr Berechnungen die wahre Position des Punktes kennen mĂŒssen, den wir zeichnen. TatsĂ€chlich benötigen Sie eine UV-Koordinate , um die Textur abzutasten , die bestimmt, von welchem ​​Pixel gelesen werden soll. Wenn das von uns verwendete 3D-Modell relativ flach ist und eine UV-Umwandlung aufweist, können wir seine UV-Strahlung verwenden, um eine zufĂ€llige Textur abzutasten.

 N = WavesNormal(IN.uv_SandTex.xy, N); N = SandNormal (IN.uv_SandTex.xy, N); 

Alternativ können Sie auch die Position IN.worldPosdes gerenderten Punkts in der Welt ( ) verwenden.

Jetzt können wir uns endlich auf SandNormaldie Umsetzung konzentrieren. Wie bereits erwÀhnt, besteht die Idee darin, ein Pixel aus einer zufÀlligen Textur abzutasten und es (nach der Konvertierung in einen Einheitsvektor) als neue Norm zu verwenden.

 sampler2D_float _SandTex; float3 SandNormal (float2 uv, float3 N) { // Random vector float3 random = tex2D(_SandTex, uv).rgb; // Random direction // [0,1]->[-1,+1] float3 S = normalize(random * 2 - 1); return S; } 

Wie zoome ich zufÀllige Textur?
UV- 3D- , . , .

, Unity . , _SandText_ST . Unity ( ) _SandTex .

_SandText_ST : . , Tiling Offset :


, TRANSFORM_TEX :

 sampler2D_float _SandTex; float4 _SandTex_ST; float3 SandNormal (float2 uv, float3 N) { // Random vector float3 random = tex2D(_SandTex, TRANSFORM_TEX(uv, _SandTex)).rgb; // Random direction // [0,1]->[-1,+1] float3 S = normalize(random * 2 - 1); return S; } 

Kippen Sie die Normalen


Das oben gezeigte Code-Snippet funktioniert, fĂŒhrt aber nicht zu sehr guten Ergebnissen. Der Grund dafĂŒr ist einfach: Wenn wir nur eine völlig zufĂ€llige Norm zurĂŒckgeben, aber im Wesentlichen das GefĂŒhl der KrĂŒmmung verlieren. TatsĂ€chlich wird die Richtung der Normalen verwendet, um zu berechnen, wie Licht von der OberflĂ€che reflektiert werden soll, und der Hauptzweck besteht darin, das Modell entsprechend seiner KrĂŒmmung zu schattieren.

Der Unterschied ist in den folgenden Bildern zu sehen. Oben sind die Normalen der DĂŒnen völlig zufĂ€llig und es ist unmöglich zu verstehen, wo eine endet und eine andere beginnt. Von unten wird nur die Normale des Modells verwendet, wodurch wir eine zu glatte OberflĂ€che erhalten.



Beide Lösungen passen nicht zu uns. Wir brauchen etwas dazwischen. Eine zufÀllige Richtung, die aus einer Textur abgetastet wurde, sollte verwendet werden, um die Normale um einen bestimmten Betrag zu neigen , wie unten gezeigt:


Die im Diagramm beschriebene Operation heißt slerp und steht fĂŒr sphĂ€rische lineare Interpolation (sphĂ€rische lineare Interpolation). Slerp funktioniert mit einer Ausnahme genauso wie lerp - es kann verwendet werden, um sicher zwischen Einheitsvektoren zu interpolieren, und das Ergebnis der Operation sind andere Einheitsvektoren.

Leider ist die korrekte Implementierung von slerp ziemlich teuer. Und fĂŒr einen Effekt, der zumindest zufĂ€llig ist, ist es unlogisch, ihn zu verwenden.

Zeigen Sie mir die Slerp-Gleichung
, p0 und p1 , . Dann slerp :

(1)

slerp(p0,p1,t)=sin[(1−t)Ω]sin(Ω)p0+sin(tΩ)sin(Ω)p1


wo Ω — p0 und p1 , :

(2)

Ω=cos−1(p0⋅p1)



Es ist wichtig zu beachten, dass bei Verwendung der traditionellen linearen Interpolation der resultierende Vektor sehr unterschiedlich aussieht:


Die Lerp-Operation zwischen zwei getrennten Einheitsvektoren erzeugt nicht immer andere Einheitsvektoren. TatsÀchlich passiert dies nie, es sei denn, der Koeffizient ist1 oder 0 .

Trotzdem erhalten wir beim Normalisieren des Lerp-Ergebnisses tatsÀchlich einen Einheitsvektor, der dem Ergebnis von slerp erstaunlich nahe kommt:

 float3 nlerp(float3 n1, float3 n2, float t) { return normalize(lerp(n1, n2, t)); } 

Diese als nlerp bezeichnete Technik liefert eine enge AnnĂ€herung an slerp. Seine Verwendung wurde von Casey Muratori , einem der Entwickler von The Witness, populĂ€r gemacht . Wenn Sie mehr ĂŒber dieses Thema erfahren möchten , empfehle ich, Slerp-Artikel zu verstehen. Dann benutze ich es nicht von Jonathan Blow und Math Magician - Lerp, Slerp und Nlerp .

Dank nlerp können wir jetzt normale Vektoren effizient auf eine zufÀllige Seite kippen, die abgetastet wird von _SandTex:

 sampler2D_float _SandTex; float _SandStrength; float3 SandNormal (float2 uv, float3 N) { // Random vector float3 random = tex2D(_SandTex, uv).rgb; // Random direction // [0,1]->[-1,+1] float3 S = normalize(random * 2 - 1); // Rotates N towards Ns based on _SandStrength float3 Ns = nlerp(N, S, _SandStrength); return Ns; } 

Das Ergebnis ist unten dargestellt:



Was weiter


Im nĂ€chsten Teil betrachten wir die flackernden Reflexionen, dank denen die DĂŒnen dem Ozean Ă€hneln.

Danksagung


Das Videospiel Journey wurde von Thatgamecompany entwickelt und von Sony Computer Entertainment veröffentlicht . Es ist fĂŒr PC ( Epic Store ) und PS4 ( PS Store ) verfĂŒgbar .

Von Jiadi Deng werden 3D-Modelle von DĂŒnenhintergrĂŒnden und Beleuchtungsoptionen erstellt .

Ein 3D-Modell von Journeys Charakter wurde im (jetzt geschlossenen) FacePunch-Forum gefunden.

Einheitspaket


Wenn Sie diesen Effekt wiederherstellen möchten, können Sie das vollstÀndige Unity-Paket von Patreon herunterladen . Es enthÀlt alles, was Sie brauchen, vom Shader bis zum 3D-Modell.

Source: https://habr.com/ru/post/de476448/


All Articles