Efecto de sombreado de Doodle

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 .

imagen

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  frac05=0,  frac15=0.2,  frac25=0.4,  frac35=0.6,  frac45=0.8,  frac55=1con, 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.

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


All Articles