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{
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
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.
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.)
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.

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.
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.
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.
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.
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.
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.
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.
Código fonte
Biblioteca SDF 2D
#ifndef SDF_2D #define SDF_2D
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{
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.