Dibujando múltiples objetos en Unity sin usar Mesh Renderer

@phillshaw 2019

En la mayoría de los juegos open world, el pasto es un elemento visual bastante común, que apoya a crear una imagen realista del juego. Para ello, es necesario dibujar una gran cantidad de polígonos que se mostrarán en la imagen y que se mostrarán como pasto al jugador.

Por supuesto, no es el único tipo de objeto que se renderiza en grandes cantidades. Otros ejemplos son los grupos de árboles (incluidas sus hojas), rocas, partículas personalizadas, grupos de peces o aves, o un campo de asteroides. En pocas palabras, elementos visuales que son relativamente repetitivos y utilizados para ocupar espacios o producir un efecto visual.

En Unity, la forma más fácil de renderizar objetos es utilizando un GameObject con los componentes MeshFilter y MeshRenderer, utilizando un mesh y materiales asociados para dibujar la geometría. Sin embargo, al tratar de renderizar miles de meshes, el rendimiento será disminuido.

Renderizado de 40000 meshes utilizando Mesh Renderer
Estadísticas de la imagen anterior

Este rendimiento para un campo de pasto muy sencillo es inaceptable, ya que, sin considerar otros elementos de gameplay y visuales, el motor de juego no puede producir las imágenes con suficiente velocidad para una experiencia fluida.

Sin embargo, Unity también ofrece la posibilidad de dibujar geometría de modo manual, mediante la clase Graphics y el método DrawMeshInstanced. La siguiente imagen demuestra cómo se vería el resultado. Esto se conoce como GPU Instancing.

Renderizado de 40000 meshes con GPU Instancing
Estadísticas de la imagen anterior usando Instancing

Como pueden ver, utilizando Instancing, el conteo de FPS aumenta casi al doble, mientras la duración de los threads se reduce significativamente. Las diferencias visuales que se aprecian en la imagen se deben al orden en que se renderizaron los objetos y el shader utilizado, pero con esta limitante, la mejora en el rendimiento es significativa.

Para utilizar este método, necesitamos un mesh (Unity recomienda que tenga más de 256 vertices), un material preparado para GPU instancing, generar las matrices de transformación para cada instancia, y el código para dibujar la geometría.

Dentro de Unity (aplica para otros engines también), y de forma simplificada, una matriz de transformación es un arreglo numérico de dos dimensiones que sirve para mover, escalar y rotar objetos, posiciones y direcciones. Por ejemplo, la siguiente matriz de transformación podría representar un objeto en las coordenadas (2,4,-1), y con una escala uniforme de 1.

Matriz de transformación

Para Instancing, usaremos matrices semejantes para describir las posiciones, rotación y escala de los meshes que queremos generar. Para esto, usaremos el método Matrix4x4.TRS para generar esas matrices.

El siguiente bloque de código demuestra cómo utilizar esta función junto con DrawMeshInstanced, generando las matrices de transformación al inicializarse y renderizar las instancias en cada ciclo de Update. La clase se encarga de generar una las matrices de transformación para cada objeto al inicializar el GameObject. Cabe mencionar que DrawMeshInstanced sólo permite renderizar hasta 1023 objetos por invocación, por lo que la clase agrupa las matrices en arreglos de ese tamaño máximo, y guarda cada grupo en otra lista, que se usa durante el Update para dibujar la geometría.

Aquí está un ejemplo de un material activado para GPU instancing. Esto es necesario para los materiales que queramos utilizar con esta técnica.

Ejemplo de material preparado para GPU Instancing.

Cabe mencionar que la técnica puede no funcionar en todos los sistemas, ya que deben contar con un GPU capaz de hacer Instancing. Así mismo, con esta técnica no es posible utilizar meshes animados con un esqueleto (SkinnedMeshRenderers), ya que las posiciones de cada hueso son calculadas en el lado del CPU y envíadas al GPU, rompiendo con el ciclo continuo necesario para la optimización. Sin embargo, es posible utilizar animaciones basadas en shaders. De igual forma, algunas optimizaciones especificas de Unity pueden lograrse con DOTS.

Para más información, puedes revisar la siguiente documentación:

Ojalá les haya servido esta información, estoy seguro que abre nuevas posibilidades para renderizar objetos y ambientes más complejos.

Postmortem: Super Clean the Place

¡Al fin! Después de un par de meses medio atareados, me di chance de escribir esta entrada, para compartir el último juego que hice, junto con un poco de la experiencia y lo que significó para mí desarrollarlo, junto con varios aprendizajes. Esta vez, no será muy técnico, pero creo que será una buena vista general de mi proceso creativo.

Descarga

Antes de spoilear todo el juego, pongo primero los enlaces al juego, tanto para PC como para Mac. La versión de Mac es completamente experimental, ya que no tengo un equipo nativo para hacer las pruebas como se deben (se aceptan donaciones en especie).

¡Disfrutenlo!

Descarga aquí

Todo empezó en un Game jam

En febrero de 2021, en un grupo de game devs al que asisto, organizaron un game jam de un mes, ya que la mayoría de los involucrados lo hacen por hobby que por otra cosa. 

El tema del jam, “One Rule”.

Con esto en mente, me animé a participar, ya que hace tiempo yo quería crear un juego de jam por mi mismo como reto personal. Además, el último juego de jam en el que había participado era de un par de años, así que las ganas no hicieron esperar.

Mi idea original era hacer un juego en el que acomodaras cosas, para que al momento de proyectar una sombra, produzcan una imagen. La idea es parecida a algunas esculturas o instalaciones, en las que el artista hace un acomodo aparentemente aleatorio de las cosas, pero vistas desde cierta perspectiva, producen una silueta o imagen.

Como parte de todo proceso, esta idea evolucionó en otra, problemas aparecieron, dudas surgieron. Y también, oportunidades de aprender más y dar rienda suelta a la creatividad.

Cambiar ideas

No importa si te dan mucho o poco tiempo para desarrollar un juego en un jam, el tiempo nunca es suficiente. Y más, cuando te das cuenta que la idea no es, en una palabra, divertida.

El concepto estaba interesante, pero rápidamente le perdí el gusto y me pareció demasiado simple. Para esto, ya había consumido aproximadamente la mitad del mes en desarrollar lo que tenía. Decidí pivotar sobre lo construido y plantear otra idea.

En esta ocasión, el juego sería, de igual manera, organizar cosas, pero esta vez, con el propósito de quitar lo que sobrara.

Es decir, limpiar un cuarto.

De principio, se me hizo rara la idea, por lo que decidí que fuera una experiencia relajante, a la vez que graciosa. Inspirado en los juegos donde la física es muy exagerada y las cosas explotan, preferí que lo más posible fuera controlado con físicas, y que tuvieras que hacer el mínimo desorden y la máxima limpieza para pasar el nivel.

Había completado la idea de “Super Clean The Place”.

Primeras pruebas de concepto

Sobre-ingeniería

Pensar demasiado las cosas, tratar de crear con base a un ideal, es un propósito noble pero poco efectivo, y más cuando cuentas con recursos limitados. Dígase, mano de obra y tiempo.

Algo que me suele suceder al desarrollar un programa, y en especial un videojuego, es que trato de hacer el código ideal, perfecto y prístino. Un código imposible de hacer, por supuesto.

Si bien, vale la pena seguir lo más posible las mejores prácticas y técnicas, no hay que dejar de lado las limitaciones que contamos. Para este proyecto, no tomar en cuenta esto me entorpeció mucho, ya que en numerosas ocasiones tuve que refactorizar código “ideal” por algo que me permitiría continuar el desarrollo, a pesar de no ser “elegante”. Y más, porque tuve que pivotar la idea.

Lección aprendida, ser consciente de balancear calidad de código con requerimientos de entrega.

Crear modelos 3D

Si bien mi “main” es la programación, los aspectos artísticos de un videojuego me fascinan de la misma forma. Si bien, no es mi aspiración ser un super artista, si me gustaría poder crear assets simples pero agradables.

Así que, para este jam, decidí retomar mis conocimientos de 3D en Blender. Ya había hecho modelos en 3D en el pasado, pero en esta ocasión, mi meta sería hacer todos los assets por mi cuenta, por lo que hice lo que mejor puedo hacer, modelos inorgánicos.

Para reducir la dificultad y complejidad de crear cada modelo, así como tener que ajustarlos después, traté de hacerlos lo más simple y modificables posible, por lo que una geometría low poly se volvió una buena opción, sin irse al realismo. Para los materiales, trate que fueran colores simples, para que le diera un acabado plano y que la iluminación se encargue de dar más detalle. También, aproveché al máximo los modificadores de Blender, para poder modificar los detalles con mayor rapidez, y dado el caso, reutilizar modelos como base para otros.

Y los resultados, hablan por sí mismos. No serán los mejores, no se verán en un Asset Store, pero vamos, a algunas cadenas de muebles les va a interesar.

Modelos en Blender

Crear música

Al igual que el arte, también quise probar con componer algo de música, ya que, gracias a la pandemia, empecé a tomar algunas clases de música y composición, así que era perfecto momento para practicar y probar suerte.

Dado que el concepto era ser un juego relajante, sin muchas presiones, más que el deseo de limpiar, decidí que la música fuera simple y ambiental, usando pocos instrumentos y notas simples. Para esto, utilicé Ableton para hacer la composición, y aproveché al máximo el audio e instrumentos que vienen incluidos. Grabé algo de audio para usarlo como samples en percusiones y algo de textura.

Si algo me demostraron, tanto el arte como la música, fue que son procesos creativos increíbles y únicos en el desarrollo de un juego. 

Composición de música en Ableton

¡Divertirse!

Nunca está de sobra decirlo. Un jam es para disfrutarse, experimentar, probar cosas nuevas, y dejarse llevar por el proceso. Por mucho tiempo, había olvidado eso, y las nuevas ideas que empezaban con una mentalidad muy rígida, demasiado exigente, acabaron completamente abandonadas porque no podía disfrutar del proceso creativo.

Ojo, esto no quiere decir que en todo proyecto, todo deba ser divertido. Pero no todo debe ser aburrido, tedioso, pesado. Es una cuestión de balance, y en este caso, me hacía falta agregar diversión a mi proceso.

Y eso es todo, por ahora

A pesar de que lo he terminado, me quedé con el sentimiento de que podría mejorarlo, incluir más niveles, más arte, más mecánicas. De cajón, se me ocurre mejorar el UI, arreglar algunos bugs (ups).

Tengo la idea de que este proyecto ha cumplido su propósito, que era alejarme un poco esa idea del letargo, de esa idea de “tener rato sin crear nada”. De cierto modo, fue un proyecto para motivarme a desarrollar más, aprender más.

Creo que empezaré por hacer otro juego.

Tutorial

Cómo subir un juego de Unity en WebGL con AWS S3

Imagínate esto. Quieres pasarle a tus amigos el último prototipo del videojuego para PC que has creado. Compilas tu juego para funcionar en Windows y lo envías para que ellos lo prueben. Pero, oh desgracia, muchos de tus amigos no usan Windows, sino Mac OS X, y uno que otro, Linux. Así que tienes que compilar el proyecto para cada uno, y asegurarte que funcione bien.

¿Y si pudieras ahorrarte todo ese esfuerzo? Si tan solo hubiera un programa que funcione en cualquier PC…

Afortunadamente, Unity tiene un módulo para compilar WebGL, que es una especificación para renderización de gráficos en un explorador web. Actualmente, los exploradores más populares lo soportan en gran medida. De esta manera, podemos crear una página web que sirva como la interfaz de nuestro juego, sin necesidad de que los jugadores instalen directamente la aplicación. Basta con que vayan a la página, esperen a que se descargue el juego, y empiezan a jugar.

¿Qué vamos a hacer?

Para este tutorial, usaremos el servicio en nube Amazon Web Services (AWS) que será nuestro “host” en línea para nuestro juego. Éste se mostrará en un sitio web estático, el cual no cambiará por sí solo o tendrá más funcionalidades que mostrar el juego.

Usaremos AWS ya que nos ahorra el comprar un nombre de dominio y hosting en línea al poder utilizar una prueba gratuita, siempre y cuando no excedamos las cuotas de uso.

Compilaremos un proyecto de Unity en WebGL, preparado para subirlo al servicio S3 de AWS. Después, crearemos un contenedor de archivos llamado “bucket” en S3 y lo configuraremos para que funcione como un sitio web estático. Finalmente, subiremos el juego compilado y configuraremos los archivos para que puedan mostrarse sin problemas.

Importante

Al seguir esta guía, eres responsable de posibles costos que se produzcan por el uso de AWS. Revisa y asegúrate de entender las cuotas de uso de AWS en este enlace.

Así mismo, los archivos que se habiliten serán públicos y visibles a quién posea el enlace de la aplicación, así que no muestres información privada y sensible.

Habiendo aclarado esto, ¡Empecemos!

Requisitos

  • Instalación de Unity con módulo WebGL. Para el tutorial, usaré Unity 2021.1.
  • Cuenta de AWS (existe opción de prueba gratuita por un año).
  • Proyecto de prueba, listo para compilar. Puedes usar simplemente el proyecto default 3D.

Compilando el proyecto WebGL

  1. Abrir “Build Settings” y cambiar la plataforma a WebGL con el botón “Switch Platform”. Unity reimportará los assets para adaptarlos a la nueva plataforma.
  1. Una vez termine, abrir “Player Settings” desde la misma vista.
  1. En la sección “Settings for WebGL”, ir al apartado “Publishing Settings” y en “Compression Format”, seleccionar “Disabled”.
  1. Regresar a “Build Settings” y dar click en “Build”. Unity solicitará una carpeta para guardar los archivos, elige una de tu agrado (y que puedas recordar después).

Crear bucket en S3

  1. Ir a la consola de AWS.
  1. Dentro de la consola de AWS, ir al servicio de S3. Puedes buscarlo desde la lista “All Services” o teclear “S3” en el buscador.
  2. Una vez en la vista de “Buckets”, elegir la opción “Create Bucket”
  1. En la vista de “Create Bucket”, proporciona la siguiente información:
    • Bucket name: nombre del bucket que almacenará los archivos del juego. Este debe ser un nombre descriptivo, ya que será usado como parte del URL para acceder a los archivos desde el explorador Web. El nombre debe ser en minúsculas y sin espacios.
    • AWS Región: elije la región geográfica que esté más cercana a ti. En mi caso, usaré “us-east-2”.
    • Desactivar la casilla “Block all public access”. Esto para que el contenido sea visible al exterior. Aparecerá una advertencia sobre el acceso público, simplemente activa la casilla.
  1. Las opciones restantes pueden conservarse con la configuración default.
  1. Da click en “Create bucket”. Deberá regresar a la pantalla principal de S3, mostrando el nuevo bucket.
  1. Da click en el nombre del nuevo bucket, lo cual abrirá una vista con detalles sobre el bucket recién creado. Ve a la sección “Properties”, copia y guarda el texto debajo de “Amazon Resource Name (ARN)”. Este es un identificador del bucket que nos servirá para dar permisos de acceso posteriormente. El texto tiene un formato parecido a este: “arn:aws:s3:::el-nombre-de-tu-bucket”
  1. En la sección “Properties”, ir al apartado “Static website hosting” y dar click en el botón “Edit”
  1. En la vista que se abre, solo llenar el texto de “Index document” con “index.html”. Este representa la página principal de nuestro proyecto de Unity.
  1. Da click al botón “Save Changes”, el cual devolverá la página anterior sobre detalles del bucket.
  2. Ir de nuevo al apartado “Static website hosting”. Ahora deberá mostrar un URL. Cópialo y guárdalo al alcance, ya que ese será el punto de acceso al proyecto de Unity.
  3. A continuación, ve a la sección “Permissions” y dirígete al apartado “Bucket Policy”. Da click al botón “Edit”.
  1. En la vista “Edit bucket policy”, copia y pega el siguiente texto JSON en la caja de “Policy”. Reemplaza el texto “<ARN HERE>” por el ARN del bucket. Este es un documento que indica a AWS que cualquiera puede leer los archivos del bucket cuando los solicite.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicGetObject",
      "Action": [
        "s3:GetObject"
      ],
      "Effect": "Allow",
      "Resource": "/*",
      "Principal": "*"
    }
  ]
}
  1. Da click en “Save Changes”.

Esto concluye la configuración del bucket S3 para el sitio web estático. Si intentas entrar al sitio web estático en este momento, solo mostrará una página de error 404, ya que aún no subimos ningún archivo del proyecto.

Cargar archivos del proyecto a Unity

  1. En la pantalla principal de S3, dirígete al bucket configurado para almacenar el proyecto para abrir la vista de detalles en la sección “Objects”.
  1. Ve a la carpeta local donde Unity generó los archivos del proyecto, selecciónalos y arrástralos a la pantalla del bucket. Asegúrate de cargar los archivos directamente en la raíz del bucket y no dentro de alguna carpeta interna, de lo contrario, AWS no podrá acceder al proyecto como sitio web estático.
  1. Dar click en el botón de “Upload”. Los datos del proyecto comenzarán a cargarse a S3.
  2. Al terminar, da click en el botón “Close”. De nuevo, AWS mostrará la vista de detalles del bucket.
  3. En la misma vista, entra a la carpeta “Build” que se acaba de cargar, y da click en el objeto “WebGL.wasm”.
  1. Busca la sección “Metadata” y da click en el botón “Edit”.
  1. En la vista que se abre, cambia el texto debajo de “Value” por “application/wasm”. Esto es para que Unity no muestre advertencias al inicializar el proyecto WebGL.
  1. Al terminar, da click en el botón “Edit metadata”.

Con esto finaliza la configuración del proyecto, entra al URL del bucket y deberías poder acceder una ventana de Unity.

¡Listo!

Honestamente, hay opciones más simples (como itch.io) para subir el contenido, pero la ventaja de hacerlo manualmente y en un servicio en la nube es la posibilidad de contar con mayor flexibilidad para personalización e integración con diversos servicios y código propio, así como usarlo de entorno de pruebas. Ejemplo de esto sería añadir métricas y controles más complejos integrados en la propia página web, así como usar dominios completamente personalizados.

¡Ojalá te haya gustado el tutorial! No dudes en comentar cualquier pregunta o sugerencia.

¡Nos escribimos pronto!

Para más información

Documentación de Unity para exportar projectos a WebGL

https://docs.unity3d.com/Manual/webgl.html

Documentación de AWS para S3

https://docs.aws.amazon.com/es_es/AmazonS3/latest/userguide/Welcome.html