Manipulación espacial 2D con campos de distancia firmados

Cuando trabaje con activos de polígono, puede dibujar solo un objeto a la vez (si no tiene en cuenta técnicas como el procesamiento por lotes y la creación de instancias), pero si usa campos de distancia con un signo (campos de distancia firmados, SDF), entonces no estamos limitados a esto. Si dos posiciones tienen la misma coordenada, las funciones de distancia con signo devolverán el mismo valor, y en un cálculo podemos obtener varias cifras. Para comprender cómo transformar el espacio utilizado para generar campos de distancia con signo, le recomiendo que descubra cómo crear formas utilizando las funciones de distancia con signo y combinar formas sdf .


Configuracion


Para este tutorial, modifico el emparejamiento entre el cuadrado y el círculo, pero puede usarlo para cualquier otra forma. Esto es similar a la configuración del tutorial anterior .

Aquí es importante que la parte modificable sea antes de usar posiciones para generar figuras.

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" } 

Y la función 2D_SDF.cginc ubicada en la misma carpeta con el sombreador, que expandiremos, al principio se ve así:

 #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 


Repetición del espacio


Reflejo del espejo


Una de las operaciones más simples es reflejar el mundo sobre un eje. Para reflejarlo alrededor del eje y, tomamos el valor absoluto del componente x de nuestra posición. Por lo tanto, las coordenadas a la derecha e izquierda del eje serán las mismas. (-1, 1) convierte en (1, 1) y resulta estar dentro de un círculo usando (1, 1) como origen de coordenadas y con un radio mayor que 0.

Muy a menudo, el código que usa esta función se verá como position = mirror(position); para que podamos simplificarlo un poco. Simplemente declararemos el argumento de posición como inout. Por lo tanto, al escribir en el argumento, también cambiará la variable que pasamos a la función. El valor de retorno puede ser de tipo nulo, porque todavía no usamos el valor de retorno.

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

 //in shader function mirror(position); 


Resultó bastante bien, pero de esta manera solo obtenemos un eje para la duplicación. Podemos expandir la función girando el espacio como lo hicimos al girar las figuras. Primero debe rotar el espacio, luego reflejarlo y luego volverlo. De esta forma podemos realizar reflejos con respecto a cualquier ángulo. Lo mismo es posible cuando se transfiere espacio y se realiza una transferencia inversa después de la duplicación. (Si realiza ambas operaciones, antes de duplicar, no olvide realizar primero la transferencia, y luego gire, después de lo cual el turno va primero).

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


Células


Si sabe cómo funciona la generación de ruido , entonces comprende que para la generación de procedimientos a menudo repetimos la posición y obtenemos celdas pequeñas que son esencialmente las mismas, que difieren solo en parámetros insignificantes. Podemos hacer lo mismo para los campos de distancia.

Dado que la función fmod (además de usar% para dividir con el resto) nos da el resto, no la definición del resto, tendremos que usar un truco. Primero, tomamos el resto de la división de enteros por la función fmod. Para números positivos, esto es exactamente lo que necesitamos, y para números negativos, este es el resultado que necesitamos menos el período. Puede arreglar esto agregando un punto y nuevamente tomando el resto de la división. Agregar un período dará el resultado deseado para valores de entrada negativos, y para valores de entrada positivos, el valor es un período más alto. El segundo resto de la división no hará nada con los valores para los valores de entrada negativos, porque ya están en el rango de 0 al período, y para los valores de entrada positivos, esencialmente restamos un período.

 //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)); 


El problema con las células es que perdemos la continuidad por la que amamos los campos de distancia. Esto no es malo si las formas están solo en el medio de las celdas, pero en el ejemplo que se muestra arriba esto puede conducir a artefactos significativos que deben evitarse cuando los campos de distancia se usan para muchas tareas en las que los campos de distancia generalmente se pueden aplicar.

Hay una solución que no funciona en todos los casos, pero cuando funciona, es maravilloso: reflejar todas las demás celdas. Para hacer esto, necesitamos un índice de celda de píxel, pero aún no tenemos un valor de retorno en la función, por lo que podemos usarlo para devolver el índice de celda.

Para calcular el índice de celda, dividimos la posición por el período. Por lo tanto, 0-1 es la primera celda, 1-2 es la segunda, y así sucesivamente ... y podemos discretizar fácilmente. Para obtener el índice de la celda, simplemente redondeamos el valor hacia abajo y devolvemos el resultado. Lo importante es que calculemos el índice de la celda antes de dividirla con el resto para repetir las celdas; de lo contrario, obtendríamos el índice 0 en todas partes, porque la posición no puede exceder el período.

 //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; } 

Con esta información, podemos voltear las celdas. Para entender si voltear o no, dividimos el módulo de índice de celda 2. El resultado de esta operación es alternativamente 0 y 1 o -1 por cada segunda celda. Para que el cambio sea más permanente, tomamos el valor absoluto y obtenemos un valor que cambia entre 0 y 1.

Para usar este valor para cambiar entre una posición normal y otra invertida, necesitamos una función que no haga nada por el valor 0, y reste la posición del período en el que la inversión es 1. Es decir, realizamos una interpolación lineal de la posición normal a la invertida usando la variable flip . Dado que la variable flip es un vector 2d, sus componentes se voltean individualmente.

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


Células radiales


Otra gran característica es la repetición del espacio en un patrón radial.

Para obtener este efecto, primero calculamos la posición radial. Para hacer esto, codificamos el ángulo relativo al centro del eje xy la distancia desde el centro a lo largo del eje y.

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

Luego repetimos la esquina. Dado que transmitir el número de repeticiones es mucho más fácil que el ángulo de cada pieza, primero calculamos el tamaño de cada pieza. Todo el círculo es 2 * pi, por lo que para obtener la parte correcta, dividimos 2 * pi por el tamaño de la celda.

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

Con esta información, podemos repetir el componente x de la posición radial en cada unidad cellSize. Realizamos la repetición por división con el resto, por lo tanto, como antes, tenemos problemas con los números negativos, que pueden eliminarse con la ayuda de dos funciones de división con el resto.

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

Luego debe mover la nueva posición a las coordenadas xy habituales. Aquí usamos la función sincos con el componente x de la posición radial como ángulo para escribir el seno en la coordenada x de la posición y el coseno en la coordenada y. Con este paso conseguimos una posición normalizada. Para obtener la dirección correcta desde el centro, debe multiplicarla por el componente y de la posición radial, lo que significa la longitud.

 //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); 


Luego también podemos agregar el índice de la celda y la duplicación, como lo hicimos con las celdas regulares.

Es necesario calcular el índice de la celda después de calcular la posición radial, pero antes de recibir el resto de la división. Lo obtenemos dividiendo el componente x de la posición radial y redondeando el resultado hacia abajo. En este caso, el índice también puede ser negativo, y esto es un problema si el número de celdas es impar. Por ejemplo, con 3 celdas, obtenemos 1 celda con un índice de 0, 1 celda con un índice de -1 y 2 medias celdas con los índices 1 y -2. Para solucionar este problema, agregamos el número de celdas a la variable redondeada hacia abajo y luego dividimos por el tamaño de la celda con el resto.

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

Para reflejar esto, necesitamos que las coordenadas se especifiquen en radianes, por lo que para evitar recalcular las coordenadas radiales fuera de la función, agregaremos una opción usando el argumento bool. Por lo general, en los sombreadores, la ramificación (si se trata de construcciones) no es bienvenida, pero en este caso todos los píxeles en la pantalla irán por el mismo camino, por lo que esto es normal.

La duplicación debe ocurrir después de que la coordenada radial se repita, pero antes de que se convierta nuevamente a su posición normal. Descubriremos si necesitamos voltear la celda actual dividiendo el índice de la celda por el resto. Por lo general, esto debería darnos ceros y unos, pero en mi caso aparecen varios dos, lo cual es extraño, y aún así podemos manejarlo. Para eliminar deuces, simplemente restamos 1 de la variable flip, y luego tomamos el valor absoluto. Por lo tanto, los ceros y los deuces se convierten en unos, y las unidades se convierten en ceros, según sea necesario, solo en el orden inverso.

Como los ceros y unos están en el orden incorrecto, realizamos una interpolación lineal desde la versión invertida a la versión invertida, y no al revés, como antes. Para voltear la coordenada, simplemente restamos la posición del tamaño de la celda.

 //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); 


Espacio oscilante


Pero para cambiar el espacio no es necesario repetirlo. Por ejemplo, en el tutorial sobre lo básico, lo rotamos, movimos y escalamos. También puede hacer lo siguiente: mover cada eje sobre la base del otro con una onda sinusoidal. Esto hará que las distancias de la distancia firmada funcionen con menos precisión, pero hasta que oscilen demasiado, todo estará bien.

Primero, calculamos la magnitud del cambio de posición volteando los componentes x e y, y luego multiplicándolos por la frecuencia de oscilación. Luego tomamos el seno de este valor y lo multiplicamos por la cantidad de bamboleo que queremos agregar. Después de eso, simplemente agregamos este factor de oscilación a la posición y nuevamente aplicamos el resultado a la posición.

 //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); 


También podemos animar este movimiento, cambiando su posición, aplicando el movimiento en la posición de desplazamiento y devolviendo el espacio. Para que los números de coma flotante no sean demasiado grandes, hago la división con el resto pi * 2 por la frecuencia de oscilación, esto se correlaciona con la oscilación (la sinusoide se repite cada unidad pi * 2), por lo que evitamos saltos y compensaciones demasiado grandes.

 //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); 


Código fuente


2D SDF Library



 #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 

Shader demo básico



 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 } 

Ahora conoces todos los conceptos básicos de las funciones de distancia de signos que puedo recordar. En el próximo tutorial, intentaré hacer algo interesante con ellos.

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


All Articles