Erkundung des Sand-Shaders des Journey-Spiels

Beginn einer Artikelserie hier

Bild

Teil 4: Spiegelbild


In diesem Teil konzentrieren wir uns auf Spiegelreflexionen, dank derer die Dünen einem Sandmeer ähneln.

Einer der faszinierendsten Effekte der Sandwiedergabe von Journey ist, wie die Dünen in den Lichtstrahlen funkeln. Diese Reflexion nennt man spiegelnd . Der Name kommt vom lateinischen Wort speculum und bedeutet "Spiegel" . Spiegelreflexion ist ein „Dach“ -Konzept, das alle Arten von Wechselwirkungen kombiniert, bei denen Licht stark in eine Richtung reflektiert und nicht gestreut oder absorbiert wird. Dank Spiegelreflexionen sehen sowohl Wasser als auch polierte Oberflächen in einem bestimmten Winkel funkelnd aus.

Auf der ReiseEs gibt drei Arten von Spiegelreflexionen: Randbeleuchtung , Ozeanspiegel- und Glitzerreflexionen (siehe Abbildung unten). In diesem Teil werden wir uns die ersten beiden Typen ansehen.




Vor und nach dem Anwenden von Spiegelreflexionen

Felgenbeleuchtung


Möglicherweise stellen Sie fest, dass auf jeder Reiseebene eine begrenzte Anzahl von Farben angezeigt wird. Und während dies eine starke und saubere Ästhetik schafft, erschwert dieser Ansatz das Rendern von Sand. Dünen werden nur durch eine begrenzte Anzahl von Schattierungen gerendert, so dass es für den Spieler schwierig ist zu verstehen, wo eine auf große Entfernung endet und die andere beginnt.

Um dies auszugleichen, hat der Rand jeder Düne einen leichten Strahlungseffekt und hebt ihre Konturen hervor. Dadurch können sich die Dünen nicht am Horizont verstecken und es entsteht die Illusion einer viel breiteren und komplexeren Umgebung.

Bevor Sie herausfinden, wie dieser Effekt implementiert werden kann, erweitern wir die Beleuchtungsfunktion, indem wir diffuse Farben hinzufügen (von uns im vorherigen Teil des Artikels betrachtet) und eine neue verallgemeinerte Komponente der Spiegelreflexion.

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);
    float3 rimColor     = RimLighting  (N, V);

    // Combining
    float3 color = diffuseColor + rimColor;

    // Final color
    return float4(color * s.Albedo, 1);
}

In dem oben gezeigten Code-Snippet sehen wir, dass die Spiegelkomponente der Randbeleuchtung, die genannt wird rimColor, einfach zur ursprünglichen diffusen Farbe hinzugefügt wird.

Hoher Dynamikbereich und Bloom-Effekte
Sowohl die diffuse Komponente als auch das Leuchten der Kanten sind RGB-Farben im Bereich von 0Vor 1. . , 1.

, , 01. , , 1. High Dynamic Range, 1«» . bloom, . .

Fresnel-Reflexionen


Das Leuchten der Kanten kann auf viele verschiedene Arten realisiert werden. Die beliebteste Shader-Codierung verwendet das bekannte Fresnel-Reflexionsmodell .

Um die Gleichung zu verstehen, die der Fresnel-Reflexion zugrunde liegt, ist es hilfreich zu visualisieren, wo sie auftritt. Das folgende Diagramm zeigt, wie die Düne durch die Kamera sichtbar ist (in blau). Der rote Pfeil zeigt die normale Oberfläche der Oberseite der Düne an, wo es sich um ein Spiegelbild handeln sollte. Es ist leicht zu erkennen, dass alle Ränder der Düne eine gemeinsame Eigenschaft haben: ihre normale (N, rot) ist senkrecht zur Blickrichtung (Vvon blauer Farbe).


Ähnlich wie in dem Teil über diffuse Farben können Sie das Skalarprodukt verwenden Nund Vum ein Maß für ihre Parallelität zu bekommen. In diesem FallNV gleich 0, weil zwei Einheitsvektoren senkrecht sind; stattdessen können wir verwenden1NVMaßnahmen ihrer Nichtparallelität zu erhalten.

Direkte Verwendung1NVwird uns keine guten Ergebnisse liefern, weil die Reflexion zu stark sein wird. Wenn wir die Reflexion schärfer machen wollen , können wir den Ausdruck nur in Grad nehmen. Größengrad von0 Vor 1bleibt auf ein Intervall begrenzt, aber der Übergang zwischen Dunkelheit und Licht wird schärfer.

Das Fresnel-Reflexionsmodell besagt, dass die Helligkeit des LichtsI wird wie folgt eingestellt:

I=(1NV)powerstrength(1)


Wo powerund strength- Dies sind zwei Parameter, mit denen der Kontrast und die Stärke des Effekts gesteuert werden können. Parameterpower und strengthmanchmal auch als Spiegel und Glanz bezeichnet , aber die Namen können variieren.

Gleichung (1) ist sehr einfach in Code umzuwandeln:

float _TerrainRimPower;
float _TerrainRimStrength;
float3 _TerrainRimColor;

float3 RimLighting(float3 N, float3 V)
{
    float rim = 1.0 - saturate(dot(N, V));
    rim = saturate(pow(rim, _TerrainRimPower) * _TerrainRimStrength);
    rim = max(rim, 0); // Never negative
    return rim * _TerrainRimColor;
}

Das Ergebnis ist in der folgenden Animation dargestellt.


Ozean spiegelnd


Einer der originellsten Aspekte des Journey- Gameplays ist, dass ein Spieler manchmal buchstäblich in den Dünen „surfen“ kann. Der führende Ingenieur John Edwards erklärte, dass dieses Spielunternehmen versucht habe, Sand nicht fester, sondern flüssiger zu machen.

Und das ist nicht ganz falsch, denn Sand kann als sehr grobe Annäherung an eine Flüssigkeit wahrgenommen werden. Und unter bestimmten Bedingungen, zum Beispiel in einer Sanduhr, verhält er sich sogar wie eine Flüssigkeit.

Um die Idee zu bekräftigen, dass Sand eine flüssige Komponente haben könnte, hat Journey einen zweiten Reflexionseffekt hinzugefügt, der häufig in flüssigen Körpern zu finden ist. John Edwards nennt es Ozeanspekular: Die Idee ist, die gleichen Reflexionen zu erzielen, die bei Sonnenuntergang auf der Oberfläche des Ozeans oder Sees sichtbar sind (siehe unten).


Nach wie vor werden wir Änderungen an der Beleuchtungsfunktion vornehmen LightingJourney, um eine neue Art der Spiegelreflexion hinzuzufügen.

float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi)
{
    // Lighting properties
    float3 L = gi.light.dir;
    float3 N = s.Normal;
    float3 V = viewDir;

    // Lighting calculation
    float3 diffuseColor	= DiffuseColor  (N, L);
    float3 rimColor     = RimLighting   (N, V);
    float3 oceanColor   = OceanSpecular (N, L, V);

    // Combining
    float3 specularColor = saturate(max(rimColor, oceanColor));
    float3 color = diffuseColor + specularColor;

    // Final color
    return float4(color * s.Albedo, 1);
}

Warum nehmen wir maximal zwei Reflexionskomponenten?
, rim lighting ocean specular. , , -. , .

, .

Spiegelreflexionen auf Wasser werden häufig mithilfe der Blinn-Fong-Reflexion realisiert , die eine kostengünstige Lösung für glänzende Materialien darstellt. Es wurde erstmals 1977 von James F. Blinn (Artikel: " Modelle der Lichtreflexion für computergestützte Bilder ") als Annäherung an eine frühere Schattierungstechnik beschrieben, die 1973 von Bui Tyong Fong entwickelt wurde (Artikel: " Beleuchtung für computergenerierte Bilder "). .

Bei Verwendung der Blinnu-Phong-Schattierung LeuchtkraftI Oberflächen ist durch die folgende Gleichung gegeben:

I=(NH)powerstrength(2)


Wo

H=V+LV+L(3)


Der Nenner von Gleichung (3) teilt den Vektor V+Lauf seiner Länge. Dies stellt sicher, dassH hat eine Länge 1. Die äquivalente Shader-Funktion zum Ausführen dieser Operation ist diese normalize. Aus geometrischer SichtH repräsentiert den Vektor zwischen Vund Lund wird daher als Halbvektor bezeichnet .



Warum liegt H zwischen V und L?
, , HVL.

, . VLLVVL.

, , V+LL+V, . , :


, , . , ( V+L) . , ( ).


, V+LVL, 1. , 1, ( ).

Eine detailliertere Beschreibung der Blinn-Fong-Reflexion finden Sie im Tutorial zu physikalisch basierten Rendering- und Beleuchtungsmodellen . Unten finden Sie eine einfache Implementierung im Shader-Code.

float _OceanSpecularPower;
float _OceanSpecularStrength;
float3 _OceanSpecularColor;

float3 OceanSpecular (float3 N, float3 L, float3 V)
{
    // Blinn-Phong
    float3 H = normalize(V + L); // Half direction
    float NdotH = max(0, dot(N, H));
    float specular = pow(NdotH, _OceanSpecularPower) * _OceanSpecularStrength;
    return specular * _OceanSpecularColor;
}

Die Animation zeigt einen Vergleich der traditionellen diffusen Schatten nach Lambert und der Spiegelschatten nach Blinn-Fong:


Teil 5: Brillante Reflexion


In diesem Teil werden wir die brillanten Reflexionen nachbilden, die normalerweise auf den Sanddünen sichtbar sind.

Kurz nach der Veröffentlichung meiner Artikelserie unternahmen Julian Oberbek und Paul Nadelek ihren eigenen Versuch, eine Szene nachzubilden , die vom Journey-Spiel in Unity inspiriert war. Der folgende Tweet zeigt, wie sie brillante Reflexionen perfektioniert haben, um eine größere zeitliche Integrität zu erzielen. Lesen Sie mehr über ihre Implementierung in einem Artikel über IndieBurg Mip Map Folding .


Im vorherigen Teil des Kurses haben wir die Implementierung von zwei Spiegelfunktionen in Journey Sand Rendering vorgestellt : Randbeleuchtung und Ozeanspekular . In diesem Teil werde ich erklären, wie die letzte Version der Spiegelreflexion implementiert wird: Glitzer .


Wenn Sie schon einmal in der Wüste waren, haben Sie wahrscheinlich bemerkt, wie glänzend Sand tatsächlich ist. Wie im Teil Sandnormalen erläutert, kann jedes Sandkorn möglicherweise Licht in zufälliger Richtung reflektieren. Aufgrund der Art der Zufallszahlen fällt ein Teil dieser reflektierten Strahlen in die Kamera. Aus diesem Grund erscheinen zufällige Sandpunkte sehr hell. Dieser Glanz ist sehr bewegungsempfindlich, da die geringste Verschiebung verhindert, dass die reflektierten Strahlen in die Kamera gelangen.

In anderen Spielen wie Astroneer und Slime Rancher wurden brillante Reflexionen für Sand und Höhlen verwendet.



Glanz: vor und nach dem Anwenden des Effekts.

Es ist einfacher, diese Glanzeigenschaften in einem größeren Bild zu bewerten:



Ohne Zweifel hängt der Glitzereffekt auf echten Dünen ganz davon ab, dass einige Sandkörner zufällig Licht in unsere Augen reflektieren. Genau genommen haben wir dies bereits im zweiten Teil des Kurses über Sandnormalen modelliert, als wir eine zufällige Verteilung von Normalen modellierten. Warum brauchen wir dafür einen anderen Effekt?

Die Antwort ist möglicherweise nicht zu offensichtlich. Stellen wir uns vor, wir versuchen, den Glanzeffekt nur mit Hilfe von Normalen wiederherzustellen. Selbst wenn alle Normalen auf die Kamera gerichtet sind, scheint der Sand immer noch nicht, da die Normalen nur die in der Szene verfügbare Lichtmenge reflektieren können. Das heißt, wir reflektieren bestenfalls nur 100% des Lichts (wenn der Sand vollständig weiß ist).

Aber wir brauchen noch etwas. Wenn das Pixel so hell erscheinen soll, dass sich das Licht auf die angrenzenden Pixel ausbreitet , sollte die Farbe größer sein1. Dies geschah, weil in Unity die Farben heller sind , wenn der Bloom-Filter mithilfe des Nachbearbeitungseffekts auf die Kamera angewendet wird1Verteilen Sie sich auf benachbarte Pixel und erzeugen Sie einen Heiligenschein, der das Gefühl erzeugt, dass einige Pixel leuchten. Dies ist die Grundlage für das HDR-Rendering .

Nein, überlagerte Normalen können nicht auf einfache Weise verwendet werden, um glänzende Oberflächen zu erzeugen. Daher ist es bequemer, diesen Effekt als separaten Prozess zu implementieren.

Theorie der Mikrofaces


Um die Situation formeller anzugehen, müssen wir die Dünen als aus mikroskopischen Spiegeln bestehend wahrnehmen, von denen jeder eine zufällige Richtung hat. Dieser Ansatz wird als Mikrofacettentheorie bezeichnet , wobei jeder dieser winzigen Spiegel als Mikrofacette bezeichnet wird . Die mathematische Grundlage der meisten modernen Schattierungsmodelle basiert auf der Theorie der Mikroflächen, einschließlich des Standard-Shader- Modells von Unity .

Der erste Schritt besteht darin, die Oberfläche der Düne in Mikroflächen zu unterteilen und deren Ausrichtung zu bestimmen. Wie bereits erwähnt, haben wir in einem Teil des Tutorials über die Normalen von Sand etwas Ähnliches gemacht, wobei die UV-Position des 3D-Modells der Düne verwendet wurde, um eine zufällige Textur abzutasten. Der gleiche Ansatz kann hier verwendet werden, um jeder Mikrofacette eine zufällige Ausrichtung zuzuweisen. Die Größe jedes Mikrofaces hängt von der Größe der Textur und dem Grad der Mip- Textur ab . Unsere Aufgabe ist es, eine bestimmte Ästhetik wiederherzustellen und nicht den Wunsch nach Fotorealismus; Dieser Ansatz wird für uns gut genug sein.

Nachdem wir eine zufällige Textur abgetastet haben, können wir jedem Sandkorn / jeder Mikrofacette der Düne eine zufällige Richtung zuordnen. Rufen wir ihn anG. Es gibt die Richtung der Helligkeit an , dh die Richtung der Normalen der Sandkörner, die wir betrachten. Ein Lichtstrahl, der auf ein Sandkorn fällt, wird reflektiert, wobei berücksichtigt wird, dass die Mikrofacette ein idealer Spiegel ist, der in die Richtung ausgerichtet istG. Der resultierende reflektierte Lichtstrahl sollte in die Kamera eintreten.R (siehe unten).


Hier können wir wieder das Skalarprodukt verwenden Rund VMaßnahmen ihrer Parallelität zu erhalten.

Ein Ansatz ist die PotenzierungRV, wie im vorherigen (vierten) Teil des Artikels erläutert. Wenn Sie dies versuchen, werden wir feststellen, dass sich das Ergebnis stark von dem in Journey unterscheidet . Brillante Reflexionen sollten selten und sehr hell sein. Am einfachsten ist es, nur die brillanten Reflexionen zu berücksichtigen, für dieRV liegt unter einem bestimmten Schwellenwert.

Implementierung


Wir können den oben beschriebenen reflectGlanzeffekt einfach mithilfe einer Funktion in Cg implementieren , was die Berechnung sehr einfach machtR.

sampler2D_float _GlitterTex;
float _GlitterThreshold;
float3 _GlitterColor;

float3 GlitterSpecular (float2 uv, float3 N, float3 L, float3 V)
{
    // Random glitter direction
    float3 G = normalize(tex2D(_GlitterTex, uv).rgb * 2 - 1); // [0,1]->[-1,+1]

    // Light that reflects on the glitter and hits the eye
    float3 R = reflect(L, G);
    float RdotV = max(0, dot(R, V));
	
    // Only the strong ones (= small RdotV)
    if (RdotV > _GlitterThreshold)
        return 0;
	
    return (1 - RdotV) * _GlitterColor;
}

Genau genommen, wenn Gdann ganz zufällig Rwird auch völlig zufällig sein. Es scheint, dass die Verwendung reflectoptional ist. Und obwohl dies für einen statischen Rahmen gilt, was passiert, wenn sich die Lichtquelle bewegt? Dies kann entweder auf die Bewegung der Sonne selbst oder auf eine an den Spieler gebundene Punktlichtquelle zurückzuführen sein. In beiden Fällen verliert der Sand die zeitliche Integrität zwischen dem aktuellen und den nachfolgenden Frames, wodurch der Glitzereffekt an zufälligen Stellen auftritt. Die Verwendung der Funktion reflectbietet jedoch ein viel stabileres Rendering.

Die Ergebnisse sind unten gezeigt:


Wie wir uns aus dem ersten Teil des Tutorials erinnern, wird die Glanzkomponente zur endgültigen Farbe hinzugefügt.

#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);
}

Es besteht eine hohe Wahrscheinlichkeit, dass einige Pixel irgendwann mehr Farbe bekommen 1, was zum Bloom-Effekt führt. Das brauchen wir. Der Effekt wird zusätzlich zu der bereits vorhandenen Spiegelreflexion (im vorherigen Teil des Artikels beschrieben) hinzugefügt, sodass glänzende Sandkörner sogar dort gefunden werden können, wo die Dünen gut beleuchtet sind.

Es gibt viele Möglichkeiten, diese Technik zu verbessern. Es hängt alles von dem Ergebnis ab, das Sie erreichen möchten. In Astroneer und Slime Rancher wird dieser Effekt beispielsweise nur nachts verwendet. Dies kann erreicht werden, indem die Stärke des Glanzeffekts in Abhängigkeit von der Richtung des Sonnenlichts verringert wird.

Zum Beispiel kann der Wert max(dot(L, fixed3(0,1,0),0))ist1wenn die Sonne von oben untergeht und gleich Null ist, wenn sie sich jenseits des Horizonts befindet. Sie können jedoch Ihr eigenes System erstellen, dessen Erscheinungsbild von Ihren Vorlieben abhängt.

Warum wird Reflexion in der Blinn-Fong-Reflexion nicht verwendet?
ocean specular, , -.

, 3D- , , . , reflect . - RVNH, .

Teil 6: Wellen


Im letzten Teil des Artikels werden wir typische Sandwellen nachbilden, die aus dem Zusammenspiel von Dünen und Wind resultieren.




Wellen auf der Oberfläche der Dünen: vorher und nachher.

Theoretisch wäre es logisch, diesen Teil nach dem Teil über die Normalen des Sandes zu setzen. Ich habe es am Ende verlassen, weil es die schwierigste der Auswirkungen des Tutorials ist. Ein Teil dieser Komplexität ist auf die Art und Weise zurückzuführen, wie die normalen Karten von einem Surface Shader gespeichert und verarbeitet werden, der viele zusätzliche Schritte ausführt.

Normale Karten


Im vorherigen (fünften) Teil haben wir eine Methode zur Herstellung von heterogenem Sand untersucht. In dem Teil, der Sandnormalen gewidmet ist, wurde eine sehr beliebte normale Kartierungstechnik verwendet, um die Art und Weise zu ändern, wie Licht mit der Geometrieoberfläche interagiert . Es wird häufig in 3D-Grafiken verwendet, um die Illusion zu erzeugen, dass das Objekt eine komplexere Geometrie aufweist, und wird normalerweise verwendet, um gekrümmte Oberflächen glatter zu machen (siehe unten).


Um diesen Effekt zu erzielen, wird jedes Pixel auf die Richtung der Normalen abgebildet und zeigt seine Ausrichtung an. Und es wird verwendet, um die Beleuchtung anstelle der tatsächlichen Ausrichtung des Netzes zu berechnen.

Durch Lesen der Richtungen der Normalen aus einer scheinbar zufälligen Textur konnten wir die Körnigkeit simulieren. Trotz der körperlichen Ungenauigkeit sieht sie immer noch glaubwürdig aus.

Wellen im Sand


Sanddünen weisen jedoch ein weiteres Merkmal auf, das nicht ignoriert werden kann: Wellen. Auf jeder Düne gibt es kleinere Dünen, die aufgrund des Einflusses des Windes erscheinen und durch die Reibung einzelner Sandkörner zusammengehalten werden.

Diese Wellen sind auf den meisten Dünen sehr gut sichtbar. Auf dem unten gezeigten Foto aus dem Oman ist zu sehen, dass der nahe Teil der Düne ein ausgeprägtes Wellenmuster aufweist.


Diese Wellen variieren erheblich, nicht nur abhängig von der Form der Düne, sondern auch von der Zusammensetzung, Richtung und Geschwindigkeit des Windes. Die meisten Dünen mit einem scharfen Gipfel haben nur auf einer Seite Wellen (siehe unten).


Der im Tutorial vorgestellte Effekt ist für glattere Dünen mit Wellen auf beiden Seiten ausgelegt. Dies ist nicht immer physikalisch korrekt, aber realistisch genug, um glaubwürdig zu sein, und ein guter erster Schritt in Richtung komplexerer Implementierungen.

Realisierung der Wellen


Es gibt viele Möglichkeiten, Wellen zu implementieren. Am billigsten ist es, sie einfach auf eine Textur zu zeichnen, aber im Tutorial wollen wir etwas anderes erreichen. Der Grund ist einfach: Die Wellen sind nicht „flach“ und müssen korrekt mit dem Licht interagieren. Wenn Sie sie einfach zeichnen, ist es unmöglich, einen realistischen Effekt zu erzielen, wenn sich die Kamera (oder die Sonne) bewegt.

Eine andere Möglichkeit, Wellen hinzuzufügen, besteht darin, die Geometrie des Dünenmodells zu ändern. Es wird jedoch nicht empfohlen, die Komplexität des Modells zu erhöhen, da dies die Gesamtleistung stark beeinflusst.

Wie wir im Teil über Sandnormalen gesehen haben, können Sie dieses Problem mit normalen Karten umgehen . Tatsächlich werden sie wie herkömmliche Texturen auf der Oberfläche gezeichnet, aber in Beleuchtungsberechnungen verwendet, um komplexere Geometrien zu simulieren.

Das heißt, die Aufgabe hat sich in eine andere verwandelt: wie man diese normalen Karten erstellt. Das manuelle Rendern ist zu zeitaufwändig. Außerdem müssen Sie jedes Mal, wenn Sie die Dünen wechseln, die Wellen neu zeichnen. Dies wird den Prozess der Erstellung von Ressourcen erheblich verlangsamen, den viele technische Künstler vermeiden möchten.

Eine viel effektivere und optimalere Lösung wäre das prozedurale Hinzufügen von Wellen . Dies bedeutet, dass sich die normalen Richtungen der Dünen basierend auf der lokalen Geometrie ändern, um nicht nur Sandkörner, sondern auch Wellen zu berücksichtigen.

Da die Wellen auf einer 3D-Oberfläche simuliert werden müssen, ist es logischer, für jedes Pixel eine Änderung der Richtung der Normalen zu implementieren. Es ist einfacher, eine nahtlose normale Karte mit einem Wellenmuster zu verwenden. Diese Karte wird dann mit der vorhandenen normalen Karte kombiniert, die zuvor für Sand verwendet wurde.

Normale Karten


Bis zu diesem Moment trafen wir drei verschiedene Normalen :

  • Geometrie Normal : Die Ausrichtung jeder Fläche des 3D-Modells, die direkt an den Scheitelpunkten gespeichert wird.
  • Sand normal : berechnet anhand der Rauschstruktur;
  • Wave Normal : Der in diesem Teil beschriebene neue Effekt.

Das folgende Beispiel auf der Beispielseite für Unity Surface Shader zeigt eine Standardmethode zum Umschreiben der Normalen eines 3D-Modells. Dies erfordert das Ändern des Werts o.Normal, was normalerweise nach dem Abtasten der Textur erfolgt (am häufigsten als normale Karte bezeichnet ).

  Shader "Example/Diffuse Bump" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _BumpMap ("Bumpmap", 2D) = "bump" {}
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
        float2 uv_MainTex;
        float2 uv_BumpMap;
      };
      sampler2D _MainTex;
      sampler2D _BumpMap;
      void surf (Input IN, inout SurfaceOutput o) {
        o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
        o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

Wir haben genau die gleiche Technik verwendet, um die Normalen der Geometrie durch die Normalen des Sandes zu ersetzen.

Was ist UnpackNormal?
. ( 1), , . X, Y Z R, G B .

1+1. 01. , «» «» . (normal packing) (normal unpacking). :

R=X2+12G=Y2+12B=Z2+12(1)


:

X=2R1Y=2G1Z=2B1(2)


Unity (2), UnpackNormal. .

Normal Map Technical Details polycount.

Steilheit der Dünen


Die erste Schwierigkeit hängt jedoch damit zusammen, dass sich die Wellenform in Abhängigkeit von der Schärfe der Dünen ändert. Niedrige, flache Dünen haben kleine Wellen; In steilen Dünen sind die Wellenmuster deutlicher. Dies bedeutet, dass Sie die Steilheit der Düne berücksichtigen müssen .

Der einfachste Weg, um dieses Problem zu umgehen, besteht darin, zwei verschiedene normale Karten für flache und steile Dünen zu erstellen. Die Grundidee besteht darin, die beiden normalen Karten basierend auf der Steilheit der Düne zu mischen.


Normale Karte für die steile Düne


Normale Karte für eine flache Düne

Normale Karten und blauer Kanal
, .

, . , (X Y) (Z) .

length(N)=1X2+Y2+Z2=1(3)


:

X2+Y2+Z2=1X2+Y2+Z2=12Z2=1X2Y2Z=1X2Y2(4)


UnpackNormal Unity. Shader API .

inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(SHADER_API_GLES)  defined(SHADER_API_MOBILE)
    return packednormal.xyz * 2 - 1;
#else
    fixed3 normal;
    normal.xy = packednormal.wy * 2 - 1;
    normal.z = sqrt(1 - normal.x*normal.x - normal.y * normal.y);
    return normal;
#endif
}

Unity DXT5nm, (packednormal.xy) - (packednormal.wy).

Normal Map Compression polycount.

Warum wirken normale Karten lila?
, , .

, . , [0,0,1].

[0,0,1][0.5,0.5,1], RGB.

; , [0,0,1].

, «». , B 1+1, 0+1. .

Die Steilheit kann mit einem Skalarprodukt berechnet werden , das häufig in der Shader-Codierung verwendet wird, um den Grad der „Parallelität“ zweier Richtungen zu berechnen. In diesem Fall nehmen wir die Richtung der Normalen der Geometrie (unten in blau dargestellt) und vergleichen sie mit einem Vektor, der zum Himmel zeigt (unten in gelb dargestellt). Das Skalarprodukt dieser beiden Vektoren gibt Werte nahe an zurück1wenn die Vektoren fast parallel sind (flache Dünen) und nahe an 0wenn der Winkel zwischen ihnen 90 Grad beträgt (steile Dünen).


Wir stehen jedoch vor dem ersten Problem - zwei Vektoren sind an dieser Operation beteiligt. Der aus der Funktion surfmit erhaltene Normalvektor o.Normalwird im Tangentenraum ausgedrückt . Dies bedeutet, dass das zur Codierung der Normalenrichtung verwendete Koordinatensystem relativ zur lokalen Oberflächengeometrie ist (siehe unten). Wir haben dieses Thema im Teil über den normalen Sand kurz angesprochen.


Ein zum Himmel zeigender Vektor wird im Weltraum ausgedrückt . Um das richtige Skalarprodukt zu erhalten, müssen beide Vektoren im gleichen Koordinatensystem ausgedrückt werden. Dies bedeutet, dass wir einen von ihnen transformieren müssen, damit beide in einem Raum ausgedrückt werden.

Glücklicherweise hilft Unity mit einer Funktion WorldNormalVector, mit der wir den normalen Vektor vom Tangentenraum in den Weltraum transformieren können . Um diese Funktion nutzen zu können, müssen wir die Struktur Inputso ändern , dass sie enthalten war float3 worldNormalund INTERNAL_DATA:

struct Input
{
    ...

    float3 worldNormal;
    INTERNAL_DATA
};

Dies wird in einem Artikel aus der Dokumentation zu Unity Writing Surface Shaders erläutert, in dem es heißt:

INTERNAL_DATA — , o.Normal.

, WorldNormalVector (IN, o.Normal).

Oft wird dies zur Hauptursache für Probleme beim Schreiben von Oberflächen-Shadern. Tatsächlich variiert der Wert o.Normal, der in der Funktion verfügbar ist surf, je nachdem, wie Sie ihn verwenden. Wenn Sie es nur lesen, enthält es den Normalenvektor des aktuellen Pixels im Weltraum . Wenn Sie den Wert ändern, befindet er sich im Tangentenraum . Wenn Sie aufnehmen , aber dennoch Zugriff auf das Normale im Weltraum benötigen (wie in unserem Fall), können Sie es verwenden . Dazu müssen Sie jedoch eine kleine Änderung an der oben gezeigten Struktur vornehmen .o.Normalo.Normal

o.NormalWorldNormalVector (IN, o.Normal)Input

Was ist INTERNAL_DATA?
INTERNAL_DATA Unity.

, , WorldNormalVector. , , . ( ).

, 3D- 3×3. , , (tangent to world matrix), Unity TtoW.

INTERNAL_DATA TtoW Input. , «Show generated code» :


, INTERNAL_DATA — , TtoW:

#define INTERNAL_DATA
    half3 internalSurfaceTtoW0;
    half3 internalSurfaceTtoW1;
    half3 internalSurfaceTtoW2;

half3x3, half3.

WorldNormalVector, , ( ) TtoW:

#define WorldNormalVector(data,normal)
    fixed3
    (
        dot(data.internalSurfaceTtoW0, normal),
        dot(data.internalSurfaceTtoW1, normal),
        dot(data.internalSurfaceTtoW2, normal)
    )

mul, TtoW , .

, :

[ToW1,1ToW1,2ToW1,3ToW2,1ToW2,2ToW2,3ToW3,1ToW3,2ToW3,3][N1N2N3]=[[ToW1,1ToW1,2ToW1,3][N1N2N3][ToW2,1ToW2,2ToW2,3][N1N2N3][ToW3,1ToW3,2ToW3,3][N1N2N3]]


LearnOpenGL.

Implementierung


Ein Ausschnitt aus dem folgenden Code konvertiert die Normale von Tangente in Weltraum und berechnet die Steigung relativ zur Aufwärtsrichtung .

// Calculates normal in world space
float3 N_WORLD = WorldNormalVector(IN, o.Normal);
float3 UP_WORLD = float3(0, 1, 0);

// Calculates "steepness"
// => 0: steep (90 degrees surface)
//  => 1: shallow (flat surface)
float steepness = saturate(dot(N_WORLD, UP_WORLD));

Nachdem wir die Steilheit der Düne berechnet haben, können wir damit die beiden normalen Karten mischen. Beide normalen Karten werden sowohl flach als auch cool abgetastet (im folgenden Code heißen sie _ShallowTexund _SteepTex). Dann werden sie basierend auf dem Wert gemischt steepness:

float2 uv = W.xz;

// [0,1]->[-1,+1]
float3 shallow = UnpackNormal(tex2D(_ShallowTex, TRANSFORM_TEX(uv, _ShallowTex)));
float3 steep   = UnpackNormal(tex2D(_SteepTex,   TRANSFORM_TEX(uv, _SteepTex  )));

// Steepness normal
float3 S = normalerp(steep, shallow, steepness);

Wie im Teil über Sandnormalen angegeben, ist es ziemlich schwierig, normale Karten richtig zu kombinieren, und dies ist nicht möglich lerp. In diesem Fall slerpist es korrekter , eine weniger teure Funktion zu verwenden normalerp.

Wellenmischung


Wenn wir den oben gezeigten Code verwenden, können uns die Ergebnisse enttäuschen. Dies liegt daran, dass die Dünen nur eine sehr geringe Steilheit aufweisen, was zu einer zu starken Vermischung der beiden normalen Texturen führt. Um dies zu beheben, können wir eine nichtlineare Transformation auf die Steilheit anwenden, die die Schärfe der Mischung erhöht:

// Steepness to blending
steepness = pow(steepness, _SteepnessSharpnessPower);

Wenn zwei Texturen gemischt werden, wird dies häufig verwendet, um ihre Schärfe und ihren Kontrast zu steuern pow. Wie und warum es funktioniert, haben wir in meinem Tutorial zum physikalisch basierten Rendern gelernt .

Unten sehen wir zwei Farbverläufe. Oben sind die Farben von Schwarz nach Weiß dargestellt, die entlang der X-Achse linear mit interpoliert sind c = uv.x. Unten wird der gleiche Farbverlauf dargestellt mit c = pow(uv.x*1.5)*3.0:



Es ist leicht zu bemerken, powwodurch Sie einen viel schärferen Übergang zwischen Schwarz und Weiß erstellen können. Wenn wir mit Texturen arbeiten, reduziert dies deren Überlappung und erzeugt schärfere Kanten.

Richtung der Dünen


Alles, was wir früher gemacht haben, funktioniert perfekt. Aber wir müssen noch ein letztes Problem lösen. Die Wellen variieren mit der Steilheit , aber nicht mit der Richtung . Wie oben erwähnt, sind Wellen normalerweise nicht symmetrisch, da der Wind hauptsächlich in eine Richtung weht.

Um die Wellen noch realistischer zu machen, müssen wir zwei weitere normale Karten hinzufügen (siehe Tabelle unten). Sie können je nach Parallelität der Düne der X- oder Z-Achse gemischt werden.

CoolEben
X.cool xflach x
Z.cool zflach z

Hier müssen wir die Berechnung der Parallelität der Düne relativ zur Z-Achse implementieren. Dies kann ähnlich wie die Berechnung der Steilheit erfolgen, float3 UP_WORLD = float3(0, 1, 0);kann aber stattdessen verwendet werden float3 Z_WORLD = float3(0, 0, 1);.

Diesen letzten Schritt werde ich dir überlassen. Wenn Sie Probleme haben, finden Sie am Ende dieses Tutorials einen Link zum Herunterladen des vollständigen Unity-Pakets.

Fazit


Dies ist der letzte Teil einer Reihe von Tutorials zum Rendern von Sand aus Journey.

Das Folgende zeigt, wie weit wir in dieser Serie vorankommen konnten:



Vorher und nachher

möchte ich mich bei Ihnen dafür bedanken, dass Sie dieses ziemlich lange Tutorial bis zum Ende gelesen haben. Ich hoffe, es hat Ihnen Spaß gemacht, diesen Shader zu erkunden und neu zu erstellen.

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 .

3D- Dünenmodelle , Hintergründe und Beleuchtungsoptionen wurden von Jiadi Deng erstellt .

Ein 3D-Modell des Journey-Charakters wurde im FacePunch-Forum gefunden (jetzt geschlossen).

Einheitspaket


Wenn Sie diesen Effekt wiederherstellen möchten, steht das vollständige Unity-Paket auf Patreon zum Download zur Verfügung . Es bietet alles, was Sie brauchen, von Shadern bis zu 3D-Modellen.

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


All Articles