Consejos de gráficos y renderizado de Survival Kids

STEVEN CANNAVAN AND DANIEL REIDLER / UNITYSurvival Kids
Aug 20, 2025|8 min.
Juego en Survival Kids
Para tu comodidad, tradujimos esta página mediante traducción automática. No podemos garantizar la precisión ni la confiabilidad del contenido traducido. Si tienes alguna duda sobre la precisión del contenido traducido, consulta la versión oficial en inglés de la página web.

Este verano, Unity lanzó el primer juego desarrollado de principio a fin internamente, una actualización del juego familiar cooperativo Survival Kids, en asociación con KONAMI. El juego fue construido por un pequeño equipo interno de unas 20 personas como máximo, por lo que el equipo tuvo que encontrar formas innovadoras de mantenerse dentro del alcance del proyecto y el cronograma de lanzamiento con recursos limitados, al igual que cualquier estudio indie. En esta publicación, profundizamos en cómo creamos el marco visual y el renderizado del juego.

Definiendo la identidad visual

Queríamos lograr algo visualmente interesante. Nuestros objetivos eran muy artísticos, pero también queríamos hacerlo muy económico en términos de rendimiento ya que no sabíamos con qué tipo de capacidades de dispositivo estaríamos trabajando al principio.

La primera parte del proyecto fue simplemente explorar visualmente; teníamos un diorama artístico que estábamos usando para mostrar cómo imaginábamos que sería el arte. Parte de eso es una configuración de iluminación muy estilizada, incluyendo sombras personalizadas.

Optamos por el Universal Render Pipeline (URP) ya que tiene un gran historial de rendimiento en una amplia gama de dispositivos, y es relativamente fácil crear cualquier nueva característica que necesitemos para alcanzar los objetivos visuales del juego. El marco renderizado está muy cerca del URP estándar en modo Forward, ya que el juego tiene principalmente solo una fuente de luz, el sol. Tenemos algunas modificaciones aquí y allá, como las sombras personalizadas, la oclusión ambiental y un par de otras características de renderizado personalizadas, pero en general es URP estándar en pantalla.

Juego en Survival Kids
Juego en Survival Kids

La mayor adición fue a los shaders para soportar el aspecto muy específico de la dirección artística, ya que necesitábamos hacer modificaciones en cómo se calculaba la iluminación. Hacer shaders personalizados no es particularmente nuevo, sin embargo, escribimos nuestros propios objetivos de Shader Graph personalizados para asegurar que cualquiera pudiera contribuir. Usar AssemblyDefinitionReferences nos permitió agregar objetivos de Shader Graph específicos del proyecto sin necesidad de tener una versión URP completamente personalizada. Esto nos permitió mantenernos en el URP estándar con solo nuestros objetivos de Shader Graph locales, lo que funcionó muy bien para nuestro proyecto.

Iluminación e iluminación global

Uno de nuestros objetivos era tener iluminación dinámica; queríamos la opción de poder cambiar el color de la iluminación, la intensidad, etc. Eso significaba que no podíamos hornear fácilmente la información de iluminación usando lightmaps, por lo que nos perderíamos algunos de los detalles de iluminación que obtendrías al hornear en la iluminación de rebote / iluminación global. Necesitábamos pensar en diferentes formas de equilibrar una alta calidad visual y un buen rendimiento con un enfoque de iluminación dinámica, ya que normalmente es más costoso. Esto nos llevó a usar LightProbes inicialmente y también a depender más del Ambient Occlusion (AO) para ayudar a anclar los objetos.

Información de iluminación e iluminación global en un entorno de Survival Kids
Información de iluminación e iluminación global

Porque sabíamos que la iluminación global iba a ser muy importante para este proyecto, inicialmente implementamos una solución personalizada que actualizaría LightProbes en tiempo de ejecución. Pero luego, cuando pasamos a Unity 6, el equipo realmente quería cambiar a Adaptive Probe Volumes (APVs) porque la calidad visual era considerablemente mejor que el sistema que habíamos ensamblado mientras mantenía un impacto de rendimiento comparable. Cuando tienes la opción de actualizar de algo bueno a algo realmente bueno que es de alta calidad y eficiente, simplemente cambias.

Océano

El océano se basó en gran medida en un proyecto de demostración de Unity URP Boat Attack, pero con un aspecto más estilizado. Una de las cosas que realmente queríamos hacer era tener estela saliendo de la isla y otros elementos en el agua. Esto generalmente se implementa utilizando el búfer de profundidad para determinar la costa por distancia, pero realmente no tenemos una costa, tenemos una isla Whurtle.

La estela ocurre en el agua del océano a una distancia establecida de la costa de un pequeño entorno insular.
El Signed Distance Field (SDF) crea estelas a una distancia establecida de la costa.

Con la isla Whurtle, tienes un descenso repentino, y no hay suficiente caída de profundidad para el efecto, especialmente teniendo en cuenta el terreno sumergido bajo el agua. La mejor idea que se nos ocurrió fue usar un campo de distancia firmada, o SDF; es básicamente una textura que codifica la distancia firmada de un objeto, o, en nuestro caso, la costa. De esta manera, podemos comenzar la estela a una cierta distancia de la costa, luego usar una onda sinusoidal y algunas texturas de distorsión para darle un aspecto interesante.

Al final, tuvimos una herramienta de Editor que horneaba la distancia firmada para la costa basada en cuatro alturas de agua establecidas. Luego hicimos algunas mezclas y interpolaciones entre ellas para una aproximación aproximada de dónde estaba realmente la costa, ya que el nivel del agua en la mayoría de los niveles cambia dependiendo del progreso del jugador. Dependimos de esta información SDF precocinada para varios efectos diferentes, desde ajustar la altura de las olas del océano hasta agregar espuma, estela y caústicas.


Desglose de un fotograma
Desglose de alto nivel de un fotograma renderizado
Desglose de alto nivel de un marco renderizado, donde los pases marcados en rojo son personalizados
Interacción visual

Para interacciones visuales, se renderiza una cápsula desde una vista de arriba hacia abajo alrededor de cualquier cosa cuya posición necesitáramos rastrear, como jugadores, objetos transportables, herramientas, etc., en un RenderTexture. La textura se basa en el espacio mundial con una ventana deslizante a medida que la cámara del jugador se mueve.

Generamos un desplazamiento (rojo, azul) desde el centro de la cápsula, así como información de altura en el espacio mundial (verde). En el canal alfa, almacenamos un valor de caída para la fuerza. Eso se utiliza luego por diferentes shaders para crear efectos como la inclinación de la vegetación, ondas animadas en superficies de agua, o oscurecer un poco el terreno para crear un efecto de sombra muy suave.


Se aplican shaders a una "cápsula" RenderTexture alrededor de jugadores y objetos para crear efectos que los hagan parecer más anclados en el mundo, como sombras o estelas en el agua.
Se aplican shaders a una "cápsula" RenderTexture alrededor de cada jugador y objeto transportable para crear efectos que los hagan parecer más anclados en el mundo, como sombras o estelas en el agua.
Pre-paso de profundidad y dithering

Para una optimización de rendimiento, utilizamos un prepaso de profundidad, que llena el búfer de profundidad antes de renderizar objetos normalmente, reduciendo el costo de renderizar esos objetos debido al rechazo temprano de la prueba de profundidad.

Un stencil utilizado para desenfocar en el paso SMAA y prellenar profundidad con un patrón de dithering para ver detrás de la geometría
Un stencil utilizado para desenfocar en el paso SMAA y prellenar profundidad con un patrón de dithering para ver detrás de la geometría

Tratamos los objetos con dithering por separado en un pase personalizado porque necesitamos renderizarlos de manera diferente dependiendo de su estado y qué jugador los está viendo. Están en una capa de GameObject diferente que está excluida de la Máscara de Capa Opaca en el renderizador, por lo que no se renderizan automáticamente, y esto significa que necesitamos renderizarlos en un pase personalizado. Utilizamos MaterialPropertyBlocks para establecer valores individuales para los objetos y aplicamos stencils para marcar los objetos que están con dithering para que podamos desenfocar esas secciones más adelante. Sin embargo, dado que esto rompe el agrupamiento SRP, necesitábamos limitar su uso. Decidimos aplicar MaterialPropertyBlocks solo según sea necesario y eliminarlos cuando terminamos, restaurando los objetos a un estado agrupable.

Al final, tenemos un pase completo que solo se ocupa de cómo renderizamos esa capa particular en el búfer de profundidad. A continuación, aplicamos un stencil en el búfer de profundidad para marcar qué píxeles son parte de los objetos que estamos desvaneciendo, y luego eso se utiliza más tarde cuando estamos haciendo anti-aliasing.

Sombras de gradiente

Parte de nuestro estilo artístico era tener sombras de colores con un degradado a lo largo de la dirección de la sombra. Para lograr esto, generamos una textura personalizada en el espacio de pantalla a partir de un RenderFeature que muestreaba el mapa de sombras en el espacio mundial, pero también miraba hacia adelante en el plano XZ para determinar un valor de mezcla de sombras. Esto es similar a un filtro PCF utilizado en sombras suaves, pero en una dirección. Esto se renderizó en una textura reducida a aproximadamente un cuarto del tamaño de la pantalla, y luego mezclamos el color de la sombra entre tres colores.

Un pase de espacio de pantalla de baja resolución en las sombras genera los colores del degradado de borde y crea un degradado en la dirección de la luz. Luego se basa en el muestreo bilineal más adelante.
Un pase de espacio de pantalla de baja resolución en las sombras genera los colores del degradado de borde y crea un degradado en la dirección de la luz. Luego se basa en el muestreo bilineal más adelante.
MSVAO (oclusión ambiental volumétrica multi-escala)

Desafortunadamente para nosotros, el SSAO proporcionado con URP no se ajustaba del todo a nuestras necesidades. Si bien es una implementación amigable para móviles, para el aspecto que buscábamos necesitábamos establecer el valor del radio bastante alto, lo que consumió una parte significativa de nuestro presupuesto de fotogramas (~4ms). En su lugar, reutilizamos la implementación de MSVAO del antiguo paquete PostProcessing Stack v2, con algunos cambios menores para hacerlo más eficiente e integrar nuestro color de sombra.

Perspectiva de una escena de isla que muestra una versión personalizada del MSVAO del paquete PostProcessing Stack v2, con ajustes menores para hacerlo más eficiente en una plataforma objetivo.
Una versión personalizada del MSVAO del paquete PostProcessing Stack v2, con ajustes menores para hacerlo más eficiente en una plataforma objetivo.
Dibujando la escena

Survival Kids tiene los pases de renderizado estándar que esperas en URP (Opaco, Skybox, Transparencia), pero también tenemos un pase adicional para manejar nuestros objetos difuminados, justo después del pase opaco. Aquí es donde realmente renderizaremos nuestra geometría difuminada debido al hecho de que la geometría en esta capa no se renderiza en el pase opaco. También hacemos una prueba de igualdad de profundidad en este pase para asegurarnos de que solo renderizamos donde prellenamos el búfer de profundidad.

Dibujo de la geometría (prueba de igualdad de profundidad) para nuestros objetos difuminados. La oclusión ambiental está desactivada en este estado.
Dibujo de la geometría (prueba de igualdad de profundidad) para nuestros objetos difuminados. La oclusión ambiental está desactivada en este estado.

Para los objetos que están difuminados, necesitamos desactivar la oclusión ambiental en ellos debido a los artefactos que ocurrirán debido a que MSVAO trata los "agujeros" en el búfer de profundidad como oclusión.

Imagen del juego que muestra los efectos visuales de usar SMAA + desenfoque en el difuminado.
SMAA + desenfoque en dithering

Después de que la escena se renderiza, aplicamos nuestro anti-aliasing. Desafortunadamente, las áreas que están dithering causarán problemas al algoritmo (SMAA), causando artefactos visuales. Para evitar esto, necesitamos tratar estas áreas por separado. Las áreas que están dithering (determinadas por el stencil) se desenfocan, produciendo un efecto de mezcla alfa en esas áreas, y luego se procesa SMAA en las áreas que no están dithering. Esto se omite en ciertas circunstancias, pero terminamos con una imagen final limpia lista para el post procesamiento.

Post-procesamiento y UI

Mantenemos nuestros efectos de post-procesamiento lo más económicos posible, utilizando solo un poco de Tonemapping, Bloom y Corrección de Color.

En un momento, usamos el desenfoque de URP en el post-procesamiento para suavizar el juego detrás de la UI, pero lo reemplazamos con un desenfoque Kawase RenderFeature más económico más adelante. Nuestro sistema de UI está construido sobre UGUI con un poco de renderizado personalizado para el desvanecimiento.

UI en Survival Kids
UI en Survival Kids

La forma en que inicialmente configuramos nuestra UI, estábamos desvaneciendo menús dentro y fuera, pero este enfoque causó algunos problemas debido a cómo se realiza el alfa para la UI. Al principio, comenzamos a renderizar la UI en una textura separada a través de una cámara, luego la bliteamos correctamente para que pudiéramos desvanecer la UI en la imagen principal, cambiamos esto para que pudiera lograrse utilizando un RenderFeature en lugar de usar una cámara extra completa.

Esta publicación ofrece solo un vistazo a cómo configuramos gráficos y renderizado estilizados y eficientes para alcanzar nuestra tasa objetivo para Survival Kids. Mantente atento al blog de Unity para más publicaciones sobre el juego, incluyendo un análisis en dos partes sobre redes multijugador y una mirada a los terrenos y flujos de trabajo del equipo, o consulta más historias técnicas de desarrolladores en nuestra Página de Recursos.