Hola a todos! Durante los 4 años completos no he escrito en Habr. Mi última
serie de publicaciones fue sobre varias herramientas y trucos que utilizamos en nuestro último juego (desarrollándolo en Unity). Desde entonces, lanzamos con éxito el juego y también lanzamos uno nuevo. Así que ahora puedes exhalar un poco y escribir algunos artículos nuevos que pueden ser útiles para alguien.
Hoy quiero hablar sobre los trucos gráficos y trucos que utilizamos para crear la imagen que ves en el GIF de arriba.
Somos muy sensibles a las imágenes de nuestros juegos y, por lo tanto, hemos invertido mucho tiempo y esfuerzo en varios efectos y otros beneficios que harían nuestro pixel art lo más atractivo posible. Quizás alguien encuentre algo útil para sí mismo.
Para comenzar, enumeraré brevemente cuál será la imagen en nuestro juego:
- Luz ambiental variable: un cambio banal en la iluminación según la hora del día.
- Corrección de color LUT: es responsable de cambiar el tono de la imagen según la hora del día (o el tipo de zona).
- Fuentes de luz dinámicas: antorchas, estufas, lámparas.
- Mapas normales: responsables de dar volumen a los objetos, especialmente al mover fuentes de luz.
- Matemáticas de la distribución de luz 3D: es responsable de garantizar que la fuente de luz en el centro de la pantalla ilumine correctamente un objeto que es más alto, pero no ilumina un objeto que es más bajo (es decir, girado hacia la cámara con el lado apagado).
- Sombras: formadas por sprites, rotan y responden a la posición de las fuentes de luz.
- Simulación de la altura de los objetos: para la correcta visualización de la niebla.
- Otros decoradores: lluvia, viento, animaciones (incluida la animación de sombreado de follaje y hierba), etc.
Ahora con más detalle.
Luz ambiental variable
Aquí, en principio, nada especial. Por la noche, más oscuro, durante el día, más claro. El color de la luz lo establece el gradiente de tiempo. Por la noche, la fuente de luz no solo se oscurece, sino que adquiere un tinte azul.
Se ve así:
Corrección de color LUT
LUT (tabla de consulta): tablas de intercambio de colores. Hablando en términos generales, esta es una matriz RGB tridimensional donde en cada nodo hay un valor de color, que debe ser reemplazado por el correspondiente. Es decir, si un punto rojo se encuentra en las coordenadas (1, 1, 1), esto significa que todo el color blanco en la imagen será reemplazado por rojo. Si las coordenadas (1, 1, 1) son blancas (R = 1, G = 1, B = 1), entonces no hay cambio. En consecuencia, la LUT sin cambios tiene un color para cada coordenada correspondiente a estas mismas coordenadas. Es decir en el punto (0.4, 0.5, 0.8) está el color (R = 0.4, G = 0.5, B = 0.8).
Bueno, vale la pena señalar que, por conveniencia, presentan una textura 3D como bidimensional. Por ejemplo, así es como se ve la LUT "predeterminada" (sin cambiar la reproducción del color):

Se implementa de manera elemental, funciona de manera rápida y conveniente.
También es muy fácil de configurar: le das al artista cualquier imagen del juego y dices "tono de color para que parezca de noche". Después de eso, aplique todas las capas de corrección de color a la LUT predeterminada y obtenga la LUT de la tarde.
En nuestro caso, el artista se atascó un poco y creó hasta 10 LUT diferentes para diferentes momentos del día (noche, crepúsculo, tarde, etc.). Así es como se ve su configuración:
Como resultado, dependiendo de la hora del día, la misma ubicación se ve diferente:

Aquí, la transparencia de los sprites de luz de las ventanas también cambia según la hora del día.
Fuentes de luz dinámicas y mapas normales.
Las fuentes de luz se utilizan absolutamente ordinarias, desde Unity. Además, se dibujan mapas normales para cada sprite, lo que le permite tener una sensación de volumen.
Tales normales se dibujan de manera bastante simple. El artista pinta aproximadamente una luz con un pincel desde 4 lados:
Y luego este script va al mapa normal:
Si está buscando un sombreador (y software) que haga esto, puede mirar en la dirección de Sprite Lamp.
Simulación de luz 3D
Esto es un poco más complicado. No puedes simplemente recoger y resaltar sprites. Necesitamos considerar si el sprite está "detrás" de la fuente de luz o "al frente".
Presta atención a esta imagen:
Ambos árboles están a la misma distancia de la fuente de luz, sin embargo, el árbol distante está iluminado y el árbol más cercano no (porque su parte no iluminada se gira hacia la cámara).
Resolví este problema de manera bastante simple. El sombreador calcula la distancia a lo largo del eje vertical y entre la fuente de luz y el sprite. Y si es positivo (la fuente de luz antes del sprite), entonces iluminamos el sprite como de costumbre, pero si es negativo (el sprite bloquea la fuente de luz), pero la intensidad de la luz decae mucho desde una distancia con un coeficiente muy grande. Es precisamente el coeficiente que se hizo, y no solo "sin iluminación", de modo que cuando la fuente de luz se mueve y aparece repentinamente detrás del sprite, el sprite no se vuelve negro instantáneamente, sino gradualmente. Pero aún así bastante rápido.
Sombras
Las sombras están formadas por sprites que giran alrededor de un punto. Intenté agregarles más compresión (sesgo), pero resultó ser innecesario.
En total, cada objeto puede tener un máximo de 4 sombras. Uno es del Sol, y tres son de fuentes de luz dinámicas. La siguiente imagen muestra el principio:

La tarea "encontrar las siguientes 3 fuentes de luz y calcular la distancia / ángulo de las sombras hacia ellas" se resuelve mediante un script que gira en Actualización. Sí, no funciona muy rápido, porque Tienes que hacer muchas matemáticas. Si escribiera ahora, usaría los sistemas novedosos de trabajos paralelos en Unity. Pero entonces esto aún no era así, así que simplemente optimicé los scripts ordinarios tanto como pude.
Lo único que importa es que hice que la rotación del sprite no se transformara, sino dentro del sombreador de vértices. Es decir la rotación no se mueve. Es solo que un parámetro está configurado en el sprite (usé el color para esto, porque de todos modos, todas las sombras son negras), y el sombreador ya es responsable de la rotación del sprite. Esto es más rápido porque no tienes que tirar de la geometría en Unity.
Otro inconveniente de este enfoque es que las sombras deben configurarse (y a veces pintarse) individualmente para cada objeto. Es cierto que, probablemente, logramos con una docena de sprites diferentes más o menos universales (delgados, gruesos, ovalados, etc.).
La segunda desventaja es que a veces es difícil hacer una sombra para un objeto cuyo punto de contacto con la tierra es muy alargado. Por ejemplo, mira la sombra de la cerca:
No perfecto Esto es lo que parece si hace que el sprite de la cerca sea translúcido:
Aquí, sin embargo, vale la pena señalar que el sprite todavía está muy deformado verticalmente (el sprite de sombra original se ve casi como un círculo). Es por eso que su turno no parece tanto un giro, sino una distorsión.
Simulaciones de niebla y altura
Hay niebla en el juego. Se ve así (arriba es la versión normal, debajo hay una niebla extrema del 100% para demostrar el efecto).

Como puede ver, las copas de las casas y los árboles sobresalen de la niebla. De hecho, lograr este efecto fue bastante simple. La niebla consta de muchas nubes horizontales que se distribuyen a lo largo de la profundidad del escenario. Y como resultado, resulta que la parte superior de todos los sprites está bloqueada por menos sprites de niebla:

El viento
Pixel art wind es otra historia. No hay muchas opciones O animar con las manos (que es casi imposible con nuestra cantidad de arte), o escribir un sombreador deformante, pero a veces tienes que soportar distorsiones feas. Puede, por supuesto, no animar en absoluto, pero la imagen se ve inanimada.
Elegimos la opción de distorsión usando el sombreador. Se ve así:
Si aplica este sombreador a una textura a cuadros, queda claro lo que sucede:
También vale la pena señalar que no estamos animando toda la corona, sino solo hojas individuales:
También sacudimos el trigo al viento, pero todo es simple: el sombreador de vértices deforma las coordenadas x, teniendo en cuenta el componente y. Cuanto más alto sea el punto, más fuerte será el escalonamiento. Esto se hace para que solo los principales se tambaleen, pero la raíz no. Además, la fase de oscilación cambia de las coordenadas x / y para que los diferentes sprites en la pantalla oscilen aleatoriamente.
El mismo sombreador también se usa para crear el efecto de balancear el trigo y la hierba cuando un jugador los atraviesa.
Eso es probablemente todo por ahora. Intencionalmente no abordé el tema de la construcción de la escena y su geometría, porque Esto es material para un artículo separado. Por lo demás, habló sobre las principales soluciones que se utilizaron en el desarrollo.
PD: Si alguien está interesado en algunos aspectos técnicos, escriba los comentarios. Quizás lo cuente en otro artículo. A menos, por supuesto, necesario.
PPS: Aprovecho esta oportunidad para decir que ahora queremos encontrar varias personas competentes en el equipo (programador, PM, KM, artista). Los detalles están en el sitio web del estudio. Espero que esta frase no haya violado las reglas.