Manipulação espacial 2D com campos de distância assinados

Ao trabalhar com ativos de polígono, você pode desenhar apenas um objeto por vez (se não levar em consideração técnicas como lote e instanciamento), mas se usar campos de distância com um sinal (campos de distância assinados, SDF), não ficaremos limitados a isso. Se duas posições tiverem a mesma coordenada, as funções de distância assinadas retornarão o mesmo valor e, em um cálculo, podemos obter vários números. Para entender como transformar o espaço usado para gerar campos de distância assinados, recomendo que você descubra como criar formas usando as funções de distância assinada e combine formas sdf .


Configuração


Neste tutorial, modifico o emparelhamento entre o quadrado e o círculo, mas você pode usá-lo para qualquer outra forma. Isso é semelhante à configuração do tutorial anterior .

É importante aqui que a parte modificável seja antes de usar posições para gerar 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" } 

E a função 2D_SDF.cginc localizada na mesma pasta do shader, que expandiremos, a princípio se parece com isso:

 #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 


Repetição do espaço


Reflexão de espelho


Uma das operações mais simples é espelhar o mundo em torno de um eixo. Para espelhá-lo em torno do eixo y, pegamos o valor absoluto do componente x de nossa posição. Assim, as coordenadas à direita e esquerda do eixo serão as mesmas. (-1, 1) transforma em (1, 1) e aparece dentro do círculo, usando (1, 1) como origem e com um raio maior que 0.

Na maioria das vezes, o código que usa essa função se parece com position = mirror(position); para que possamos simplificá-lo um pouco. Simplesmente declararemos o argumento da posição como inout. Assim, ao escrever no argumento, ele também mudará a variável que passamos para a função. O valor de retorno pode ser do tipo nulo, porque ainda não usamos o valor de retorno.

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

 //in shader function mirror(position); 


Acabou bem, mas dessa maneira temos apenas um eixo para o espelhamento. Podemos expandir a função girando o espaço, como fizemos ao girar as figuras. Primeiro você precisa girar o espaço, depois espelhá-lo e depois girá-lo de volta. Dessa forma, podemos realizar o espelhamento em relação a qualquer ângulo. O mesmo é possível ao transferir espaço e realizar a transferência reversa após o espelhamento. (Se você executar as duas operações, antes de espelhar, não se esqueça de primeiro executar a transferência e depois virar, após o que a vez passa primeiro.)

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


Células


Se você sabe como a geração de ruído funciona, então entende que, para a geração procedural, repetimos frequentemente a posição e obtemos pequenas células essencialmente iguais, diferindo apenas em parâmetros insignificantes. Podemos fazer o mesmo para campos de distância.

Como a função fmod (além de usar% para dividir com o restante) nos fornece o restante, não a definição do restante, teremos que usar um truque. Primeiro, pegamos o restante da divisão inteira pela função fmod. Para números positivos, é exatamente disso que precisamos, e para números negativos, este é o resultado que precisamos menos o período. Você pode corrigir isso adicionando um ponto e novamente pegando o restante da divisão. Adicionar um período fornecerá o resultado desejado para valores de entrada negativos e, para valores de entrada positivos, o valor é um período mais alto. O segundo restante da divisão não fará nada com os valores dos valores de entrada negativos, porque eles já estão no intervalo de 0 ao período e, para os valores de entrada positivos, subtraímos essencialmente um 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)); 


O problema com as células é que perdemos a continuidade pela qual amamos os campos de distância. Isso não é ruim se as formas estiverem apenas no meio das células, mas no exemplo mostrado acima, isso pode levar a artefatos significativos que devem ser evitados quando os campos de distância são usados ​​para muitas tarefas nas quais os campos de distância geralmente podem ser aplicados.

Há uma solução que não funciona em todos os casos, mas quando funciona, é maravilhoso: espelhar todas as outras células. Para fazer isso, precisamos de um índice de célula de pixel, mas ainda não temos um valor de retorno na função, portanto, podemos apenas usá-lo para retornar o índice de célula.

Para calcular o índice de células, dividimos a posição pelo período. Assim, 0-1 é a primeira célula, 1-2 é a segunda, e assim por diante ... e podemos discretizá-la facilmente. Para obter o índice da célula, basta arredondar o valor para baixo e retornar o resultado. O importante é que calculemos o índice da célula antes de dividir com o restante para repetir as células; caso contrário, obteríamos o índice 0 em qualquer lugar, porque a posição não pode exceder o 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; } 

Com esta informação, podemos inverter as células. Para entender se devemos ou não inverter, dividimos o módulo de índice de células 2. O resultado dessa operação é alternadamente 0 e 1 ou -1 a cada segunda célula. Para tornar a alteração mais permanente, pegamos o valor absoluto e obtemos um valor que alterna entre 0 e 1.

Para usar esse valor para alternar entre uma posição normal e invertida, precisamos de uma função que não faça nada para o valor 0 e subtrai a posição do período em que a inversão é 1. Ou seja, executamos interpolação linear da posição normal para a invertida usando a variável inversa . Como a variável flip é um vetor 2D, seus componentes são invertidos 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 radiais


Outra grande característica é a repetição do espaço em um padrão radial.

Para obter esse efeito, primeiro calculamos a posição radial. Para fazer isso, codificamos o ângulo relativo ao centro do eixo x e a distância do centro ao longo do eixo y.

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

Então repetimos a esquina. Como transmitir o número de repetições é muito mais fácil que o ângulo de cada peça, primeiro calculamos o tamanho de cada peça. O círculo inteiro é 2 * pi; portanto, para obter a parte correta, dividimos 2 * pi pelo tamanho da célula.

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

Com essas informações, podemos repetir o componente x da posição radial em todas as unidades cellSize. Realizamos repetição por divisão com o restante, portanto, como antes, temos problemas com números negativos, que podem ser eliminados com a ajuda de duas funções de divisão com o restante.

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

Então você precisa mover a nova posição de volta para as coordenadas xy habituais. Aqui, usamos a função sincos com o componente x da posição radial como ângulo para escrever o seno na coordenada x da posição e o cosseno na coordenada y. Com esta etapa, obtemos uma posição normalizada. Para obter a direção correta a partir do centro, é necessário multiplicá-la pelo componente y da posição radial, o que significa o comprimento.

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


Em seguida, também podemos adicionar o índice de células e o espelhamento, como fizemos com as células regulares.

É necessário calcular o índice de células após calcular a posição radial, mas antes de receber o restante da divisão. Para isso, dividimos o componente x da posição radial e arredondamos o resultado para baixo. Nesse caso, o índice também pode ser negativo, e isso é um problema se o número de células for ímpar. Por exemplo, com 3 células, obtemos 1 célula com um índice de 0, 1 célula com um índice de -1 e 2 meias células com os índices 1 e -2. Para contornar esse problema, adicionamos o número de células à variável arredondada para baixo e dividimos pelo tamanho da célula com o restante.

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

Para espelhar isso, precisamos que as coordenadas estejam em radianos, para evitar recalcular as coordenadas radiais fora da função, adicionamos uma opção a ela usando o argumento bool. Normalmente, em shaders, a ramificação (se construções) não é bem-vinda, mas nesse caso todos os pixels da tela seguirão o mesmo caminho, portanto isso é normal.

O espelhamento deve ocorrer depois que a coordenada radial é repetida, mas antes de ser convertida de volta à sua posição normal. Descobriremos se precisamos inverter a célula atual dividindo o índice da célula por 2. Com o restante, geralmente isso deve nos dar zeros e uns, mas, no meu caso, aparecem dois pares, o que é estranho, e ainda assim podemos lidar com isso. Para eliminar duques, simplesmente subtraímos 1 da variável flip e pegamos o valor absoluto. Assim, zeros e duques se tornam uns, e as unidades se tornam zeros, como precisamos, apenas na ordem inversa.

Como zeros e uns estão na ordem errada, executamos interpolação linear da versão invertida para a versão invertida, e não vice-versa, como antes. Para inverter a coordenada, simplesmente subtraímos a posição do tamanho da célula.

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


Espaço oscilante


Mas para mudar o espaço não é necessário repeti-lo. Por exemplo, no tutorial básico, rotacionamos, movemos e escalamos. Você também pode fazer o seguinte: mova cada eixo com base no outro com uma onda senoidal. Isso tornará as distâncias da função de distância sinalizada menos precisas, mas até elas girarem demais, tudo ficará bem.

Primeiro, calculamos a magnitude da mudança de posição, invertendo os componentes xey, e depois multiplicando-os pela frequência de oscilação. Depois, extraímos o seno desse valor e o multiplicamos pela quantidade de oscilação que queremos adicionar. Depois disso, simplesmente adicionamos esse fator de oscilação à posição e novamente aplicamos o resultado à posição.

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


Também podemos animar esse movimento, alterando sua posição, aplicando movimentos na posição de deslocamento e retornando o espaço de volta. Para que os números de ponto flutuante não fiquem muito grandes, eu faço a divisão com o restante pi * 2 pela frequência de oscilação, isso se correlaciona com a oscilação (o sinusóide repete cada unidade de pi * 2), para evitar saltos e compensações muito 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 fonte


Biblioteca SDF 2D



 #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 de demonstração 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 } 

Agora você conhece todos os conceitos básicos das funções de distância de sinal que eu conseguia lembrar. No próximo tutorial, tentarei fazer algo interessante com eles.

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


All Articles