Noções básicas de campo de distância assinado em 2D

Embora as malhas sejam a maneira mais simples e versátil de renderizar, existem outras opções para representar formas em 2D e 3D. Um método comumente usado são os campos de distância assinada (SDF). Os campos de distância assinados fornecem um traçado de raio menos dispendioso, permitem que diferentes formas fluam entre si e economizam em texturas de baixa resolução para imagens de alta qualidade.

Começaremos gerando o sinal dos campos de distância usando funções em duas dimensões, mas depois continuaremos a gerá-los em 3D. Usarei as coordenadas do espaço mundial para termos a menor dependência possível de dimensionamento e coordenadas UV; portanto, se você não entender como ele funciona, estude este tutorial em uma sobreposição plana , o que explica o que acontece.


Preparação da fundação


Vamos jogar fora temporariamente as propriedades do sombreador de sobreposição plana de base, porque, por enquanto, cuidaremos da base técnica. Em seguida, escrevemos a posição do vértice no mundo diretamente na estrutura do fragmento, e não o converteremos primeiro em UV. No último estágio de preparação, escreveremos uma nova função que calcula a cena e retorna a distância para a superfície mais próxima. Então chamamos as funções e usamos o resultado como uma cor.

Shader "Tutorial/034_2D_SDF_Basics"{ SubShader{ //           Tags{ "RenderType"="Opaque" "Queue"="Geometry"} Pass{ CGPROGRAM #include "UnityCG.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; //         o.position = UnityObjectToClipPos(v.vertex); //     o.worldPos = mul(unity_ObjectToWorld, v.vertex); return o; } float scene(float2 position) { //      return 0; } fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = fixed4(dist, dist, dist, 1); return col; } ENDCG } } FallBack "Standard" //fallback   ,       } 

Escreverei todas as funções para os campos de distância assinados em um arquivo separado, para que possamos usá-los repetidamente. Para fazer isso, vou criar um novo arquivo. Não adicionaremos nenhum mal a ele, depois o definiremos e concluiremos a proteção condicional de inclusão, verificando primeiro se a variável de pré-processador está definida. Se ainda não estiver definido, nós o definimos e concluímos a construção condicional if após as funções que queremos incluir. A vantagem disso é que, se adicionarmos o arquivo duas vezes (por exemplo, se adicionarmos dois arquivos diferentes, cada um com as funções necessárias e os dois adicionarem o mesmo arquivo), isso quebrará o sombreador. Se você tiver certeza de que isso nunca acontecerá, não poderá executar esta verificação.

 // in include file // include guards that keep the functions from being included more than once #ifndef SDF_2D #define SDF_2D // functions #endif 

Se o arquivo de inclusão estiver localizado na mesma pasta que o shader principal, podemos simplesmente incluí-lo usando a construção pragma.

 // in main shader #include "2D_SDF.cginc" 

Portanto, veremos apenas uma superfície preta na superfície renderizada, pronta para exibir a distância com um sinal nela.


Círculo


A função mais simples do campo de distância sinalizada é a função de círculo. A função receberá apenas a posição da amostra e o raio do círculo. Começamos obtendo o comprimento do vetor de posição da amostra. Portanto, obtemos um ponto na posição (0, 0), que é semelhante a um círculo com um raio de 0.

 float circle(float2 samplePosition, float radius){ return length(samplePosition); } 

Em seguida, você pode chamar a função de círculo na função de cena e retornar a distância que ela retorna.

 float scene(float2 position) { float sceneDistance = circle(position, 2); return sceneDistance; } 


Em seguida, adicionamos o raio aos cálculos. Um aspecto importante das funções de distância assinada é que, quando estamos dentro do objeto, obtemos uma distância negativa da superfície (é isso que a palavra assinado significa no campo de distância assinada da expressão). Para aumentar o círculo para um raio, subtraímos o raio do comprimento. Assim, a superfície, que está em toda parte onde a função retorna 0, se move para fora. O que está em duas unidades da distância da superfície para um círculo com tamanho 0, é apenas uma unidade de um círculo com raio de 1 e uma unidade dentro do círculo (o valor é -1) para um círculo com raio de 3;

 float circle(float2 samplePosition, float radius){ return length(samplePosition) - radius; } 


Agora, a única coisa que não podemos fazer é mover o círculo do centro. Para corrigir isso, você pode adicionar um novo argumento à função círculo para calcular a distância entre a posição da amostra e o centro do círculo e subtrair o raio desse valor para definir um círculo. Ou, você pode redefinir a origem movendo o espaço do ponto de amostra e, em seguida, obter um círculo nesse espaço. A segunda opção parece muito mais complicada, mas como mover objetos é uma operação que queremos usar para todas as figuras, é muito mais universal e, portanto, explicarei.

Movendo


"Transformação do espaço de um ponto" - soa muito pior do que realmente é. Isso significa que passamos o ponto para a função, e a função o altera para que ainda possamos usá-lo no futuro. No caso de uma transferência, simplesmente subtraímos o deslocamento do ponto. A posição é subtraída quando queremos mover as formas na direção positiva, porque as formas que renderizamos no espaço se movem na direção oposta à movimentação do espaço.

Por exemplo, se queremos desenhar uma esfera na posição (3, 4) , precisamos alterar o espaço para que (3, 4) transforme em (0, 0) , e para isso precisamos subtrair (3, 4) . Agora, se desenharmos uma esfera em torno de um novo ponto de origem, será um ponto antigo (3, 4) .

 // in sdf functions include file float2 translate(float2 samplePosition, float2 offset){ return samplePosition - offset; } 

 float scene(float2 position) { float2 circlePosition = translate(position, float2(3, 2)); float sceneDistance = circle(circlePosition, 2); return sceneDistance; } 


Retângulo


Outra forma simples é um retângulo. Para começar, consideramos os componentes separadamente. Primeiro, obtemos a distância do centro, assumindo o valor absoluto. Então, da mesma forma que um círculo, subtraímos metade do tamanho (que basicamente se assemelha ao raio de um retângulo). Para apenas mostrar como serão os resultados, retornaremos apenas um componente por enquanto.

 float rectangle(float2 samplePosition, float2 halfSize){ float2 componentWiseEdgeDistance = abs(samplePosition) - halfSize; return componentWiseEdgeDistance.x; } 


Agora podemos obter uma versão barata do retângulo simplesmente retornando o maior componente 2. Isso funciona em muitos casos, mas não corretamente, porque não exibe a distância correta nos cantos.


Os valores corretos para o retângulo fora da figura podem ser obtidos, primeiro levando o máximo entre as distâncias até as arestas e 0, e depois o comprimento.

Se não limitarmos a distância abaixo de 0, simplesmente calculamos a distância até os cantos (onde as distâncias das arestas são (0, 0) ), mas as coordenadas entre os cantos não cairão abaixo de 0, portanto, toda a aresta será usada. A desvantagem disso é que 0 é usado como a distância da borda para todo o interior da figura.

Para corrigir a distância 0 para toda a parte interna, é necessário gerar a distância interna, simplesmente usando a fórmula de retângulo barato (obtendo o valor máximo do componente xey) e garantindo que nunca exceda 0, levando o valor mínimo para 0. Em seguida, adicionamos a distância externa, que nunca é menor que 0, e a distância interna, que nunca excede 0, e obtemos a função de distância finalizada.

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

Como já gravamos a função de transferência de forma universal, agora também podemos usá-la para mover seu centro para qualquer lugar.

 float scene(float2 position) { float2 circlePosition = translate(position, float2(1, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } 


Turn


Girar formas é semelhante a movimento. Antes de calcular a distância da figura, giramos as coordenadas na direção oposta. Para simplificar o máximo possível a compreensão das rotações, multiplicamos a rotação por 2 * pi para obter o ângulo em radianos. Assim, passamos uma rotação para a função, em que 0,25 é um quarto de volta, 0,5 é meia volta e 1 é uma volta completa (você pode realizar conversões de maneira diferente se lhe parecer mais natural). Também invertemos a rotação, porque precisamos girar a posição na direção oposta à rotação da figura pelo mesmo motivo que ao mover.

Para calcular as coordenadas giradas, primeiro calculamos o seno e o cosseno com base no ângulo. O Hlsl possui uma função sincos que calcula esses dois valores mais rapidamente do que quando calculados separadamente.

Ao construir um novo vetor para o componente x, pegamos o componente original x multiplicado pelo cosseno e o componente y multiplicado pelo seno. Isso pode ser facilmente lembrado se você lembrar que o cosseno de 0 é 1 e, quando girado por 0, queremos que o componente x do novo vetor seja exatamente o mesmo de antes (ou seja, multiplique por 1). O componente y, que anteriormente apontou para cima, não contribuiu para o componente x, gira para a direita e seus valores começam em 0, inicialmente ficando maiores, ou seja, seu movimento é completamente descrito por um seno.

Para o componente y do novo vetor, multiplicamos o cosseno pelo componente y do vetor antigo e subtraímos o seno multiplicado pelo componente antigo x. Para entender por que subtraímos, em vez de adicionar o seno, multiplicado pelo componente x, é melhor imaginar como o vetor (1, 0) muda quando girado no sentido horário. O componente y do resultado começa em 0 e depois se torna menor que 0. Esse é o oposto de como o seno se comporta, então trocamos de sinal.

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

Agora que escrevemos o método de rotação, podemos usá-lo em combinação com a transferência para mover e girar a figura.

 float scene(float2 position) { float2 circlePosition = position; circlePosition = rotate(circlePosition, _Time.y); circlePosition = translate(circlePosition, float2(2, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } 


Nesse caso, primeiro giramos o objeto em torno do centro de toda a cena, para que a rotação também afete a transferência. Para girar uma figura em relação ao seu próprio centro, primeiro é necessário movê-la e depois girá-la. Devido a essa ordem alterada no momento da rotação, o centro da figura se tornará o centro do sistema de coordenadas.

 float scene(float2 position) { float2 circlePosition = position; circlePosition = translate(circlePosition, float2(2, 0)); circlePosition = rotate(circlePosition, _Time.y); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } 


Dimensionamento


A escala funciona de maneira semelhante a outras maneiras de transformar formas. Dividimos as coordenadas por escala, renderizando a figura no espaço com uma escala reduzida e, no sistema de coordenadas base, elas se tornam maiores.

 float2 scale(float2 samplePosition, float scale){ return samplePosition / scale; } 

 float scene(float2 position) { float2 circlePosition = position; circlePosition = translate(circlePosition, float2(0, 0)); circlePosition = rotate(circlePosition, .125); float pulseScale = 1 + 0.5*sin(_Time.y * 3.14); circlePosition = scale(circlePosition, pulseScale); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } 


Embora isso execute o dimensionamento corretamente, a distância também é dimensionada. A principal vantagem do campo de distância assinada é que sempre sabemos a distância da superfície mais próxima, mas diminuir o zoom destrói completamente essa propriedade. Isso pode ser facilmente corrigido multiplicando o campo de distância obtido a partir da função de distância do sinal (no nosso caso, o rectangle ) pela escala. Pela mesma razão, não podemos facilmente escalar desigualmente (com escalas diferentes para os eixos x e y).

 float scene(float2 position) { float2 circlePosition = position; circlePosition = translate(circlePosition, float2(0, 0)); circlePosition = rotate(circlePosition, .125); float pulseScale = 1 + 0.5*sin(_Time.y * 3.14); circlePosition = scale(circlePosition, pulseScale); float sceneDistance = rectangle(circlePosition, float2(1, 2)) * pulseScale; return sceneDistance; } 


Visualização


Os campos de distância assinados podem ser usados ​​para várias coisas, como criar sombras, renderizar cenas 3D, física e renderizar texto. Mas ainda não queremos aprofundar a complexidade, portanto, explicarei apenas duas técnicas de visualização. A primeira é uma forma clara com antialiasing, a segunda é a renderização de linhas, dependendo da distância.

Limpar formulário


Este método é semelhante ao que é frequentemente usado ao renderizar texto, pois cria um formulário claro. Se queremos gerar um campo de distância não a partir de uma função, mas lê-lo a partir de uma textura, isso nos permite usar texturas com uma resolução muito menor do que o habitual e obter bons resultados. O TextMesh Pro usa essa técnica para renderizar texto.

Para aplicar essa técnica, aproveitamos o fato de os dados nos campos de distância serem assinados e conhecemos o ponto de corte. Começamos calculando a distância que o campo de distância muda para o próximo pixel. Esse deve ser o mesmo valor que o comprimento da alteração de coordenadas, mas é mais fácil e confiável calcular a distância com um sinal.

Depois de receber a mudança de distância, podemos fazer um passo suave da metade da mudança de distância para menos / mais metade da mudança de distância. Isso fará um recorte simples em torno de 0, mas com suavização. Então você pode usar esse valor suavizado para qualquer valor binário que precisarmos. Neste exemplo, alterarei o sombreador para um sombreador de transparência e o utilizarei para o canal alfa. Eu passo suavemente de um valor positivo para um negativo porque queremos que o valor negativo do campo de distância seja visível. Se você não entende bem como a renderização em transparência funciona aqui, recomendo a leitura do meu tutorial sobre renderização em transparência.

 //properties Properties{ _Color("Color", Color) = (1,1,1,1) } 

 //in subshader outside of pass Tags{ "RenderType"="Transparent" "Queue"="Transparent"} Blend SrcAlpha OneMinusSrcAlpha ZWrite Off 

 fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); float distanceChange = fwidth(dist) * 0.5; float antialiasedCutoff = smoothstep(distanceChange, -distanceChange, dist); fixed4 col = fixed4(_Color, antialiasedCutoff); return col; } 


Linhas de elevação


Outra técnica comum para visualizar campos de distância é exibir distâncias como linhas. Em nossa implementação, adicionarei algumas linhas grossas e algumas finas entre elas. Também pintarei o interior e o exterior da figura em cores diferentes, para que você possa ver onde está o objeto.

Começaremos exibindo a diferença entre o interior e o exterior da figura. Como as cores podem ser personalizadas no material, adicionaremos novas propriedades, bem como variáveis ​​de sombreamento para as cores internas e externas da figura.

 Properties{ _InsideColor("Inside Color", Color) = (.5, 0, 0, 1) _OutsideColor("Outside Color", Color) = (0, .5, 0, 1) } 

 //global shader variables float4 _InsideColor; float4 _OutsideColor; 

Em seguida, no shader de fragmento, verificamos onde o pixel está localizado, que renderizamos comparando a distância com o sinal com 0 usando a função step . Usamos essa variável para interpolar da cor interna para a externa e renderizá-la na tela.

 fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = lerp(_InsideColor, _OutsideColor, step(0, dist)); return col; } 


Para renderizar linhas, primeiro precisamos especificar com que freqüência renderizamos as linhas e qual a espessura delas, definindo as propriedades e as variáveis ​​de sombreamento correspondentes.

 //Properties _LineDistance("Mayor Line Distance", Range(0, 2)) = 1 _LineThickness("Mayor Line Thickness", Range(0, 0.1)) = 0.05 

 //shader variables float _LineDistance; float _LineThickness; 

Então, para renderizar as linhas, começaremos calculando a mudança na distância para que possamos usá-la mais tarde para suavizar. Também já o dividimos por 2, porque mais tarde adicionamos metade e subtraímos metade para cobrir a distância de mudança de 1 pixel.

 float distanceChange = fwidth(dist) * 0.5; 

Então tomamos a distância e a transformamos para que ele tenha o mesmo comportamento em pontos de repetição. Para fazer isso, primeiro dividimos pela distância entre as linhas, enquanto não obteremos números completos a cada primeiro passo, mas números completos apenas com base na distância que definimos.

Então adicionamos 0,5 ao número, pegamos a parte fracionária e subtraímos 0,5 novamente. A parte fracionária e a subtração são necessárias aqui para que a linha passe por zero no padrão de repetição. Adicionamos 0,5 para obter a parte fracionária, a fim de neutralizar subtrações adicionais de 0,5 - o deslocamento levará ao fato de que os valores nos quais o gráfico é 0 estão em 0, 1, 2 etc., e não em 0,5, 1,5, etc.

Os últimos passos para converter o valor - pegamos o valor absoluto e multiplicamos novamente pela distância entre as linhas. O valor absoluto torna as áreas antes e depois dos pontos da linha iguais, o que facilita a criação de recortes para as linhas. A última operação, na qual multiplicamos novamente o valor pela distância entre as linhas, é necessária para neutralizar a divisão no início da equação, graças a ela, a alteração no valor é novamente a mesma do início, e a alteração calculada anteriormente na distância ainda está correta.


 float majorLineDistance = abs(frac(dist / _LineDistance + 0.5) - 0.5) * _LineDistance; 

Agora que calculamos a distância das linhas com base na distância da figura, podemos desenhar as linhas. Fazemos um passo suave da espessura da linha menos metade da alteração na distância para espessura da linha mais metade da mudança na distância e usamos a distância da linha calculada como um valor para comparação. Depois de calcular esse valor, multiplicamos por cor para criar linhas pretas (você também pode ler uma cor diferente se precisar de linhas coloridas).

 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); return col * majorLines; } 


Implementamos linhas finas entre as grossas da mesma maneira - adicionamos uma propriedade que determina quantas linhas finas devem existir entre as grossas e depois fazemos o que fizemos com as grossas, mas, devido à distância entre as linhas finas, dividimos a distância entre as grossas pelo número de linhas finas entre elas. eles. Também IntRange o número de linhas finas IntRange , graças a isso, podemos apenas atribuir valores inteiros e não obter linhas finas que não IntRange grossas. Depois de calcular as linhas finas, nós as multiplicamos por cor da mesma maneira que as grossas.

 //properties [IntRange]_SubLines("Lines between major lines", Range(1, 10)) = 4 _SubLineThickness("Thickness of inbetween lines", Range(0, 0.05)) = 0.01 

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


Código fonte


Recursos SDF 2D



 #ifndef SDF_2D #define SDF_2D 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; } 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 

Exemplo de círculo



 Shader "Tutorial/034_2D_SDF_Basics/Rectangle"{ 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) { float2 circlePosition = position; circlePosition = rotate(circlePosition, _Time.y * 0.5); circlePosition = translate(circlePosition, float2(2, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = fixed4(dist, dist, dist, 1); return col; } ENDCG } } FallBack "Standard" //fallback adds a shadow pass so we get shadows on other objects } 

Exemplo de retângulo



 Shader "Tutorial/034_2D_SDF_Basics/Rectangle"{ 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) { float2 circlePosition = position; circlePosition = rotate(circlePosition, _Time.y * 0.5); circlePosition = translate(circlePosition, float2(2, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); fixed4 col = fixed4(dist, dist, dist, 1); return col; } ENDCG } } FallBack "Standard" //fallback adds a shadow pass so we get shadows on other objects } 

Cutoff



 Shader "Tutorial/034_2D_SDF_Basics/Cutoff"{ Properties{ _Color("Color", Color) = (1,1,1,1) } SubShader{ Tags{ "RenderType"="Transparent" "Queue"="Transparent"} Blend SrcAlpha OneMinusSrcAlpha ZWrite Off 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; }; fixed3 _Color; 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) { float2 circlePosition = position; circlePosition = rotate(circlePosition, _Time.y * 0.5); circlePosition = translate(circlePosition, float2(2, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } fixed4 frag(v2f i) : SV_TARGET{ float dist = scene(i.worldPos.xz); float distanceChange = fwidth(dist) * 0.5; float antialiasedCutoff = smoothstep(distanceChange, -distanceChange, dist); fixed4 col = fixed4(_Color, antialiasedCutoff); return col; } ENDCG } } FallBack "Standard" //fallback adds a shadow pass so we get shadows on other objects } 

Linhas de distância



 Shader "Tutorial/034_2D_SDF_Basics/DistanceLines"{ 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) { float2 circlePosition = position; circlePosition = rotate(circlePosition, _Time.y * 0.2); circlePosition = translate(circlePosition, float2(2, 0)); float sceneDistance = rectangle(circlePosition, float2(1, 2)); return sceneDistance; } 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 } 

Espero ter conseguido explicar o básico dos campos de distância com um sinal e você já está aguardando alguns novos tutoriais nos quais falarei sobre outras maneiras de usá-los.

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


All Articles