2D-Raummanipulation mit vorzeichenbehafteten Entfernungsfeldern

Wenn Sie mit Polygon-Assets arbeiten, können Sie jeweils nur ein Objekt zeichnen (wenn Sie Techniken wie Stapeln und Instanziieren nicht berücksichtigen). Wenn Sie jedoch Entfernungsfelder mit einem Vorzeichen (signierte Entfernungsfelder, SDF) verwenden, sind wir nicht darauf beschränkt. Wenn zwei Positionen dieselbe Koordinate haben, geben die vorzeichenbehafteten Abstandsfunktionen denselben Wert zurück, und in einer Berechnung können wir mehrere Zahlen erhalten. Um zu verstehen, wie der zum Generieren von vorzeichenbehafteten Abstandsfeldern verwendete Raum transformiert wird, empfehlen wir Ihnen, herauszufinden , wie Sie mithilfe der vorzeichenbehafteten Abstandsfunktionen Formen erstellen und SDF-Formen kombinieren .


Konfiguration


In diesem Tutorial ändere ich die Paarung zwischen dem Quadrat und dem Kreis, aber Sie können sie für jede andere Form verwenden. Dies ähnelt der Konfiguration für das vorherige Lernprogramm .

Hierbei ist es wichtig, dass der modifizierbare Teil vor der Verwendung von Positionen zum Generieren von Zahlen liegt.

Shader "Tutorial/036_SDF_Space_Manpulation/Type"{ 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) { // manipulate position with cool methods here! float2 squarePosition = position; squarePosition = translate(squarePosition, float2(2, 2)); squarePosition = rotate(squarePosition, .125); float squareShape = rectangle(squarePosition, float2(1, 1)); float2 circlePosition = position; circlePosition = translate(circlePosition, float2(1, 1.5)); float circleShape = circle(circlePosition, 1); float combination = merge(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" } 

Und die Funktion 2D_SDF.cginc, die sich im selben Ordner wie der Shader befindet, 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; } //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 


Wiederholung des Raumes


Spiegelreflexion


Eine der einfachsten Operationen ist das Spiegeln der Welt um eine Achse. Um es um die y-Achse zu spiegeln, nehmen wir den absoluten Wert der x-Komponente unserer Position. Somit sind die Koordinaten rechts und links von der Achse gleich. (-1, 1) wird zu (1, 1) und befindet sich innerhalb eines Kreises, wobei (1, 1) als Koordinatenursprung und mit einem Radius größer als 0 verwendet wird.

Meistens sieht der Code, der diese Funktion verwendet, ungefähr so ​​aus wie position = mirror(position); so können wir es ein bisschen vereinfachen. Wir werden das Positionsargument einfach als inout deklarieren. Wenn Sie also in das Argument schreiben, ändert sich auch die Variable, die wir an die Funktion übergeben. Der Rückgabewert kann dann vom Typ void sein, da wir den Rückgabewert immer noch nicht verwenden.

 //in 2D_SDF.cginc void mirror(inout float2 position){ position.x = abs(position.x); } 

 //in shader function mirror(position); 


Es ist schon ziemlich gut gelaufen, aber auf diese Weise erhalten wir nur eine Achse zum Spiegeln. Wir können die Funktion erweitern, indem wir den Raum drehen, wie wir es beim Drehen der Figuren getan haben. Zuerst müssen Sie den Raum drehen, dann spiegeln und dann zurückdrehen. Auf diese Weise können wir eine Spiegelung in Bezug auf jeden Winkel durchführen. Das Gleiche ist möglich, wenn Speicherplatz übertragen und nach dem Spiegeln eine umgekehrte Übertragung durchgeführt wird. (Wenn Sie beide Operationen ausführen, vergessen Sie vor dem Spiegeln nicht, zuerst die Übertragung durchzuführen und dann zu drehen. Danach geht die Runde zuerst.)

 //in shader function float rotation = _Time.y * 0.25; position = rotate(position, rotation); mirror(position); position = rotate(position, -rotation); 


Zellen


Wenn Sie wissen, wie die Rauschgenerierung funktioniert, dann verstehen Sie, dass wir bei der prozeduralen Generierung häufig die Position wiederholen und kleine Zellen erhalten, die im Wesentlichen gleich sind und sich nur in unbedeutenden Parametern unterscheiden. Dasselbe können wir für Entfernungsfelder tun.

Da die fmod Funktion (und die Verwendung von% zum Teilen mit dem Rest) den Rest und nicht die Definition des Rests ergibt, müssen wir einen Trick verwenden. Zunächst nehmen wir den Rest der Ganzzahldivision durch die Funktion fmod. Für positive Zahlen ist dies genau das, was wir brauchen, und für negative Zahlen ist dies das Ergebnis, das wir abzüglich der Periode benötigen. Sie können dies beheben, indem Sie einen Punkt hinzufügen und den Rest der Division erneut übernehmen. Das Hinzufügen einer Periode ergibt das gewünschte Ergebnis für negative Eingabewerte, und für positive Eingabewerte ist der Wert eine Periode höher. Der zweite Rest der Division wird mit den Werten für negative Eingabewerte nichts anfangen, da sie bereits im Bereich von 0 bis zur Periode liegen und für positive Eingabewerte im Wesentlichen eine Periode subtrahiert wird.

 //in 2D_SDF.cginc void cells(inout float2 position, float2 period){ position = fmod(position, period); //negative positions lead to negative modulo position += period; //negative positions now have correct cell coordinates, positive input positions too high position = fmod(position, period); //second mod doesn't change values between 0 and period, but brings down values that are above period. } 

 //in shader function cells(position, float2(3, 3)); 


Das Problem mit den Zellen ist, dass wir die Kontinuität verlieren, für die wir Distanzfelder lieben. Dies ist nicht schlecht, wenn sich die Formen nur in der Mitte der Zellen befinden. In dem oben gezeigten Beispiel kann dies jedoch zu erheblichen Artefakten führen, die vermieden werden sollten, wenn Distanzfelder für eine Vielzahl von Aufgaben verwendet werden, bei denen Distanzfelder normalerweise angewendet werden können.

Es gibt eine Lösung, die nicht in jedem Fall funktioniert, aber wenn sie funktioniert, ist es wunderbar: jede andere Zelle zu spiegeln. Dafür benötigen wir einen Pixelzellenindex, aber wir haben immer noch keinen Rückgabewert in der Funktion, sodass wir ihn einfach verwenden können, um den Zellenindex zurückzugeben.

Um den Zellenindex zu berechnen, teilen wir die Position durch die Periode. Somit ist 0-1 die erste Zelle, 1-2 die zweite und so weiter ... und wir können sie leicht diskretisieren. Um den Index der Zelle zu erhalten, runden wir den Wert einfach ab und geben das Ergebnis zurück. Wichtig ist, dass wir den Index der Zelle berechnen, bevor wir uns mit dem Rest teilen, um die Zellen zu wiederholen. Andernfalls würden wir überall den Index 0 erhalten, da die Position den Zeitraum nicht überschreiten kann.

 //in 2D_SDF.cginc float2 cells(inout float2 position, float2 period){ position = fmod(position, period); //negative positions lead to negative modulo position += period; //negative positions now have correct cell coordinates, positive input positions too high position = fmod(position, period); //second mod doesn't change values between 0 and period, but brings down values that are above period. float2 cellIndex = position / period; cellIndex = floor(cellIndex); return cellIndex; } 

Mit diesen Informationen können wir Zellen umdrehen. Um zu verstehen, ob umgedreht werden soll oder nicht, teilen wir den Zellenindex Modulo 2. Das Ergebnis dieser Operation ist abwechselnd 0 und 1 oder -1 pro zweite Zelle. Um die Änderung dauerhafter zu machen, nehmen wir den absoluten Wert und erhalten einen Wert, der zwischen 0 und 1 wechselt.

Um diesen Wert zum Umschalten zwischen einer normalen und einer umgedrehten Position zu verwenden, benötigen wir eine Funktion, die nichts für den Wert 0 tut und die Position von der Periode subtrahiert, in der das Umdrehen 1 ist. Das heißt, wir führen eine lineare Interpolation von der normalen zur umgedrehten Position unter Verwendung der Umkehrvariablen durch . Da die Flip-Variable ein 2d-Vektor ist, werden ihre Komponenten einzeln gespiegelt.

 //in shader function float2 period = 3; float2 cell = cells(position, period); float2 flip = abs(fmod(cell, 2)); position = lerp(position, period - position, flip); 


Radialzellen


Ein weiteres großartiges Merkmal ist die Wiederholung des Raums in einem radialen Muster.

Um diesen Effekt zu erzielen, berechnen wir zunächst die radiale Position. Dazu codieren wir den Winkel relativ zum Mittelpunkt der x-Achse und den Abstand vom Mittelpunkt entlang der y-Achse.

 float2 radialPosition = float2(atan2(position.x, position.y), length(position)); 

Dann wiederholen wir die Ecke. Da die Übertragung der Anzahl der Wiederholungen viel einfacher ist als der Winkel jedes Stücks, berechnen wir zunächst die Größe jedes Stücks. Der gesamte Kreis ist 2 * pi. Um den richtigen Teil zu erhalten, teilen wir 2 * pi durch die Zellengröße.

 const float PI = 3.14159; float cellSize = PI * 2 / cells; 

Mit diesen Informationen können wir die x-Komponente der radialen Position jeder cellSize-Einheit wiederholen. Wir führen Wiederholungen durch Division mit dem Rest durch, daher erhalten wir nach wie vor Probleme mit negativen Zahlen, die mit Hilfe von zwei Funktionen der Division mit dem Rest beseitigt werden können.

 radialPosition.x = fmod(fmod(radialPosition.x, cellSize) + cellSize, cellSize); 

Dann müssen Sie die neue Position wieder auf die üblichen xy-Koordinaten verschieben. Hier verwenden wir die Sincos-Funktion mit der x-Komponente der radialen Position als Winkel, um den Sinus in die x-Koordinate der Position und den Cosinus in die y-Koordinate zu schreiben. Mit diesem Schritt erhalten wir eine normalisierte Position. Um die richtige Richtung von der Mitte zu erhalten, müssen Sie sie mit der Komponente y der radialen Position multiplizieren, dh der Länge.

 //in 2D_SDF.cginc void radial_cells(inout float2 position, float cells){ const float PI = 3.14159; float cellSize = PI * 2 / cells; float2 radialPosition = float2(atan2(position.x, position.y), length(position)); radialPosition.x = fmod(fmod(radialPosition.x, cellSize) + cellSize, cellSize); sincos(radialPosition.x, position.x, position.y); position = position * radialPosition.y; } 

 //in shader function float2 period = 6; radial_cells(position, period, false); 


Dann können wir auch den Zellindex und die Spiegelung hinzufügen, wie wir es bei normalen Zellen getan haben.

Es ist notwendig, den Zellenindex nach der Berechnung der radialen Position zu berechnen, aber bevor der Rest von der Teilung erhalten wird. Wir erhalten es, indem wir die x-Komponente der radialen Position teilen und das Ergebnis abrunden. In diesem Fall kann der Index auch negativ sein, und dies ist ein Problem, wenn die Anzahl der Zellen ungerade ist. Zum Beispiel erhalten wir mit 3 Zellen 1 Zelle mit einem Index von 0, 1 Zelle mit einem Index von -1 und 2 Halbzellen mit den Indizes 1 und -2. Um dieses Problem zu umgehen, addieren wir die Anzahl der Zellen zu der Variablen, die die Variable abgerundet hat, und dividieren sie durch die Größe der Zelle mit dem Rest.

 //in 2D_SDF.cginc float cellIndex = fmod(floor(radialPosition.x / cellSize) + cells, cells); //at the end of the function: return cellIndex; 

Um dies widerzuspiegeln, müssen die Koordinaten im Bogenmaß angegeben werden. Um eine Neuberechnung der Radialkoordinaten außerhalb der Funktion zu vermeiden, fügen wir ihr eine Option mit dem Argument bool hinzu. Normalerweise ist in Shadern eine Verzweigung (wenn Konstrukte) nicht erwünscht, aber in diesem Fall gehen alle Pixel auf dem Bildschirm denselben Pfad, sodass dies normal ist.

Die Spiegelung sollte erfolgen, nachdem die Radialkoordinate geloopt wurde, aber bevor sie wieder in ihre normale Position konvertiert wurde. Wir finden heraus, ob wir die aktuelle Zelle umdrehen müssen, indem wir den Zellenindex durch 2 mit dem Rest teilen. Normalerweise sollte dies Nullen und Einsen ergeben, aber in meinem Fall erscheinen mehrere Zweien, was seltsam ist, und dennoch können wir damit umgehen. Um Zweien zu eliminieren, subtrahieren wir einfach 1 von der Flip-Variablen und nehmen dann den absoluten Wert. Somit werden Nullen und Zweien zu Einsen, und Einheiten werden nach Bedarf zu Nullen, und zwar nur in umgekehrter Reihenfolge.

Da Nullen und Einsen in der falschen Reihenfolge sind, führen wir eine lineare Interpolation von der verkehrten Version zur verkehrten Version durch und nicht wie zuvor umgekehrt. Um die Koordinate umzudrehen, subtrahieren wir einfach die Position von der Zellengröße.

 //in 2D_SDF.cginc float radial_cells(inout float2 position, float cells, bool mirrorEverySecondCell = false){ const float PI = 3.14159; float cellSize = PI * 2 / cells; float2 radialPosition = float2(atan2(position.x, position.y), length(position)); float cellIndex = fmod(floor(radialPosition.x / cellSize) + cells, cells); radialPosition.x = fmod(fmod(radialPosition.x, cellSize) + cellSize, cellSize); if(mirrorEverySecondCell){ float flip = fmod(cellIndex, 2); flip = abs(flip-1); radialPosition.x = lerp(cellSize - radialPosition.x, radialPosition.x, flip); } sincos(radialPosition.x, position.x, position.y); position = position * radialPosition.y; return cellIndex; } 

 //in shader function float2 period = 6; radial_cells(position, period, true); 


Schwankender Raum


Aber um den Raum zu ändern, ist es nicht notwendig, ihn zu wiederholen. Im Tutorial zu den Grundlagen haben wir es beispielsweise gedreht, verschoben und skaliert. Sie können auch Folgendes tun: Bewegen Sie jede Achse mit einer Sinuswelle auf der Basis der anderen. Dadurch werden die Abstände der vorzeichenbehafteten Abstandsfunktion weniger genau, aber bis sie zu stark schwingen, ist alles in Ordnung.

Zuerst berechnen wir die Größe der Positionsänderung, indem wir die x- und y-Komponenten umdrehen und sie dann mit der Wobbelfrequenz multiplizieren. Dann nehmen wir den Sinus von diesem Wert und multiplizieren ihn mit der Menge an Wackeln, die wir hinzufügen möchten. Danach addieren wir einfach diesen Wobbelfaktor zur Position und wenden das Ergebnis erneut auf die Position an.

 //in 2D_SDF.cginc void wobble(inout float2 position, float2 frequency, float2 amount){ float2 wobble = sin(position.yx * frequency) * amount; position = position + wobble; } 

 //in shader function wobble(position, 5, .05); 


Wir können dieses Winken auch animieren, indem wir seine Position ändern, das Winken an der versetzten Position anwenden und den Raum zurückgeben. Damit die Gleitkommazahlen nicht zu groß werden, dividiere ich mit dem Rest pi * 2 durch die Wobbelfrequenz, dies korreliert mit dem Wobble (die Sinuskurve wiederholt alle pi * 2-Einheiten), sodass Sprünge und zu große Offsets vermieden werden.

 //in shader function const float PI = 3.14159; float frequency = 5; float offset = _Time.y; offset = fmod(offset, PI * 2 / frequency); position = translate(position, offset); wobble(position, 5, .05); position = translate(position, -offset); 


Quellcode


2D SDF 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); } // space repetition void mirror(inout float2 position){ position.x = abs(position.x); } float2 cells(inout float2 position, float2 period){ //find cell index float2 cellIndex = position / period; cellIndex = floor(cellIndex); //negative positions lead to negative modulo position = fmod(position, period); //negative positions now have correct cell coordinates, positive input positions too high position += period; //second mod doesn't change values between 0 and period, but brings down values that are above period. position = fmod(position, period); return cellIndex; } float radial_cells(inout float2 position, float cells, bool mirrorEverySecondCell = false){ const float PI = 3.14159; float cellSize = PI * 2 / cells; float2 radialPosition = float2(atan2(position.x, position.y), length(position)); float cellIndex = fmod(floor(radialPosition.x / cellSize) + cells, cells); radialPosition.x = fmod(fmod(radialPosition.x, cellSize) + cellSize, cellSize); if(mirrorEverySecondCell){ float flip = fmod(cellIndex, 2); flip = abs(flip-1); radialPosition.x = lerp(cellSize - radialPosition.x, radialPosition.x, flip); } sincos(radialPosition.x, position.x, position.y); position = position * radialPosition.y; return cellIndex; } void wobble(inout float2 position, float2 frequency, float2 amount){ float2 wobble = sin(position.yx * frequency) * amount; position = position + wobble; } //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 

Grundlegender Demo-Shader



 Shader "Tutorial/036_SDF_Space_Manpulation/Mirror"{ 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) { // modify position here! float2 squarePosition = position; squarePosition = translate(squarePosition, float2(2, 2)); squarePosition = rotate(squarePosition, .125); float squareShape = rectangle(squarePosition, float2(1, 1)); float2 circlePosition = position; circlePosition = translate(circlePosition, float2(1, 1.5)); float circleShape = circle(circlePosition, 1); float combination = merge(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 } 

Jetzt kennen Sie alle Grundlagen der Vorzeichenentfernungsfunktionen, an die ich mich erinnern konnte. Im nächsten Tutorial werde ich versuchen, etwas Interessantes mit ihnen zu machen.

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


All Articles