Neste tutorial, falarei sobre como usar os shaders para recriar o popular efeito de doodle de sprite no Unity. Se o seu estilo exigir esse estilo, neste artigo você aprenderá como alcançá-lo sem renderizar um monte de imagens adicionais.
Nos últimos anos, esse estilo se tornou cada vez mais popular e é usado ativamente em jogos como
GoNNER e
Baba is You .
Este tutorial aborda tudo o que você precisa, desde o básico da codificação de sombreador até a matemática usada. No final do artigo, há um link para baixar o pacote completo do Unity.
O sucesso do
Doodle Studio 95 me inspirou a criar este tutorial
! .
1. Introdução
No meu blog, exploro tópicos bastante complexos, da
matemática da cinemática inversa à
dispersão atmosférica de Rayleigh . Eu realmente gosto de tornar tópicos tão difíceis compreensíveis para um público amplo. Mas o número de pessoas interessadas neles e com um nível técnico suficiente não é tão grande. Portanto, não se surpreenda que os artigos mais populares sejam os mais simples. Isso também se aplica a um recente
tweet de Nick Caman, no qual ele mostrou como criar
um efeito de doodle no Unity.
Depois de 1000 curtidas e 4000 retuítes, ficou claro que há uma forte demanda por tutoriais mais simples que podem ser estudados mesmo por pessoas que quase não têm conhecimento em criar shaders.
Se você está procurando uma maneira profissional e eficaz de animar sprites 2D com um alto grau de controle artístico, recomendo o
Doodle Studio 95! (veja GIF abaixo).
Aqui você pode ver alguns jogos que usam essa ferramenta.
Anatomia do efeito Doodle
Para recriar o efeito doodle, primeiro precisamos entender como ele funciona e quais técnicas são usadas nele.
Efeito Shader. Primeiramente, queremos que esse efeito seja o mais simples possível e não exija scripts adicionais. Isso é possível graças ao uso de
shaders que informam ao Unity como renderizar modelos 3D (incluindo os planos!) Na tela. Se você não está familiarizado com o mundo
da codificação de shader , deve estudar meu artigo
A Gentle Introduction to Shaders .
Sprite shader. A unidade vem com muitos tipos de shaders. Se você usar as ferramentas 2D do Unity fornecidas, provavelmente estará trabalhando com
sprites .
SpriteRenderer
caso, você precisa do
Sprite Shader - um tipo especial de shader compatível com o
SpriteRenderer
Unity. Ou você pode começar com o
sombreador Unlit mais tradicional.
Deslocamento de vértice. Ao desenhar sprites manualmente, nenhum quadro coincide completamente com os outros. Queremos fazer o sprite "balançar" de alguma forma, a fim de simular esse efeito. Os shaders têm uma maneira muito eficaz de fazer isso, usando o
deslocamento de vértice . Essa é uma técnica que permite alterar a posição dos vértices de um objeto 3D. Se os movermos aleatoriamente, obteremos o efeito desejado.
Tempo de viagem. As animações à mão livre geralmente têm uma baixa taxa de quadros. Se queremos simular, digamos, cinco quadros por segundo, precisamos mudar a posição dos vértices do sprite cinco vezes por segundo. No entanto, é provável que o Unity execute o jogo a uma taxa de atualização muito maior; talvez com 30 ou até 60 quadros por segundo. Para que o sprite não seja alterado 60 vezes por segundo, você precisa trabalhar no componente de tempo da animação.
Etapa 1: concluindo o sprite shader
Se você deseja criar um novo sombreador no Unity, a escolha será bastante limitada. O shader mais próximo com o qual podemos começar será o
Unlit Shader , embora não seja necessariamente o mais adequado para nossos propósitos.
Se você deseja que o shader doodle seja totalmente compatível com o
SpriteRenderer
Unity, precisamos complementar o
Sprite Shader existente. Infelizmente, não podemos acessá-lo diretamente do próprio Unity.
Você pode acessá-lo acessando a página de
arquivo de download do
Unity e baixando o pacote
Build in shaders para a versão do Unity com a qual você está trabalhando. Este é um arquivo zip que contém o código fonte de todos os shaders que acompanham sua compilação do Unity.
Após o download, descompacte-o e localize o arquivo
Sprites-Diffuse.shader
na
Sprites-Diffuse.shader
builtin_shaders-2018.1.6f1\DefaultResourcesExtra
. Este é o arquivo que usaremos no tutorial.
Sprites-Diffuse não é um shader de sprite padrão!Ao criar um novo sprite, seu material padrão usa um shader chamado Sprites-Default.shader
, não Sprites-Diffuse.shader
.
A diferença entre os dois é que o primeiro não usa iluminação e o segundo responde à iluminação na cena. Devido à natureza da implementação do Unity, a versão difusa é muito mais fácil de editar do que a versão sem iluminação.
No final deste tutorial, há um link para baixar os shaders de doodle com e sem iluminação.
Etapa 2: vértices de deslocamento
Dentro de
Sprites-Diffuse.shader
existe uma função chamada
vert
, que é a
função de vértice que falamos acima. Seu nome não é importante, o principal é que ele coincide com o especificado na seção de
vertex:
da
#pragma
:
#pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing
Em resumo, a função de vértice é chamada para cada vértice do modelo 3D e decide como sobrepor esse espaço no espaço bidimensional da tela. Neste tutorial, estamos interessados apenas em como mover um objeto.
O parâmetro
appdata_full v
contém o campo de
vertex
, que contém a posição 3D de cada vértice no
espaço do objeto . Quando o valor muda, o vértice muda. Ou seja, por exemplo, o código mostrado abaixo transferirá o objeto com seu shader uma unidade ao longo do eixo X.
void vert (inout appdata_full v, out Input o) { v.vertex = UnityFlipSprite(v.vertex, _Flip); v.vertex.x += 1; #if defined(PIXELSNAP_ON) v.vertex = UnityPixelSnap (v.vertex); #endif UNITY_INITIALIZE_OUTPUT(Input, o); o.color = v.color * _Color * _RendererColor; }
Por padrão, os jogos 2D criados no Unity operam apenas com os eixos X e Y, portanto, precisamos alterar
v.vertex.xy
para mover o sprite em um plano bidimensional.
O que é espaço de objeto?O campo de vertex
da estrutura appdata_full
contém a posição do vértice atual processado pelo sombreador no espaço do objeto. Esta é a posição do vértice sob a suposição de que o objeto está localizado no centro do mundo (0,0,0), sem alterar a escala e sem rotação.
Os picos expressos no espaço do mundo refletem sua posição real no cenário da Unidade.
Por que o objeto não se move a uma velocidade de um metro por quadro?Se adicionarmos +1 ao componente x
do valor transform.position
no método Update
de um script C #, veremos como o objeto voa para a direita a uma velocidade de 1 metro por quadro, ou seja, cerca de 216 quilômetros por hora.
Isso ocorre porque as alterações feitas pelo C # estão mudando a própria posição. Na função de vértice, isso não acontece. O sombreador altera apenas a representação visual do modelo, mas não atualiza ou modifica os vértices armazenados do modelo. É por isso que adicionar +1 ao v.vertex.x
desloca um objeto por metro apenas uma vez.
Lembre-se de importar o sprite como apertado!Esse efeito desloca a parte superior do sprite. Tradicionalmente, os sprites são importados para o Unity como quadrângulos (veja a figura à esquerda). Isso significa que eles têm apenas quatro picos. Nesse caso, somente esses pontos podem ser movidos, o que reduz a força do efeito doodle.
Para uma distorção mais forte e mais realista, você precisa importar sprites escolhendo
Apertado para o parâmetro
Tipo de malha , que os transforma em uma forma convexa (veja a figura à direita).
Isso aumenta o número de vértices. Isso nem sempre é desejável, mas é exatamente disso que precisamos agora.
Deslocamento aleatório
O efeito doodle altera aleatoriamente a posição de cada vértice. A
amostragem de números aleatórios em um sombreador sempre foi uma tarefa difícil. Isso é causado principalmente pela arquitetura distribuída da GPU, o que complica e reduz a eficiência da recriação do algoritmo usado na maioria das bibliotecas (incluindo o
Mathf.Random
).
O post de Nick Caman usou uma textura de ruído que, quando amostrada, dá a ilusão de aleatoriedade. No contexto do seu projeto, essa pode não ser a abordagem mais eficaz, pois nesse caso o número de operações de pesquisa de textura executadas pelo shader dobra.
Portanto, na maioria dos shaders, são utilizadas funções bastante confusas e caóticas, as quais, apesar de seu determinismo, parecem não ter regularidades. E como eles devem ser distribuídos, cada número aleatório deve ser gerado com sua própria semente. Isso é ótimo para nós, porque a posição de cada vértice deve ser única. Podemos usar isso para ligar um número aleatório a cada vértice. Discutiremos a implementação dessa função aleatória mais tarde; vamos chamá-lo de
random3
.
Podemos usar
random3
para gerar um deslocamento aleatório de cada vértice. No exemplo abaixo, os números aleatórios são dimensionados usando a propriedade
_NoiseScale
, que permite controlar a força de deslocamento.
void vert (inout appdata_full v, out Input o) { ... float2 noise = random3(v.vertex.xyz).xy * _NoiseScale; v.vertex.xy += noise; ... }
Agora precisamos escrever o
random3
código
random3
.
Aleatoriedade no sombreador
Uma das funções pseudo-aleatórias mais comuns e icônicas usadas em shaders é extraída de um artigo de 1998 de W. Ray, sob o título "
Ao gerar números aleatórios, com a ajuda de y = [(a + x) sen (bx)] mod 1 ".
float rand(float2 co) { return fract(sin(dot(co.xy ,float2(12.9898,78.233))) * 43758.5453); }
Essa função é determinística (ou seja, não é
verdadeiramente aleatória), mas se comporta tão aleatoriamente que parece completamente aleatória. Tais funções são chamadas de
pseudo-aleatórias . Para o meu tutorial, escolhi uma função mais complexa, inventada por
Nikita Miropolsky.Gerar um número pseudo-aleatório em um shader é um tópico muito complexo. Se você estiver interessado em aprender mais sobre ela,
o Livro de Shaders tem um bom capítulo sobre ela. Além disso,
Patricio Gonzales Vivo construiu um grande repositório de funções pseudo-aleatórias chamadas de
ruído GLSL , que pode ser usado em shaders.
Etapa 3: adicionar tempo
Graças ao código que escrevemos, cada ponto em cada quadro é alterado na mesma quantidade. Portanto, temos um sprite distorcido, não um efeito de doodle. Para corrigir isso, você precisa encontrar uma maneira de alterar o efeito ao longo do tempo. Uma das maneiras mais fáceis de fazer isso é usar a posição do vértice e a hora atual para gerar um número aleatório.
No nosso caso, acabei de adicionar o tempo atual em segundos
_Time.y
à posição do vértice.
float time = float3(_Time.y, 0, 0); float2 noise = random3(v.vertex.xyz + time).xy * _NoiseScale; v.vertex.xy += noise;
Efeitos mais complexos podem exigir maneiras mais sofisticadas de adicionar tempo à equação. Porém, como estamos interessados apenas em um efeito aleatório intermitente, dois valores são mais que suficientes.
Mudança de tempo
O principal problema ao adicionar
_Time.y
é que ele faz com que o sprite seja animado em todos os quadros. Isso é indesejável para nós, porque a maioria das animações desenhadas à mão tem uma baixa taxa de quadros. O componente de tempo não deve ser contínuo, mas discreto. Isso significa que, se queremos exibir cinco quadros por segundo, ele deve mudar apenas cinco vezes por segundo. Ou seja, o tempo deve estar
vinculado a um quinto de segundo. Os únicos valores válidos devem ser
,
,
,
,
,
com e assim por diante ...
Já abordei o snap no meu blog em
How To Snap To Grid . Neste artigo, propus uma solução para o problema de vincular a posição de um objeto em uma grade espacial. Se precisarmos vincular o tempo a uma grade de tempo, a matemática e, portanto, o código serão os mesmos.
A função mostrada abaixo pega o número
x
e o vincula a valores inteiros que são múltiplos de
snap
.
inline float snap (float x, float snap) { return snap * round(x / snap); }
Ou seja, nosso código se torna assim:
float time = snap(_Time.y, _NoiseSnap); float2 noise = random3(v.vertex.xyz + float3(time, 0.0, 0.0) ).xy * _NoiseScale; v.vertex.xy += noise;
Conclusão
O pacote Unity para esse efeito pode ser baixado gratuitamente
no Patreon .
Recursos Adicionais
Nos últimos meses, um grande número de jogos no estilo de rabiscos apareceu. Parece-me que o motivo disso foi o sucesso do
Doodle Studio 95! - Uma ferramenta para Unity, desenvolvida por
Fernando Ramallo . Se esse estilo é adequado para o seu jogo, recomendo comprar esta ferramenta incrível.