Unity Interactive Map Shader

Bild

In diesem Tutorial geht es um interaktive Karten und deren Erstellung in Unity mithilfe von Shadern.

Dieser Effekt kann als Grundlage für komplexere Techniken wie holographische Projektionen oder sogar einen Sandtisch aus dem Film "Black Panther" dienen.

Eine Inspiration für dieses Tutorial ist der von Baran Kahyaoglu veröffentlichte Tweet, der ein Beispiel dafür zeigt, was er für Mapbox erstellt .



Die Szene (ohne Karte) wurde aus der Unity Visual Effect Graph-Raumschiff-Demo (siehe unten) entnommen, die hier heruntergeladen werden kann .


Teil 1. Scheitelpunktversatz


Anatomie der Wirkung


Das erste, was Sie sofort bemerken können, ist, dass geografische Karten flach sind : Wenn sie als Texturen verwendet werden, fehlt ihnen die Dreidimensionalität, die ein reales 3D-Modell des entsprechenden Kartenbereichs haben würde.

Sie können diese Lösung anwenden: Erstellen Sie ein 3D-Modell des Bereichs, der im Spiel benötigt wird, und wenden Sie dann eine Textur von der Karte darauf an. Dies wird zur Lösung des Problems beitragen, nimmt jedoch viel Zeit in Anspruch und ermöglicht es nicht, den Effekt des „Scrollens“ aus dem Video Baran Kahyaoglu zu realisieren.

Offensichtlich ist ein technischerer Ansatz am besten. Glücklicherweise können Shader verwendet werden, um die Geometrie eines 3D-Modells zu ändern. Mit ihrer Hilfe können Sie jedes Flugzeug in Täler und Berge der Region verwandeln, die wir brauchen.

In diesem Tutorial verwenden wir eine Karte von Chillot , Chilli, berühmt für seine charakteristischen Hügel. Das Bild unten zeigt die Textur des Bereichs, der auf einem runden Netz aufgetragen ist.


Obwohl wir Hügel und Berge sehen, sind sie immer noch völlig flach. Dies zerstört die Illusion des Realismus.

Extrudieren von Normalen


Der erste Schritt zur Verwendung von Shadern zum Ändern der Geometrie ist eine Technik, die als normale Extrusion bezeichnet wird . Sie benötigt einen Scheitelpunktmodifikator : eine Funktion, mit der einzelne Scheitelpunkte eines 3D-Modells bearbeitet werden können.

Die Art und Weise, wie der Vertex-Modifikator verwendet wird, hängt von der Art des verwendeten Shaders ab. In diesem Tutorial ändern wir den Surface Standard Shader - einen der Shader-Typen, die Sie in Unity erstellen können.

Es gibt viele Möglichkeiten, die Eckpunkte eines 3D-Modells zu bearbeiten. Eine der ersten Methoden, die in den meisten Vertex-Shader-Tutorials beschrieben werden, ist das Extrudieren von Normalen . Es besteht darin, jeden Scheitelpunkt nach außen zu drücken (zu extrudieren ), wodurch das 3D-Modell ein aufgeblähteres Aussehen erhält. "Außerhalb" bedeutet, dass sich jeder Scheitelpunkt entlang der Richtung der Normalen bewegt.


Bei glatten Oberflächen funktioniert dies sehr gut, aber bei Modellen mit schlechten Scheitelpunktverbindungen kann diese Methode seltsame Artefakte erzeugen. Dieser Effekt wird in einem meiner ersten Tutorials ausführlich erläutert: Eine sanfte Einführung in Shader , in der ich zeigte, wie ein 3D-Modell extrudiert und eingedrungen wird .


Das Hinzufügen von extrudierten Normalen zu einem Oberflächen-Shader ist sehr einfach. Jeder Surface Shader verfügt über eine #pragma , mit der zusätzliche Informationen und Befehle übertragen werden. Ein solcher Befehl ist vert , was bedeutet, dass die vert Funktion verwendet wird, um jeden Scheitelpunkt des 3D-Modells zu verarbeiten.

Der bearbeitete Shader lautet wie folgt:

 #pragma surface surf Standard fullforwardshadows addshadow vertex:vert ... float _Amount; ... void vert(inout appdata_base v) { v.vertex.xyz += v.normal * _Amount; } 

Da wir die Position der Scheitelpunkte ändern, müssen wir auch addshadow wenn das Modell Schatten korrekt auf sich selbst werfen soll.

Was ist appdata_base?
Wie Sie sehen können, haben wir eine Funktion des Vertices-Modifikators ( vert ) hinzugefügt, der als Parameter eine Struktur namens appdata_base . Diese Struktur speichert Informationen zu jedem einzelnen Scheitelpunkt des 3D-Modells. Es enthält nicht nur die Scheitelpunktposition ( v.vertex ), sondern auch andere Felder, z. B. die normale Richtung ( v.normal ) und Texturinformationen, die dem Scheitelpunkt ( v.texcoord ) zugeordnet sind.

In einigen Fällen reicht dies nicht aus, und wir benötigen möglicherweise andere Eigenschaften, z. B. die Scheitelpunktfarbe ( v.color ) und die Tangentenrichtung ( v.tangent ). Vertex-Modifikatoren können mithilfe einer Vielzahl anderer appdata_tan werden, einschließlich appdata_tan und appdata_full , die auf Kosten eines geringen Leistungsaufwands mehr Informationen bereitstellen. Weitere appdata zu appdata (und ihren Varianten) finden Sie im Unity3D-Wiki .

Wie werden Werte von vert zurückgegeben?
Die Top-Funktion hat keinen Rückgabewert. Wenn Sie mit der C # -Sprache vertraut sind, sollten Sie wissen, dass Strukturen als Wert übergeben werden. Wenn sich v.vertex ändert v.vertex wirkt sich dies nur auf die Kopie von v , deren Umfang durch den Hauptteil der Funktion begrenzt ist.

v auch als inout deklariert, was bedeutet, dass es sowohl für die Eingabe als auch für die Ausgabe verwendet wird. Alle Änderungen, die Sie vornehmen, ändern die Variable selbst, die wir an vert . Die Schlüsselwörter inout und out sehr häufig in der Computergrafik verwendet und können grob mit ref und out in C # korreliert werden.

Extrudieren von Normalen mit Texturen


Der oben verwendete Code funktioniert korrekt, ist jedoch weit von dem gewünschten Effekt entfernt. Der Grund ist, dass wir nicht alle Eckpunkte um den gleichen Betrag extrudieren möchten. Wir möchten, dass die Oberfläche des 3D-Modells mit den Tälern und Bergen der entsprechenden geografischen Region übereinstimmt. Zuerst müssen wir irgendwie Informationen darüber speichern und abrufen, wie viel jeder Punkt auf der Karte angehoben wird. Wir möchten, dass das Extrudieren von der Textur beeinflusst wird, in der die Höhen der Landschaft codiert sind. Solche Texturen werden oft als Höhenkarten bezeichnet , aber je nach Kontext werden sie auch als Tiefenkarten bezeichnet . Nachdem wir Informationen über die Höhen erhalten haben, können wir die Extrusion der Ebene basierend auf der Höhenkarte ändern. Wie in der Abbildung gezeigt, können wir so das Anheben und Absenken von Bereichen steuern.


Es ist ganz einfach, ein Satellitenbild des gewünschten geografischen Gebiets und eine zugehörige Höhenkarte zu finden. Unten finden Sie die Satellitenkarte des Mars (oben) und die Höhenkarte (unten), die in diesem Tutorial verwendet wurden:



Ich habe in einer anderen Reihe von Tutorials mit dem Titel "3D-Fotos von Facebook von innen: Parallaxen-Shader" [ Übersetzung in Habré] ausführlich über das Konzept der Tiefenkarte gesprochen.

In diesem Tutorial wird davon ausgegangen, dass die Höhenkarte als Bild in Graustufen gespeichert ist, wobei Schwarzweiß niedrigeren und höheren Höhen entspricht. Wir brauchen diese Werte auch, um linear zu skalieren, dh der Farbunterschied, beispielsweise bei 0.1 entspricht einem Höhenunterschied zwischen 0 und 0.1 oder zwischen 0.9 und 1.0 . Bei Tiefenkarten ist dies nicht immer der Fall, da viele von ihnen Tiefeninformationen auf einer logarithmischen Skala speichern.

Zum Abtasten einer Textur werden zwei Informationselemente benötigt: die Textur selbst und die UV-Koordinaten des Punktes, den wir abtasten möchten. Auf Letzteres kann über das in der Struktur texcoord gespeicherte Feld appdata_base werden. Dies ist die UV-Koordinate, die dem aktuell verarbeiteten Scheitelpunkt zugeordnet ist. Die tex2D in einer Oberflächenfunktion erfolgt mit tex2D . Wenn wir uns jedoch in einer , ist tex2Dlod erforderlich.

Im folgenden _HeightMap eine Textur namens _HeightMap verwendet, um den für jeden Scheitelpunkt durchgeführten Extrusionswert zu ändern:

 sampler2D _HeightMap; ... void vert(inout appdata_base v) { fixed height = tex2Dlod(_HeightMap, float4(v.texcoord.xy, 0, 0)).r; vertex.xyz += v.normal * height * _Amount; } 

Warum kann tex2D nicht als Scheitelpunktfunktion verwendet werden?
Wenn Sie sich den Code ansehen, den Unity für Standard Surface Shader generiert, werden Sie feststellen, dass er bereits ein Beispiel für das Beispiel von Texturen enthält. Insbesondere wird die _MainTex ( _MainTex ) in einer Oberflächenfunktion (genannt surf ) unter Verwendung der integrierten tex2D Funktion tex2D .

Tatsächlich wird tex2D verwendet, um Pixel aus einer Textur tex2D , unabhängig davon, was darin gespeichert ist, Farbe oder Höhe. Möglicherweise stellen Sie jedoch fest, dass tex2D nicht in einer Scheitelpunktfunktion verwendet werden kann.

Der Grund ist, dass tex2D nicht nur Pixel aus der Textur liest. Sie entscheidet auch, welche Version der Textur verwendet werden soll, abhängig von der Entfernung zur Kamera. Diese Technik wird als Mipmapping bezeichnet : Sie ermöglicht kleinere Versionen einer einzelnen Textur, die automatisch in unterschiedlichen Entfernungen verwendet werden können.

In der Oberflächenfunktion weiß der Shader bereits, welche MIP-Textur verwendet werden soll. Diese Informationen sind möglicherweise noch nicht in der Scheitelpunktfunktion verfügbar, und daher kann tex2D nicht mit voller tex2D verwendet werden. Im Gegensatz dazu kann der tex2Dlod Funktion zwei zusätzliche Parameter übergeben werden, die in diesem Tutorial einen Nullwert haben können.

Das Ergebnis ist in den folgenden Bildern deutlich sichtbar.



In diesem Fall kann eine leichte Vereinfachung vorgenommen werden. Der zuvor überprüfte Code kann mit jeder Geometrie verwendet werden. Wir können jedoch davon ausgehen, dass die Oberfläche absolut flach ist. Tatsächlich möchten wir diesen Effekt wirklich auf das Flugzeug anwenden.

Daher können Sie v.normal entfernen und durch float3(0, 1, 0) ersetzen:

 void vert(inout appdata_base v) { float3 normal = float3(0, 1, 0); fixed height = tex2Dlod(_HeightMap, float4(v.texcoord.xy, 0, 0)).r; vertex.xyz += normal * height * _Amount; } 

Wir könnten dies tun, weil alle Koordinaten in appdata_base im appdata_base gespeichert sind, appdata_base sie werden relativ zum Zentrum und zur Ausrichtung des 3D-Modells festgelegt. Übergang, Drehung und Skalierung mit Transformation in Unity ändern Position, Drehung und Skalierung des Objekts, haben jedoch keinen Einfluss auf das ursprüngliche 3D-Modell.

Teil 2. Bildlaufeffekt


Alles, was wir oben gemacht haben, funktioniert ziemlich gut. Bevor wir getVertex , extrahieren wir den Code, der zur Berechnung der neuen Scheitelpunkthöhe erforderlich ist, in eine separate Funktion getVertex :

 float4 getVertex(float4 vertex, float2 texcoord) { float3 normal = float3(0, 1, 0); fixed height = tex2Dlod(_HeightMap, float4(texcoord, 0, 0)).r; vertex.xyz += normal * height * _Amount; return vertex; } 

Dann hat die gesamte Funktion vert die Form:

 void vert(inout appdata_base v) { vertex = getVertex(v.vertex, v.texcoord.xy); } 

Wir haben dies getan, weil wir unten die Höhe mehrerer Punkte berechnen müssen. Aufgrund der Tatsache, dass diese Funktionalität in einer eigenen Funktion ausgeführt wird, wird der Code viel einfacher.

UV-Koordinatenberechnung


Dies führt uns jedoch zu einem anderen Problem. Die Funktion getVertex hängt nicht nur von der Position des aktuellen Scheitelpunkts (v.vertex) ab, sondern auch von seinen UV-Koordinaten ( v.texcoord ).

Wenn wir den Scheitelpunkthöhenversatz berechnen möchten, den die vert Funktion derzeit verarbeitet, sind beide Datenelemente in der Struktur appdata_base verfügbar. Was passiert jedoch, wenn wir die Position eines benachbarten Punkts abtasten müssen? In diesem Fall können wir die xyz-Position im Modellraum kennen , haben jedoch keinen Zugriff auf die UV-Koordinaten.

Dies bedeutet, dass das vorhandene System den Höhenversatz nur für den aktuellen Scheitelpunkt berechnen kann. Eine solche Einschränkung wird es uns nicht ermöglichen, weiterzumachen, daher müssen wir eine Lösung finden.

Am einfachsten ist es, die UV-Koordinaten eines 3D-Objekts zu berechnen und die Position seines Scheitelpunkts zu kennen. Dies ist eine sehr schwierige Aufgabe, und es gibt verschiedene Techniken, um sie zu lösen (eine der beliebtesten ist die triplanare Projektion ). In diesem speziellen Fall müssen wir UV jedoch nicht an die Geometrie anpassen. Wenn wir davon ausgehen, dass der Shader immer auf das flache Netz angewendet wird, wird die Aufgabe trivial.

Wir können UV-Koordinaten (unteres Bild) aus den Positionen der Eckpunkte (oberes Bild) berechnen, da beide linear auf einem flachen Netz überlagert sind.



Dies bedeutet, dass wir zur Lösung unseres Problems die Komponenten XZ der Scheitelpunktposition in die entsprechenden UV-Koordinaten umwandeln müssen.


Diese Prozedur wird als lineare Interpolation bezeichnet . Es wird auf meiner Website ausführlich besprochen (zum Beispiel: Die Geheimnisse der Farbinterpolation ).

In den meisten Fällen liegen die UV-Werte im Bereich von 0vorher 1;; Im Gegensatz dazu sind die Koordinaten jedes Scheitelpunkts möglicherweise unbegrenzt. Aus mathematischer Sicht benötigen wir für die Umstellung von XZ auf UV nur deren Grenzwerte:

  • Xmin, Xmax
  • Zmin, Zmax
  • Umin, Umax
  • Vmin, Vmax

die unten gezeigt werden:


Diese Werte variieren je nach verwendetem Netz. Auf der Unity-Ebene liegen die UV-Koordinaten im Bereich von 0vorher 1und die Koordinaten der Eckpunkte liegen im Bereich von 5vorher +5.

Die Gleichungen zur Umwandlung von XZ in UV sind:

(1)
Bild


Wie werden sie angezeigt?
Wenn Sie mit dem Konzept der linearen Interpolation nicht vertraut sind, können diese Gleichungen ziemlich einschüchternd wirken.

Sie werden jedoch ganz einfach angezeigt. Schauen wir uns nur ein Beispiel an. U. Wir haben zwei Intervalle: eines hat Werte von Xminvorher Xmaxein anderer aus Uminvorher Umax. Eingehende Daten für die Koordinate Xist die Koordinate des aktuellen Scheitelpunkts, der verarbeitet wird, und die Ausgabe ist die Koordinate Uwird verwendet, um die Textur abzutasten.

Wir müssen die Eigenschaften der Proportionalität zwischen beibehalten Xund sein Intervall und Uund sein Intervall. Zum Beispiel wenn Xzählt dann 25% seines Intervalls Uwird auch 25% seines Intervalls ausmachen.

All dies ist in der folgenden Abbildung dargestellt:


Daraus können wir schließen, dass der Anteil des roten Segments in Bezug auf das Rosa dem Anteil zwischen dem blauen Segment und dem blauen Segment entsprechen sollte:

(2)

Jetzt können wir die oben gezeigte Gleichung transformieren, um zu erhalten U::


und diese Gleichung hat genau die gleiche Form wie oben gezeigt (1).

Diese Gleichungen können wie folgt im Code implementiert werden:

 float2 _VertexMin; float2 _VertexMax; float2 _UVMin; float2 _UVMax; float2 vertexToUV(float4 vertex) { return (vertex.xz - _VertexMin) / (_VertexMax - _VertexMin) * (_UVMax - _UVMin) + _UVMin; } 

Jetzt können wir die Funktion getVertex , ohne v.texcoord :

 float4 getVertex(float4 vertex) { float3 normal = float3(0, 1, 0); float2 texcoord = vertexToUV(vertex); fixed height = tex2Dlod(_HeightMap, float4(texcoord, 0, 0)).r; vertex.xyz += normal * height * _Amount; return vertex; } 

Dann nimmt die gesamte Funktion vert die Form an:

 void vert(inout appdata_base v) { v.vertex = getVertex(v.vertex); } 

Bildlaufeffekt


Dank des von uns geschriebenen Codes wird die gesamte Karte auf dem Netz angezeigt. Wenn wir die Anzeige verbessern wollen, müssen wir Änderungen vornehmen.

Lassen Sie uns den Code etwas formalisieren. Erstens müssen wir möglicherweise einen separaten Teil der Karte vergrößern, anstatt ihn als Ganzes zu betrachten.


Dieser Bereich kann durch zwei Werte definiert werden: seine Größe ( _CropSize ) und seine Position auf der Karte ( _CropOffset ), gemessen im Scheitelpunktraum (von _VertexMin bis _VertexMax ).

 // Cropping float2 _CropSize; float2 _CropOffset; 

Nachdem wir diese beiden Werte erhalten haben, können wir wieder die lineare Interpolation verwenden, sodass getVertex nicht für die aktuelle Position der Oberseite des 3D-Modells getVertex , sondern für den skalierten und übertragenen Punkt.


Relevanter Code:

 void vert(inout appdata_base v) { float2 croppedMin = _CropOffset; float2 croppedMax = croppedMin + _CropSize; // v.vertex.xz: [_VertexMin, _VertexMax] // cropped.xz : [croppedMin, croppedMax] float4 cropped = v.vertex; cropped.xz = (v.vertex.xz - _VertexMin) / (_VertexMax - _VertexMin) * (croppedMax - croppedMin) + croppedMin; v.vertex.y = getVertex(cropped); } 

Wenn wir scrollen möchten, reicht es aus, _CropOffset über das Skript zu aktualisieren. Aus diesem Grund wird der Kürzungsbereich verschoben und tatsächlich durch die Landschaft gescrollt.

 public class MoveMap : MonoBehaviour { public Material Material; public Vector2 Speed; public Vector2 Offset; private int CropOffsetID; void Start () { CropOffsetID = Shader.PropertyToID("_CropOffset"); } void Update () { Material.SetVector(CropOffsetID, Speed * Time.time + Offset); } } 

Damit dies funktioniert, ist es sehr wichtig, den Umbruchmodus aller Texturen auf Wiederholen einzustellen. Wenn dies nicht getan wird, können wir die Textur nicht schleifen.

Für den Zoom / Zoom-Effekt reicht es aus, nur _CropSize ändern.

Teil 3. Geländeschattierung


Flache Schattierung


Der gesamte Code, den wir geschrieben haben, funktioniert, hat aber ein ernstes Problem. Das Modell zu beschatten ist irgendwie seltsam. Die Oberfläche ist richtig gekrümmt, reagiert aber auf Licht, als wäre es flach.

Dies ist in den folgenden Bildern sehr deutlich zu sehen. Das obere Bild zeigt einen vorhandenen Shader. Der untere Teil zeigt, wie es tatsächlich funktioniert.



Die Lösung dieses Problems kann eine große Herausforderung sein. Aber zuerst müssen wir herausfinden, was der Fehler ist.

Der normale Extrusionsvorgang hat die allgemeine Geometrie der ursprünglich verwendeten Ebene geändert. Unity hat jedoch nur die Position der Scheitelpunkte geändert, nicht jedoch ihre normalen Richtungen. Die Richtung der Scheitelpunktnormalen ist, wie der Name schon sagt, ein Einheitslängenvektor ( Richtung ), der senkrecht zur Oberfläche anzeigt. Normalen sind notwendig, weil sie eine wichtige Rolle bei der Schattierung eines 3D-Modells spielen. Sie werden von allen Oberflächen-Shadern verwendet, um zu berechnen, wie Licht von jedem Dreieck des 3D-Modells reflektiert werden soll. Normalerweise ist dies notwendig, um die Dreidimensionalität des Modells zu verbessern. Beispielsweise wird Licht von einer ebenen Oberfläche reflektiert, genau wie es von einer gekrümmten Oberfläche reflektiert wird. Dieser Trick wird häufig verwendet, um Low-Poly-Oberflächen glatter aussehen zu lassen als sie tatsächlich sind (siehe unten).


In unserem Fall passiert jedoch das Gegenteil. Die Geometrie ist gekrümmt und glatt, aber da alle Normalen nach oben gerichtet sind, wird das Licht vom Modell reflektiert, als wäre es flach (siehe unten):


Weitere Informationen zur Rolle von Normalen bei der Objektschattierung finden Sie im Artikel über Normal Mapping (Bump Mapping) , in dem identische Zylinder trotz des gleichen 3D-Modells aufgrund unterschiedlicher Methoden zur Berechnung von Scheitelpunktnormalen sehr unterschiedlich aussehen (siehe unten).



Leider verfügen weder Unity noch die Sprache zum Erstellen von Shadern über eine integrierte Lösung zum automatischen Neuberechnen von Normalen. Dies bedeutet, dass Sie sie abhängig von der lokalen Geometrie des 3D-Modells manuell ändern müssen.

Normale Berechnung


Die einzige Möglichkeit, das Schattierungsproblem zu beheben, besteht darin, die Normalen basierend auf der Oberflächengeometrie manuell zu berechnen. Eine ähnliche Aufgabe wurde in einem Beitrag von Vertex Displacement - Melting Shader Part 1 besprochen, in dem das Schmelzen von 3D-Modellen in Cone Wars simuliert wurde .

Obwohl der fertige Code in 3D-Koordinaten arbeiten muss, beschränken wir die Aufgabe vorerst auf nur zwei Dimensionen. Stellen Sie sich vor, Sie müssen die Richtung der Normalen berechnen, die dem Punkt auf der 2D-Kurve entspricht (der große blaue Pfeil im folgenden Diagramm).


Aus geometrischer Sicht ist die Richtung der Normalen (großer blauer Pfeil) ein Vektor senkrecht zu der Tangente, die durch den für uns interessanten Punkt verläuft (eine dünne blaue Linie). Die Tangente kann als Linie dargestellt werden, die sich auf der Krümmung des Modells befindet. Ein Tangentenvektor ist ein Einheitsvektor , der auf einer Tangente liegt.

Dies bedeutet, dass Sie zur Berechnung der Normalen zwei Schritte ausführen müssen: Suchen Sie zuerst die Tangente an den gewünschten Punkt. Berechnen Sie dann den Vektor senkrecht dazu (dies ist die notwendige Richtung der Normalen ).

Tangentenberechnung


Um die Normalität zu erhalten, müssen wir zuerst die Tangente berechnen. Sie kann angenähert werden, indem ein Punkt in der Nähe abgetastet und daraus eine Linie in der Nähe des Scheitelpunkts erstellt wird. Je kleiner die Linie, desto genauer ist der Wert.

Drei Schritte sind erforderlich:

  • Stufe 1. Bewegen Sie eine kleine Menge auf einer ebenen Fläche
  • Schritt 2. Berechnen Sie die Höhe des neuen Punktes.
  • Schritt 3. Verwenden Sie die Höhe des aktuellen Punkts, um die Tangente zu berechnen

All dies ist im Bild unten zu sehen:


Damit dies funktioniert, müssen wir die Höhe von zwei Punkten berechnen, nicht von einem. Zum Glück wissen wir bereits, wie das geht. Im vorherigen Teil des Tutorials haben wir eine Funktion erstellt, die die Höhe einer Landschaft basierend auf einem Netzpunkt abtastet. Wir haben es getVertex .

Wir können den neuen Scheitelpunktwert am aktuellen Punkt und dann an zwei anderen nehmen. Einer ist für die Tangente, der andere für die Tangente an zwei Punkten. Mit ihrer Hilfe bekommen wir das Normale. Wenn das ursprüngliche Netz, mit dem der Effekt erstellt wurde, flach ist (und in unserem Fall auch), benötigen wir keinen Zugriff auf v.normal und können einfach float3(0, 0, 1) für Tangente bzw. Tangente an zwei Punkte float3(0, 0, 1) und float3(1, 0, 0) . Wenn wir dasselbe tun wollten, aber zum Beispiel für eine Kugel, wäre es viel schwieriger, zwei geeignete Punkte für die Berechnung der Tangente und der Tangente an zwei Punkte zu finden.

Vektorgrafiken


Nachdem wir die geeigneten Tangenten- und Tangentenvektoren für zwei Punkte erhalten haben, können wir die Normalen unter Verwendung einer Operation berechnen, die als Vektorprodukt bezeichnet wird . Es gibt viele Definitionen und Erklärungen für eine Vektorarbeit und deren Funktionsweise.

Ein Vektorprodukt empfängt zwei Vektoren und gibt einen neuen zurück. Wenn zwei Anfangsvektoren Einheit waren (ihre Länge ist gleich Eins) und sie sich in einem Winkel von 90 befinden, befindet sich der resultierende Vektor bei 90 Grad relativ zu beiden.

Dies kann zunächst verwirrend sein, kann jedoch grafisch wie folgt dargestellt werden: Das Vektorprodukt zweier Achsen erzeugt eine dritte. Also X malY=Zaber auch X malZ=Y, usw.

Wenn wir einen ausreichend kleinen Schritt machen (im Code ist dies offset ), befinden sich die Vektoren der Tangente und der Tangente an zwei Punkte in einem Winkel von 90 Grad.Zusammen mit dem Normalenvektor bilden sie drei senkrechte Achsen, die entlang der Oberfläche des Modells ausgerichtet sind.

Wenn wir das wissen, können wir den gesamten notwendigen Code schreiben, um den normalen Vektor zu berechnen und zu aktualisieren.

 void vert(inout appdata_base v) { float3 bitangent = float3(1, 0, 0); float3 tangent = float3(0, 0, 1); float offset = 0.01; float4 vertexBitangent = getVertex(v.vertex + float4(bitangent * offset, 0) ); float4 vertex = getVertex(v.vertex); float4 vertexTangent = getVertex(v.vertex + float4(tangent * offset, 0) ); float3 newBitangent = (vertexBitangent - vertex).xyz; float3 newTangent = (vertexTangent - vertex).xyz; v.normal = cross(newTangent, newBitangent); v.vertex.y = vertex.y; } 

Alles zusammenfügen


Jetzt, da alles funktioniert, können wir den Scroll-Effekt zurückgeben.

 void vert(inout appdata_base v) { // v.vertex.xz: [_VertexMin, _VertexMax] // cropped.xz : [croppedMin, croppedMax] float2 croppedMin = _CropOffset; float2 croppedMax = croppedMin + _CropSize; float4 cropped = v.vertex; cropped.xz = (v.vertex.xz - _VertexMin) / (_VertexMax - _VertexMin) * (croppedMax - croppedMin) + croppedMin; float3 bitangent = float3(1, 0, 0); float3 normal = float3(0, 1, 0); float3 tangent = float3(0, 0, 1); float offset = 0.01; float4 vertexBitangent = getVertex(cropped + float4(bitangent * offset, 0) ); float4 vertex = getVertex(cropped); float4 vertexTangent = getVertex(cropped + float4(tangent * offset, 0) ); float3 newBitangent = (vertexBitangent - vertex).xyz; float3 newTangent = (vertexTangent - vertex).xyz; v.normal = cross(newTangent, newBitangent); v.vertex.y = vertex.y; v.texcoord = float4(vertexToUV(cropped), 0,0); } 

Und damit ist unser Effekt endlich abgeschlossen.


Wohin als nächstes gehen


Dieses Tutorial kann zur Grundlage für komplexere Effekte werden, beispielsweise für holographische Projektionen oder sogar für eine Kopie des Sandtisches aus dem Film "Black Panther".


Einheitspaket


Das vollständige Paket für dieses Tutorial kann auf Patreon heruntergeladen werden. Es enthält alle Elemente , die zum Spielen des beschriebenen Effekts erforderlich sind.

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


All Articles