Im
vorherigen Tutorial haben wir gelernt, wie einfache Formen mit signierten Abstandsfunktionen erstellt und verschoben werden. In diesem Artikel erfahren Sie, wie Sie mehrere Formen kombinieren, um komplexere Distanzfelder zu erstellen. Die meisten der hier beschriebenen Techniken habe ich aus der glsl-Zeichenbibliothek der Distanzfunktionen gelernt, die
hier zu finden
ist . Es gibt auch verschiedene Möglichkeiten, Formen zu kombinieren, die ich hier nicht diskutiere.
Vorbereitung
Zur Visualisierung von vorzeichenbehafteten Entfernungsfeldern (vorzeichenbehaftete Entfernungsfelder, SDF) verwenden wir eine einfache Konfiguration und wenden dann die Operatoren darauf an. Zum Anzeigen von Entfernungsfeldern werden die Entfernungslinien aus dem ersten Lernprogramm visualisiert. Der Einfachheit halber legen wir alle Parameter außer den Visualisierungsparametern im Code fest. Sie können jedoch jeden Wert durch eine Eigenschaft ersetzen, um sie anpassbar zu machen.
Der Haupt-Shader, mit dem wir beginnen werden, sieht folgendermaßen aus:
Shader "Tutorial/035_2D_SDF_Combinations/Champfer Union"{ Properties{ _InsideColor("Inside Color", Color) = (.5, 0, 0, 1) _OutsideColor("Outside Color", Color) = (0, .5, 0, 1) _LineDistance("Mayor Line Distance", Range(0, 2)) = 1 _LineThickness("Mayor Line Thickness", Range(0, 0.1)) = 0.05 [IntRange]_SubLines("Lines between major lines", Range(1, 10)) = 4 _SubLineThickness("Thickness of inbetween lines", Range(0, 0.05)) = 0.01 } SubShader{
Und die Funktion 2D_SDF.cginc im selben Ordner wie der Shader, den wir erweitern werden, sieht zunächst so aus:
#ifndef SDF_2D #define SDF_2D
Einfache Kombinationen
Wir beginnen mit ein paar einfachen Möglichkeiten, zwei Formen zu kombinieren, um eine große Form, Konjugationen, Schnittpunkte und Subtraktionen zu erstellen, sowie eine Möglichkeit, eine Form in eine andere umzuwandeln.
Pairing
Der einfachste Operator ist das Pairing. Damit können wir die beiden Figuren zusammenfügen und den Abstand mit dem Vorzeichen der verbundenen Figur ermitteln. Wenn wir einen Abstand mit dem Vorzeichen zweier Figuren haben, können wir diese kombinieren, indem wir die kleinere der beiden mit der
min
Funktion nehmen.
Aufgrund der Wahl des kleineren der beiden Werte liegt die endgültige Zahl unter 0 (sichtbar), wobei eine der beiden eingehenden Figuren einen Abstand zur Kante von weniger als 0 aufweist. Gleiches gilt für alle anderen Abstandswerte, die eine Kombination aus zwei Zahlen zeigen.
Hier werde ich die Funktion zum Erstellen der Konjugation "Zusammenführen" nennen, teils weil wir sie zusammenführen, teils weil das Schlüsselwort union in hlsl reserviert ist und es daher nicht als Name der Funktion verwendet werden kann.
Schnittpunkt
Eine andere übliche Methode zum Verbinden von Formen besteht darin, Bereiche zu verwenden, in denen sich zwei Formen überlappen. Dazu nehmen wir den Maximalwert der Abstände der beiden Figuren, die wir kombinieren möchten. Wenn Sie den größten der beiden Werte verwenden, erhalten Sie einen Wert größer als 0 (außerhalb der Abbildung), wenn einer der Abstände zu zwei Zahlen außerhalb der Abbildung liegt und andere Abstände ebenfalls auf die gleiche Weise ausgerichtet sind.
Subtraktion
Oft möchten wir jedoch nicht beide Formen auf die gleiche Weise verarbeiten und müssen die andere von einer Form subtrahieren. Dies ist ziemlich einfach, indem Sie zwischen der Form, die wir ändern möchten, und allen Formen außer der Form, die wir subtrahieren möchten, schneiden. Wir erhalten die entgegengesetzten Werte für den inneren und äußeren Teil der Figur, indem wir den Abstand mit dem Vorzeichen umkehren. Was außerhalb der Figur 1 Einheit war, ist jetzt 1 Einheit innerhalb.
Interpolation
Eine nicht offensichtliche Möglichkeit, zwei Figuren zu kombinieren, besteht darin, zwischen ihnen zu interpolieren. In gewissem Umfang ist dies auch für Polygonnetze mit Mischformen möglich, jedoch viel eingeschränkter als bei vorzeichenbehafteten Abstandsfeldern. Durch einfache Interpolation zwischen den Abständen zweier Figuren erreichen wir einen reibungslosen Fluss von einer in die andere. Für die Interpolation können Sie einfach die
lerp
Methode verwenden.
Andere Verbindungen
Nachdem wir einfache Verbindungen erhalten haben, haben wir bereits alles Notwendige für eine einfache Kombination von Figuren, aber die erstaunliche Eigenschaft von Distanzzeichenfeldern ist, dass wir uns nicht darauf beschränken können. Es gibt viele verschiedene Möglichkeiten, Figuren zu kombinieren und interessante Aktionen an den Stellen ihrer Verbindung durchzuführen. Hier werde ich nur einige dieser Techniken noch einmal erläutern, aber Sie können viele andere in der Bibliothek
http://mercury.sexy/hg_sdf finden (schreiben Sie mir, wenn Sie andere nützliche SDF-Bibliotheken kennen).
Rundung
Wir können die Oberfläche zweier kombinierter Figuren als x-Achse und y-Achse der Position im Koordinatensystem interpretieren und dann den Abstand zum Koordinatenursprung dieser Position berechnen. Wenn wir dies tun, erhalten wir eine sehr seltsame Zahl, aber wenn wir die Achse auf Werte unter 0 beschränken, erhalten wir etwas, das der glatten Konjugation der inneren Abstände zweier Figuren ähnelt.
float round_merge(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1, shape2); intersectionSpace = min(intersectionSpace, 0); return length(intersectionSpace); }
Das ist schön, aber wir können dies nicht verwenden, um die Linie zu ändern, in der der Abstand 0 ist. Daher ist diese Operation nicht wertvoller als gewöhnliches Pairing. Aber bevor wir die beiden Figuren verbinden, können wir sie ein wenig erhöhen. Genauso wie wir einen Kreis erstellt haben, um eine Figur zu vergrößern, subtrahieren wir sie von ihrer Entfernung, um eine Linie weiter nach außen zu drücken, in der die Entfernung mit einem Zeichen 0 ist.
float radius = max(sin(_Time.y * 5) * 0.5 + 0.4, 0); float combination = round_intersect(squareShape, circleShape, radius);
float round_merge(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1 - radius, shape2 - radius); intersectionSpace = min(intersectionSpace, 0); return length(intersectionSpace); }
Es vergrößert nur die Figur und sorgt für reibungslose Übergänge im Inneren, aber wir möchten die Figuren nicht vergrößern, wir brauchen nur einen glatten Übergang. Die Lösung besteht darin, den Radius nach Berechnung der Länge erneut zu subtrahieren. Die meisten Teile sehen genauso aus wie zuvor, mit Ausnahme des Übergangs zwischen den Figuren, der entsprechend dem Radius schön geglättet ist. Wir werden den äußeren Teil der Figur vorerst ignorieren.
float round_merge(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1 - radius, shape2 - radius); intersectionSpace = min(intersectionSpace, 0); return length(intersectionSpace) - radius; }

Die letzte Stufe ist die Korrektur des äußeren Teils der Figur. Während die Innenseite der Figur grün ist, verwenden wir diese Farbe für die Außenseite. Der erste Schritt besteht darin, die äußeren und inneren Teile auszutauschen, indem Sie einfach ihren Abstand mit einem Zeichen umkehren. Dann ersetzen wir den Teil, bei dem der Radius abgezogen wird. Zuerst ändern wir es von Subtraktion zu Addition. Dies ist notwendig, da wir vor dem Kombinieren mit dem Radius den Abstand des Vektors gezeichnet haben und dementsprechend die verwendete mathematische Operation umkehren müssen. Dann ersetzen wir den Radius durch den üblichen Partner, wodurch wir die korrekten Werte außerhalb der Figur erhalten, jedoch nicht in der Nähe der Kanten und innerhalb der Figur. Um dies zu vermeiden, nehmen wir ein Maximum zwischen dem Wert und dem Radius und erhalten so einen positiven Wert der korrekten Werte außerhalb der Figur sowie die Addition des Radius, den wir innerhalb der Figur benötigen.
float round_merge(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1 - radius, shape2 - radius); intersectionSpace = min(intersectionSpace, 0); float insideDistance = -length(intersectionSpace); float simpleUnion = merge(shape1, shape2); float outsideDistance = max(simpleUnion, radius); return insideDistance + outsideDistance; }
Um einen Schnittpunkt zu erstellen, müssen wir das Gegenteil tun: Reduzieren Sie die Zahlen um den Radius, stellen Sie sicher, dass alle Komponenten des Vektors größer als 0 sind, nehmen Sie die Länge und ändern Sie nicht das Vorzeichen. Also werden wir den äußeren Teil der Figur erstellen. Um den inneren Teil zu erstellen, nehmen wir den üblichen Schnittpunkt und stellen sicher, dass er nicht kleiner als der Radius ist. Dann addieren wir nach wie vor die internen und externen Werte.
float round_intersect(float shape1, float shape2, float radius){ float2 intersectionSpace = float2(shape1 + radius, shape2 + radius); intersectionSpace = max(intersectionSpace, 0); float outsideDistance = length(intersectionSpace); float simpleIntersection = intersect(shape1, shape2); float insideDistance = min(simpleIntersection, -radius); return outsideDistance + insideDistance; }
Und als letzter Punkt kann die Subtraktion wieder als Schnittpunkt zwischen der Basisfigur und allem außer der Figur, die wir subtrahieren, beschrieben werden.
float round_subtract(float base, float subtraction, float radius){ round_intersect(base, -subtraction, radius); }
Hier und insbesondere beim Subtrahieren können Sie Artefakte sehen, die sich aus der Annahme ergeben, dass wir zwei Zahlen als Koordinaten verwenden können, aber für die meisten Anwendungen sind die Entfernungsfelder immer noch gut genug.
Abschrägung
Wir können den Übergang auch mähen, um ihm einen Winkel wie eine Fase zu geben. Um diesen Effekt zu erzielen, erstellen wir zunächst eine neue Form, indem wir die vorhandenen zwei hinzufügen. Wenn wir wieder annehmen, dass der Punkt, an dem sich die beiden Figuren treffen, orthogonal ist, ergibt diese Operation eine diagonale Linie, die durch den Treffpunkt der beiden Flächen verläuft.
Da wir einfach die beiden Komponenten hinzugefügt haben, hat der Abstand mit dem Vorzeichen dieser neuen Linie die falsche Skala, aber wir können ihn korrigieren, indem wir ihn durch die Diagonale eines Einheitsquadrats dividieren, dh die Quadratwurzel von 2. Division durch die Wurzel von 2 ist dieselbe wie Multiplizieren Sie mit der Quadratwurzel von 0,5, und wir können diesen Wert einfach in den Code schreiben, um nicht jedes Mal dieselbe Wurzel zu berechnen.
Nachdem wir nun eine Form haben, die die Form einer gewünschten Abschrägung hat, werden wir sie so erweitern, dass sich die Abschrägung über die Grenzen der Figur hinaus erstreckt. Auf die gleiche Weise wie zuvor subtrahieren wir den Wert, den wir zur Erhöhung der Zahl benötigen. Dann kombinieren wir die Abschrägungsform mit der Ausgabe der üblichen Zusammenführung, was zu einem abgeschrägten Übergang führt.
float champferSize = sin(_Time.y * 5) * 0.3 + 0.3; float combination = champfer_merge(circleShape, squareShape, champferSize);
float champfer_merge(float shape1, float shape2, float champferSize){ const float SQRT_05 = 0.70710678118; float simpleMerge = merge(shape1, shape2); float champfer = (shape1 + shape2) * SQRT_05; champfer = champfer - champferSize; return merge(simpleMerge, champfer); }
Um eine gekreuzte Abschrägung zu erhalten, fügen wir wie zuvor zwei Zahlen hinzu, aber dann reduzieren wir die Zahl durch Hinzufügen der Abschrägung und schneiden sie mit der üblichen gekreuzten Zahl.
float champfer_intersect(float shape1, float shape2, float champferSize){ const float SQRT_05 = 0.70710678118; float simpleIntersect = intersect(shape1, shape2); float champfer = (shape1 + shape2) * SQRT_05; champfer = champfer + champferSize; return intersect(simpleIntersect, champfer); }
Und ähnlich wie bei den vorherigen Subtraktionen können wir hier auch den Schnittpunkt mit der invertierten zweiten Zahl durchführen.
float champfer_subtract(float base, float subtraction, float champferSize){ return champfer_intersect(base, -subtraction, champferSize); }
Abgerundete Kreuzung
Bisher haben wir nur Boolesche Operatoren verwendet (außer für die Interpolation). Wir können die Formen jedoch auch auf andere Weise kombinieren, indem wir beispielsweise an den Stellen, an denen sich die Ränder der beiden Formen überlappen, neue abgerundete Formen erstellen.
Dazu müssen wir die beiden Figuren erneut als x-Achse und y-Achse des Punktes interpretieren. Dann berechnen wir einfach den Abstand dieses Punktes zum Ursprung. Wenn sich die Grenzen der beiden Figuren überschneiden, beträgt der Abstand zu beiden Figuren 0, was einen Abstand von 0 zum Ursprungspunkt unseres imaginären Koordinatensystems ergibt. Wenn wir dann einen Abstand zum Ursprung haben, können wir damit die gleichen Operationen wie für Kreise ausführen und den Radius subtrahieren.
float round_border(float shape1, float shape2, float radius){ float2 position = float2(shape1, shape2); float distanceFromBorderIntersection = length(position); return distanceFromBorderIntersection - radius; }
Randkerbe
Das Letzte, was ich erklären werde, ist die Möglichkeit, eine Kerbe in einer Form an der Randposition einer anderen Form zu erstellen.
Wir beginnen mit der Berechnung der Form der Kreisgrenze. Dies kann erreicht werden, indem der absolute Wert des Abstands der ersten Figur erhalten wird, während sowohl der innere als auch der äußere Teil als der innere Teil der Figur betrachtet werden, der Rand jedoch immer noch den Wert 0 hat. Wenn wir diese Zahl durch Subtrahieren der Kerbbreite erhöhen, erhalten wir die Figur entlang des Randes der vorherigen Figur .
float depth = max(sin(_Time.y * 5) * 0.5 + 0.4, 0); float combination = groove_border(squareShape, circleShape, .3, depth);
float groove_border(float base, float groove, float width, float depth){ float circleBorder = abs(groove) - width; return circleBorder; }
Jetzt muss der Rand des Kreises nur um den von uns angegebenen Wert tiefer gehen. Dazu subtrahieren wir eine reduzierte Version der Basisfigur. Das Ausmaß der Verringerung der Grundform ist die Tiefe der Kerbe.
float groove_border(float base, float groove, float width, float depth){ float circleBorder = abs(groove) - width; float grooveShape = subtract(circleBorder, base + depth); return grooveShape; }
Der letzte Schritt besteht darin, die Kerbe von der Grundform zu subtrahieren und das Ergebnis zurückzugeben.
float groove_border(float base, float groove, float width, float depth){ float circleBorder = abs(groove) - width; float grooveShape = subtract(circleBorder, base + depth); return subtract(base, grooveShape); }
Quellcode
Die Bibliothek
#ifndef SDF_2D #define SDF_2D
Shader-Basis
Shader "Tutorial/035_2D_SDF_Combinations/Round"{ Properties{ _InsideColor("Inside Color", Color) = (.5, 0, 0, 1) _OutsideColor("Outside Color", Color) = (0, .5, 0, 1) _LineDistance("Mayor Line Distance", Range(0, 2)) = 1 _LineThickness("Mayor Line Thickness", Range(0, 0.1)) = 0.05 [IntRange]_SubLines("Lines between major lines", Range(1, 10)) = 4 _SubLineThickness("Thickness of inbetween lines", Range(0, 0.05)) = 0.01 } SubShader{