En este tutorial, hablaré sobre cómo recrear el popular efecto de sprite doodle en Unity usando sombreadores. Si su estilo requiere este estilo, de este artículo aprenderá cómo lograrlo sin generar un montón de imágenes adicionales.
En los últimos años, este estilo se ha vuelto cada vez más popular y se usa activamente en juegos como
GoNNER y
Baba is You .
Este tutorial cubre todo lo que necesita, desde los conceptos básicos de la codificación de sombreadores hasta las matemáticas utilizadas. Al final del artículo hay un enlace para descargar el paquete completo de Unity.
¡El éxito de
Doodle Studio 95 me inspiró a crear este tutorial
! .
Introduccion
En mi blog, exploro temas bastante complejos, desde las
matemáticas de la cinemática inversa hasta
la dispersión atmosférica de Rayleigh . Realmente me gusta hacer que temas tan difíciles sean entendibles para una audiencia amplia. Pero la cantidad de personas interesadas en ellos y con un nivel técnico suficiente no es tan grande. Por lo tanto, no debería sorprenderse de que los artículos más populares sean los más simples. Esto también se aplica a un reciente
tweet de Nick Caman en el que mostró cómo crear
un efecto de doodle en Unity.
Después de 1000 me gusta y 4000 retweets, quedó claro que existe una gran demanda de tutoriales más simples que puedan ser estudiados incluso por personas que casi no tienen conocimiento de la creación de sombreadores.
Si está buscando una forma profesional y efectiva de animar sprites 2D con un alto grado de control artístico, ¡le recomiendo
Doodle Studio 95! (Ver GIF a continuación).
Aquí puedes ver algunos juegos que usan esta herramienta.
Doodle Effect Anatomy
Para recrear el efecto de garabato, primero debemos entender cómo funciona y qué técnicas se utilizan en él.
Efecto sombreador. En primer lugar, queremos que este efecto sea lo más simple posible y no requiera scripts adicionales. Esto es posible gracias al uso de
sombreadores que le dicen a Unity cómo renderizar modelos 3D (¡incluidos los planos!) En la pantalla. Si no está familiarizado con el mundo
de la codificación de sombreadores , entonces debería estudiar mi artículo
Una introducción suave a los sombreadores .
Shader Sprite. La unidad viene con muchos tipos de sombreadores. Si usa las herramientas Unity 2D proporcionadas, lo más probable es que esté trabajando con
sprites . Si es así, entonces necesita
Sprite Shader , un tipo especial de sombreador compatible con
SpriteRenderer
Unity. O puede comenzar con el
sombreador Unlit más tradicional.
Desplazamiento de vértice. Al dibujar sprites manualmente, ningún marco coincidirá completamente con los demás. Queremos hacer que el sprite se tambalee de alguna manera para simular este efecto. Los sombreadores tienen una forma muy efectiva de hacer esto, utilizando el
desplazamiento de vértice . Esta es una técnica que le permite cambiar la posición de los vértices de un objeto 3D. Si los movemos al azar, obtenemos el efecto deseado.
Tiempo de viaje. Las animaciones a mano alzada suelen tener una velocidad de fotogramas baja. Si queremos simular, digamos, cinco cuadros por segundo, entonces necesitamos cambiar la posición de los vértices del sprite cinco veces por segundo. Sin embargo, es probable que Unity ejecute el juego a una frecuencia de actualización mucho mayor; quizás con 30 o incluso 60 cuadros por segundo. Para que el sprite no cambie 60 veces por segundo, debe trabajar en el componente de sincronización de la animación.
Paso 1: completar el sombreador de sprites
Si desea crear un nuevo sombreador en Unity, la elección será bastante limitada. El sombreador más cercano con el que podemos comenzar será
Unlit Shader , aunque no es necesariamente el más adecuado para nuestros propósitos.
Si desea que el sombreador de garabatos sea totalmente compatible con
SpriteRenderer
Unity, entonces debemos complementar el
Sprite Shader existente. Desafortunadamente, no podemos acceder a él directamente desde la propia Unidad.
Puede acceder a él yendo a la página del
archivo de descarga de Unity y descargando el paquete
Build in shaders para la versión de Unity con la que está trabajando. Este es un archivo zip que contiene el código fuente de todos los sombreadores que vienen con la compilación de Unity.
Después de descargarlo, descomprímalo y busque el archivo
Sprites-Diffuse.shader
en la
builtin_shaders-2018.1.6f1\DefaultResourcesExtra
. Este es el archivo que usaremos en el tutorial.
¡Sprites-Diffuse no es un sombreador de sprites estándar!Al crear un nuevo sprite, su material estándar utiliza un sombreador llamado Sprites-Default.shader
, no Sprites-Diffuse.shader
.
La diferencia entre los dos es que el primero no usa iluminación, y el segundo responde a la iluminación en la escena. Debido a la naturaleza de la implementación de Unity, la versión difusa es mucho más fácil de editar que la versión sin iluminación.
Al final de este tutorial hay un enlace para descargar los sombreadores de doodle con y sin iluminación.
Paso 2: vértices desplazados
Dentro de
Sprites-Diffuse.shader
hay una función llamada
vert
, que es la
función de vértice que mencionamos anteriormente. Su nombre no es importante, lo principal es que coincide con el especificado en la sección de
vertex:
de la
#pragma
:
#pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing
En resumen, la función de vértice se llama para cada vértice del modelo 3D y decide cómo superponerla en el espacio de pantalla bidimensional. En este tutorial, solo estamos interesados en cómo mover un objeto.
El parámetro
appdata_full v
contiene el campo de
vertex
, que contiene la posición 3D de cada vértice en
el espacio del objeto . Cuando el valor cambia, el vértice cambia. Es decir, por ejemplo, el código que se muestra a continuación transferirá el objeto con su sombreador una unidad a lo largo del eje 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 defecto, los juegos 2D creados en Unity funcionan solo con los ejes X e Y, por lo que debemos cambiar
v.vertex.xy
para mover el sprite en un plano bidimensional.
¿Qué es el espacio de objetos?El campo de vertex
de la estructura appdata_full
contiene la posición del vértice actual procesada por el sombreador en el espacio del objeto. Esta es la posición del vértice bajo el supuesto de que el objeto está ubicado en el centro del mundo (0,0,0), sin cambiar la escala y sin rotación.
Los picos expresados en el espacio mundial reflejan su posición real en la escena de Unity.
¿Por qué el objeto no se mueve a una velocidad de un metro por cuadro?Si agregamos +1 al componente x
del valor transform.position
en el método Update
de un script C #, veremos cómo el objeto vuela hacia la derecha a una velocidad de 1 metro por cuadro, es decir, unos 216 kilómetros por hora.
Esto se debe a que los cambios que C # realiza están cambiando la posición misma. En la función de vértice, esto no sucede. El sombreador solo cambia la representación visual del modelo, pero no actualiza ni modifica los vértices almacenados del modelo. Es por eso que agregar +1 a v.vertex.x
cambia un objeto por un metro solo una vez.
¡Recuerde importar el sprite como Tight!Este efecto desplaza la parte superior del sprite. Tradicionalmente, los sprites se importan a Unity como cuadrángulos (ver la figura de la izquierda). Esto significa que solo tienen cuatro picos. Si es así, solo se pueden mover estos puntos, lo que reduce la fuerza del efecto de doodle.
Para una distorsión más fuerte y realista, debe importar los sprites eligiendo
Tight para el parámetro
Mesh Type , que los convierte en una forma convexa (vea la figura de la derecha).
Esto aumenta el número de vértices. Esto no siempre es deseable, pero eso es exactamente lo que ahora necesitamos.
Desplazamiento aleatorio
El efecto de doodle cambia aleatoriamente la posición de cada vértice. El muestreo de
números aleatorios en un sombreador siempre ha sido una tarea difícil. Esto se debe principalmente a la arquitectura de GPU distribuida, que complica y reduce la eficiencia de la recreación del algoritmo utilizado en la mayoría de las bibliotecas (incluido
Mathf.Random
).
La publicación de Nick Caman utilizó una textura de ruido que, cuando se muestrea, da la ilusión de aleatoriedad. En el contexto de su proyecto, este puede no ser el enfoque más efectivo, porque en este caso se duplica el número de operaciones de búsqueda de texturas realizadas por el sombreador.
Por lo tanto, en la mayoría de los sombreadores, se utilizan funciones bastante confusas y caóticas que, a pesar de su determinismo, nos parecen no tener regularidades. Y dado que deben distribuirse, cada número aleatorio debe generarse con su propia semilla. Esto es genial para nosotros, porque la posición de cada vértice debe ser única. Podemos usar esto para unir un número aleatorio a cada vértice. Discutiremos la implementación de esta función aleatoria más adelante; llamémoslo
random3
.
Podemos usar
random3
para generar un desplazamiento aleatorio de cada vértice. En el siguiente ejemplo, los números aleatorios se escalan utilizando la propiedad
_NoiseScale
, que le permite controlar la fuerza de desplazamiento.
void vert (inout appdata_full v, out Input o) { ... float2 noise = random3(v.vertex.xyz).xy * _NoiseScale; v.vertex.xy += noise; ... }
Ahora necesitamos escribir código
random3
.
Aleatoriedad en shader
Una de las funciones pseudoaleatorias más comunes e icónicas utilizadas en los sombreadores se tomó de un artículo de 1998 de W. Ray bajo el título "
Al generar números aleatorios, con la ayuda de y = [(a + x) sin (bx)] mod 1 ".
float rand(float2 co) { return fract(sin(dot(co.xy ,float2(12.9898,78.233))) * 43758.5453); }
Esta función es determinista (es decir, no es
realmente aleatoria), pero se comporta de manera tan aleatoria que parece completamente aleatoria. Dichas funciones se llaman
pseudoaleatorias . Para mi tutorial, elegí una función más compleja, inventada por
Nikita Miropolsky.Generar un número pseudoaleatorio en un sombreador es un tema muy complejo. Si estás interesado en aprender más sobre ella,
The Book of Shaders tiene un buen capítulo sobre ella. Además,
Patricio Gonzales Vivo ha creado un gran repositorio de funciones pseudoaleatorias llamado
ruido GLSL , que puede usarse en sombreadores.
Paso 3: agrega tiempo
Gracias al código que escribimos, cada punto en cada cuadro se desplaza en la misma cantidad. Entonces obtenemos un sprite distorsionado, no un efecto de doodle. Para solucionar esto, debe encontrar una manera de cambiar el efecto con el tiempo. Una de las formas más fáciles de hacer esto es usar la posición del vértice y la hora actual para generar un número aleatorio.
En nuestro caso, acabo de agregar la hora actual en segundos
_Time.y
a la posición del vértice.
float time = float3(_Time.y, 0, 0); float2 noise = random3(v.vertex.xyz + time).xy * _NoiseScale; v.vertex.xy += noise;
Los efectos más complejos pueden requerir formas más sofisticadas para agregar tiempo a la ecuación. Pero como solo estamos interesados en un efecto aleatorio intermitente, dos valores son más que suficientes.
Cambio de hora
El principal problema al agregar
_Time.y
es que hace que el sprite se anime en cada cuadro. Esto no es deseable para nosotros, porque la mayoría de las animaciones dibujadas a mano tienen una baja velocidad de cuadros. El componente de tiempo no debe ser continuo, sino discreto. Esto significa que si queremos mostrar cinco cuadros por segundo, entonces debería cambiar solo cinco veces por segundo. Es decir, el tiempo debe estar
vinculado a un quinto de segundo. Los únicos valores válidos deben ser
,
,
,
,
,
con, y así sucesivamente ...
Ya cubrí snap en mi blog en
Cómo ajustar a la cuadrícula . En este artículo, propuse una solución al problema de vincular la posición de un objeto en una cuadrícula espacial. Si necesitamos vincular el tiempo a una cuadrícula de tiempo, entonces las matemáticas y, por lo tanto, el código serán los mismos.
La función que se muestra a continuación toma el número
x
y lo vincula a valores enteros que son múltiplos de
snap
.
inline float snap (float x, float snap) { return snap * round(x / snap); }
Es decir, nuestro código se convierte así:
float time = snap(_Time.y, _NoiseSnap); float2 noise = random3(v.vertex.xyz + float3(time, 0.0, 0.0) ).xy * _NoiseScale; v.vertex.xy += noise;
Conclusión
El paquete Unity para este efecto se puede descargar de forma gratuita
en Patreon .
Recursos Adicionales
En los últimos meses, ha aparecido una gran cantidad de juegos al estilo de los garabatos. ¡Me parece que la razón de esto fue el éxito de
Doodle Studio 95! - Una herramienta para la Unidad, desarrollada por
Fernando Ramallo . Si este estilo es adecuado para tu juego, te recomiendo comprar esta increíble herramienta.