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 GleichungSand 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) {
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 LNormal 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 EinheitVor 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 KlimaLambert-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;
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 = ...
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 TexturenEs 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);
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.worldPos
des gerenderten Punkts in der Welt ( ) verwenden.Jetzt können wir uns endlich auf SandNormal
die 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) {
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) {
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)
wo
Ω â
p0 und
p1 , :
(2)
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) {
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.