Vorzeichenbehaftete Distanzfelder in 2D kombinieren

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{ //the material is completely non-transparent and is rendered at the same time as the other opaque geometry Tags{ "RenderType"="Opaque" "Queue"="Geometry"} Pass{ CGPROGRAM #include "UnityCG.cginc" #include "2D_SDF.cginc" #pragma vertex vert #pragma fragment frag struct appdata{ float4 vertex : POSITION; }; struct v2f{ float4 position : SV_POSITION; float4 worldPos : TEXCOORD0; }; v2f vert(appdata v){ v2f o; //calculate the position in clip space to render the object o.position = UnityObjectToClipPos(v.vertex); //calculate world position of vertex o.worldPos = mul(unity_ObjectToWorld, v.vertex); return o; } float scene(float2 position) { const float PI = 3.14159; float2 squarePosition = position; squarePosition = translate(squarePosition, float2(1, 0)); squarePosition = rotate(squarePosition, .125); float squareShape = rectangle(squarePosition, float2(2, 2)); float2 circlePosition = position; circlePosition = translate(circlePosition, float2(-1.5, 0)); float circleShape = circle(circlePosition, 2.5); float combination = combination_function(circleShape, squareShape); return combination; } float4 _InsideColor; float4 _OutsideColor; float _LineDistance; float _LineThickness; float _SubLines; float _SubLineThickness; fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = lerp(_InsideColor, _OutsideColor, step(0, dist)); float distanceChange = fwidth(dist) * 0.5; float majorLineDistance = abs(frac(dist / _LineDistance + 0.5) - 0.5) * _LineDistance; float majorLines = smoothstep(_LineThickness - distanceChange, _LineThickness + distanceChange, majorLineDistance); float distanceBetweenSubLines = _LineDistance / _SubLines; float subLineDistance = abs(frac(dist / distanceBetweenSubLines + 0.5) - 0.5) * distanceBetweenSubLines; float subLines = smoothstep(_SubLineThickness - distanceChange, _SubLineThickness + distanceChange, subLineDistance); return col * majorLines * subLines; } ENDCG } } FallBack "Standard" //fallback adds a shadow pass so we get shadows on other objects } 

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 //transforms float2 rotate(float2 samplePosition, float rotation){ const float PI = 3.14159; float angle = rotation * PI * 2 * -1; float sine, cosine; sincos(angle, sine, cosine); return float2(cosine * samplePosition.x + sine * samplePosition.y, cosine * samplePosition.y - sine * samplePosition.x); } float2 translate(float2 samplePosition, float2 offset){ //move samplepoint in the opposite direction that we want to move shapes in return samplePosition - offset; } float2 scale(float2 samplePosition, float scale){ return samplePosition / scale; } //shapes float circle(float2 samplePosition, float radius){ //get distance from center and grow it according to radius return length(samplePosition) - radius; } float rectangle(float2 samplePosition, float2 halfSize){ float2 componentWiseEdgeDistance = abs(samplePosition) - halfSize; float outsideDistance = length(max(componentWiseEdgeDistance, 0)); float insideDistance = min(max(componentWiseEdgeDistance.x, componentWiseEdgeDistance.y), 0); return outsideDistance + insideDistance; } #endif 

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.

 //in 2D_SDF.cginc include file float merge(float shape1, float shape2){ return min(shape1, shape2); } 

 //in scene function in shader float combination = merge(circleShape, squareShape); 




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.

 //in 2D_SDF.cginc include file float intersect(float shape1, float shape2){ return max(shape1, shape2); } 

 //in scene function in shader float combination = intersect(circleShape, squareShape); 


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.

 //in 2D_SDF.cginc include file float subtract(float base, float subtraction){ return intersect(base, -subtraction); } 

 //in scene function in shader float combination = subtract(squareShape, circleShape); 


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.

 //in 2D_SDF.cginc include file float interpolate(float shape1, float shape2, float amount){ return lerp(shape1, shape2, amount); } 

 //in scene function in shader float pulse = sin(_Time.y) * 0.5 + 0.5; float combination = interpolate(circleShape, pulse); 


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 //transforms float2 rotate(float2 samplePosition, float rotation){ const float PI = 3.14159; float angle = rotation * PI * 2 * -1; float sine, cosine; sincos(angle, sine, cosine); return float2(cosine * samplePosition.x + sine * samplePosition.y, cosine * samplePosition.y - sine * samplePosition.x); } float2 translate(float2 samplePosition, float2 offset){ //move samplepoint in the opposite direction that we want to move shapes in return samplePosition - offset; } float2 scale(float2 samplePosition, float scale){ return samplePosition / scale; } //combinations ///basic float merge(float shape1, float shape2){ return min(shape1, shape2); } float intersect(float shape1, float shape2){ return max(shape1, shape2); } float subtract(float base, float subtraction){ return intersect(base, -subtraction); } float interpolate(float shape1, float shape2, float amount){ return lerp(shape1, shape2, amount); } /// round 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; } 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; } float round_subtract(float base, float subtraction, float radius){ return round_intersect(base, -subtraction, radius); } ///champfer 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); } 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); } float champfer_subtract(float base, float subtraction, float champferSize){ return champfer_intersect(base, -subtraction, champferSize); } /// round border intersection float round_border(float shape1, float shape2, float radius){ float2 position = float2(shape1, shape2); float distanceFromBorderIntersection = length(position); return distanceFromBorderIntersection - radius; } 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); } //shapes float circle(float2 samplePosition, float radius){ //get distance from center and grow it according to radius return length(samplePosition) - radius; } float rectangle(float2 samplePosition, float2 halfSize){ float2 componentWiseEdgeDistance = abs(samplePosition) - halfSize; float outsideDistance = length(max(componentWiseEdgeDistance, 0)); float insideDistance = min(max(componentWiseEdgeDistance.x, componentWiseEdgeDistance.y), 0); return outsideDistance + insideDistance; } #endif 

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{ //the material is completely non-transparent and is rendered at the same time as the other opaque geometry Tags{ "RenderType"="Opaque" "Queue"="Geometry"} Pass{ CGPROGRAM #include "UnityCG.cginc" #include "2D_SDF.cginc" #pragma vertex vert #pragma fragment frag struct appdata{ float4 vertex : POSITION; }; struct v2f{ float4 position : SV_POSITION; float4 worldPos : TEXCOORD0; }; v2f vert(appdata v){ v2f o; //calculate the position in clip space to render the object o.position = UnityObjectToClipPos(v.vertex); //calculate world position of vertex o.worldPos = mul(unity_ObjectToWorld, v.vertex); return o; } float scene(float2 position) { const float PI = 3.14159; float2 squarePosition = position; squarePosition = translate(squarePosition, float2(1, 0)); squarePosition = rotate(squarePosition, .125); float squareShape = rectangle(squarePosition, float2(2, 2)); float2 circlePosition = position; circlePosition = translate(circlePosition, float2(-1.5, 0)); float circleShape = circle(circlePosition, 2.5); float combination = /* combination calculation here */; return combination; } float4 _InsideColor; float4 _OutsideColor; float _LineDistance; float _LineThickness; float _SubLines; float _SubLineThickness; fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = lerp(_InsideColor, _OutsideColor, step(0, dist)); float distanceChange = fwidth(dist) * 0.5; float majorLineDistance = abs(frac(dist / _LineDistance + 0.5) - 0.5) * _LineDistance; float majorLines = smoothstep(_LineThickness - distanceChange, _LineThickness + distanceChange, majorLineDistance); float distanceBetweenSubLines = _LineDistance / _SubLines; float subLineDistance = abs(frac(dist / distanceBetweenSubLines + 0.5) - 0.5) * distanceBetweenSubLines; float subLines = smoothstep(_SubLineThickness - distanceChange, _SubLineThickness + distanceChange, subLineDistance); return col * majorLines * subLines; } ENDCG } } FallBack "Standard" //fallback adds a shadow pass so we get shadows on other objects } 

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


All Articles