Caso práctico: Creación de una galaxia en realidad mixta

Antes de Microsoft HoloLens enviado, le preguntamos a nuestra comunidad de desarrolladores qué tipo de aplicación les gustaría ver una compilación de equipo interna con experiencia para el nuevo dispositivo. Más de 5000 ideas fueron compartidas, y después de un sondeo de Twitter de 24 horas, el ganador fue una idea llamada Galaxy Explorer.

Andy Zibits, responsable del arte en el proyecto, y El ingeniero gráfico del equipo, hablan sobre el esfuerzo colaborativo entre el arte y la ingeniería que llevó a la creación de una representación precisa e interactiva de la galaxia Milky Way en galaxy Galaxy Explorer.

La tecnología

Nuestro equipo , formado por dos diseñadores, tres desarrolladores, cuatro artistas, un productor y un evaluador, tenían seis semanas para crear una aplicación totalmente funcional que permitiría a las personas aprender y explorar la amplitud y belleza de nuestra Milky Way Galaxy.

Queríamos aprovechar al máximo la capacidad de HoloLens para representar objetos 3D directamente en su espacio de vida, por lo que decidimos que queríamos crear una galaxia realista en la que las personas pudieran acercar y ver estrellas individuales, cada una en sus propias trayectorias.

En la primera semana de desarrollo, llegamos con algunos objetivos para nuestra representación de la Galaxia de la Vía Lechera: Necesitaba tener profundidad, movimiento y sensación volumétrica, llena de estrellas que ayudarían a crear la forma de la galaxia.

El problema con la creación de una galaxia animada que tenía miles de millones de estrellas era que el número mayor de elementos individuales que necesitan actualizar sería demasiado grande por fotograma para que HoloLens se animara mediante la CPU. Nuestra solución implicaba una combinación compleja de arte y ciencia.

Entre bambalinas

Para permitir que la gente explore estrellas individuales, nuestro primer paso era averiguar cuántas partículas podríamos representar a la vez.

Representación de partículas

Las CPU actuales son excelentes para procesar tareas serie y hasta algunas tareas paralelas a la vez (dependiendo del número de núcleos que tienen), pero las GPU son mucho más eficaces al procesar miles de operaciones en paralelo. Sin embargo, dado que normalmente no comparten la misma memoria que la CPU, el intercambio de datos entre GPU de CPU<>puede convertirse rápidamente en un cuello de botella. Nuestra solución era hacer una galaxia en la GPU, y tenía que vivir completamente en la GPU.

Empezamos las pruebas de esfuerzo con miles de partículas de punto en varios patrones. Esto nos permitió obtener la galaxia en HoloLens para ver lo que funcionó y lo que no.

Creación de la posición de las estrellas

Uno de nuestros miembros del equipo ya había escrito el código de C# que generaría estrellas en su posición inicial. Las estrellas están en una elipse y su posición se puede describir mediante (curveOffset, ellipseSize, elevation) donde curveOffset es el ángulo del star a lo largo de la elipse, elipseSize es la dimensión de la elipse a lo largo de X y Z, y eleva la elevación adecuada de la star dentro de la galaxia. Por lo tanto, podemos crear un búfer (ComputeBuffer de Unity) que se inicializaría con cada atributo star y enviarlo en la GPU donde residiría para el resto de la experiencia. Para dibujar este búfer, usamos DrawProcedural de Unity , que permite ejecutar un sombreador (código en una GPU) en un conjunto arbitrario de puntos sin tener una malla real que represente la galaxia:

CPU:

GraphicsDrawProcedural(MeshTopology.Points, starCount, 1);

GPU:

v2g vert (uint index : SV_VertexID)
{

 // _Stars is the buffer we created that contains the initial state of the system
 StarDescriptor star = _Stars[index];
 …

}

Empezamos con patrones circulares sin formato con miles de partículas. Esto nos dio la prueba que necesitábamos que podríamos administrar muchas partículas Y ejecutarla a velocidades de rendimiento, pero no nos siento satisfecho con la forma general de la galaxia. Para mejorar la forma, intentamos varios patrones y sistemas de partículas con rotación. Estos fueron inicialmente prometedores porque el número de partículas y rendimiento se mantuvo coherente, pero la forma se rompió cerca del centro y las estrellas se emitían externamente que no eran realistas. Necesitamos una emisión que nos permitiría manipular el tiempo y hacer que las partículas se muevan de forma realista, en bucle cada vez más cerca del centro de la galaxia.

Intentamos varios patrones y sistemas de partículas que giraban, como estos.

Intentamos varios patrones y sistemas de partículas que giraban, como estos.

Nuestro equipo realizó algunas investigaciones sobre la forma en que las galaxias funcionan y hicimos un sistema de partículas personalizado específicamente para la galaxia para que podamos mover las partículas sobre puntos suspensivos basados en la "teoría de la onda de densidad", que teoriza que los brazos de una galaxia son áreas de mayor densidad pero en flujo constante, como un atasco de tráfico. Parece estable y sólido, pero las estrellas se mueven y salen de los brazos a medida que se mueven a lo largo de sus respectivos elipses. En nuestro sistema, las partículas nunca existen en la CPU, generamos las tarjetas y las orientamos todas en la GPU, por lo que todo el sistema es simplemente el estado inicial + tiempo. Ha progresado de la siguiente manera:

Progresión del sistema de partículas con representación de GPU

Progresión del sistema de partículas con representación de GPU

Una vez que se agregan suficientes puntos suspensivos y se establecen para girar, las galaxias comenzaron a formar "brazos" donde converge el movimiento de las estrellas. El espaciado de las estrellas a lo largo de cada ruta elíptica se le dio una aleatoriedad, y cada star obtuvo un poco de aleatoriedad posicional agregada. Esto creó una distribución de aspecto mucho más natural de star movimiento y forma de brazo. Por último, hemos agregado la capacidad de conducir el color en función de la distancia desde el centro.

Creación del movimiento de las estrellas

Para animar el movimiento general star, necesitamos agregar un ángulo constante para cada fotograma y conseguir que las estrellas se muevan a lo largo de sus puntos suspensivos a una velocidad radial constante. Esta es la razón principal para usar curveOffset. Esto no es técnicamente correcto, ya que las estrellas se moverán más rápido a lo largo de los lados largos de los puntos suspensivos, pero el movimiento general se sintió bueno.

Las estrellas se mueven más rápido en el arco largo, más lento en los bordes.

Las estrellas se mueven más rápido en el arco largo, más lento en los bordes.

Con eso, cada star se describe por completo por (curveOffset, elipseSize, elevación, Age) donde Age es una acumulación del tiempo total que ha pasado desde que se cargó la escena.

float3 ComputeStarPosition(StarDescriptor star)
{

  float curveOffset = star.curveOffset + Age;
  
  // this will be coded as a “sincos” on the hardware which will compute both sides
  float x = cos(curveOffset) * star.xRadii;
  float z = sin(curveOffset) * star.zRadii;
   
  return float3(x, star.elevation, z);
  
}

Esto nos permitió generar decenas de miles de estrellas una vez al principio de la aplicación, y luego animamos un único conjunto de estrellas a lo largo de las curvas establecidas. Puesto que todo está en la GPU, el sistema puede animar todas las estrellas en paralelo sin costo alguno para la CPU.

Este es el aspecto que tiene al dibujar cuadrantes blancos.

Este es el aspecto que tiene al dibujar cuadrantes blancos.

Para convertir cada cuádruple cara en la cámara, usamos un sombreador de geometría para transformar cada star posición en un rectángulo 2D en la pantalla que contendrá nuestra textura de star.

Diamantes en lugar de quads.

Diamantes en lugar de quads.

Dado que queríamos limitar el exceso de dibujo (número de veces que se procesará un píxel) tanto como sea posible, giramos nuestros quads para que tengan menos superposición.

Adición de nubes

Hay muchas maneras de obtener una sensación volumétrica con partículas, desde el rayo que marcha dentro de un volumen hasta dibujar tantas partículas como sea posible para simular una nube. La marcha de rayos en tiempo real iba a ser demasiado costosa y difícil de crear, por lo que primero intentamos crear un sistema imposter usando un método para representar bosques en juegos, con una gran cantidad de imágenes 2D de árboles orientados a la cámara. Cuando lo hacemos en un juego, podemos tener texturas de árboles representados desde una cámara que gira alrededor, guardar todas esas imágenes y en tiempo de ejecución para cada tarjeta de cartelera, seleccionar la imagen que coincida con la dirección de la vista. Esto tampoco funciona cuando las imágenes son hologramas. La diferencia entre el ojo izquierdo y el ojo derecho lo hacen para que necesitemos una resolución mucho más alta o, de lo contrario, simplemente se ve plana, con alias o repetitivo.

En nuestro segundo intento, intentamos tener tantas partículas como sea posible. Los mejores objetos visuales se lograron cuando dibujamos partículas y los difuminábamos antes de agregarlos a la escena. Los problemas típicos con ese enfoque estaban relacionados con el número de partículas que podríamos dibujar en un solo momento y la cantidad de área de pantalla que cubrieron mientras se mantienen 60 fps. La desenfoque de la imagen resultante para obtener esta sensación de nube suele ser una operación muy costosa.

Sin textura, esto es lo que parecen las nubes con opacidad del 2 %.

Sin textura, esto es lo que parecen las nubes con opacidad del 2 %.

Ser aditivos y tener una gran cantidad de ellos significa que tendríamos varios cuadrantes encima entre sí, sombreando repetidamente el mismo píxel. En el centro de la galaxia, el mismo píxel tiene cientos de quads encima del otro y esto tuvo un gran costo cuando se realiza la pantalla completa.

Hacer nubes de pantalla completa e intentar desenfocarlas habría sido una mala idea, por lo que en su lugar decidimos dejar que el hardware haga el trabajo para nosotros.

Un poco de contexto primero

Cuando se usan texturas en un juego, el tamaño de textura rara vez coincidirá con el área en la que queremos usarlo, pero podemos usar diferentes tipos de filtrado de texturas para obtener la tarjeta gráfica para interpolar el color que queremos a partir de los píxeles de la textura (Filtrado de texturas). El filtrado que nos interesa es el filtrado bilineal que calculará el valor de cualquier píxel con los 4 vecinos más cercanos.

Original antes del filtrado

Resultado después del filtrado

Con esta propiedad, vemos que cada vez que intentamos dibujar una textura en un área dos veces mayor, desenfoca el resultado.

En lugar de representar en una pantalla completa y perder esos milisegundos preciosos, podríamos gastar en otra cosa, se representa en una versión pequeña de la pantalla. Después, copiando esta textura y extendiéndolo por un factor de 2 varias veces, regresamos a pantalla completa mientras desenfoquemos el contenido en el proceso.

x3 escalado vertical a resolución completa.

x3 escalado vertical a resolución completa.

Esto nos permitió obtener la parte de nube con solo una fracción del costo original. En lugar de agregar nubes en la resolución completa, solo pintamos 1/64 de los píxeles y simplemente estiramos la textura a la resolución completa.

Izquierda, con un escalado de 1/8 a resolución completa; y derecha, con 3 escalas con potencia de 2.

Izquierda, con un escalado de 1/8 a resolución completa; y derecha, con 3 escalas con potencia de 2.

Tenga en cuenta que intentar pasar del 1/64 del tamaño al tamaño completo en una go tendría un aspecto completamente diferente, ya que la tarjeta gráfica seguiría usando 4 píxeles en nuestra configuración para sombrear un área más grande y los artefactos empiezan a aparecer.

A continuación, si agregamos estrellas de resolución completa con tarjetas más pequeñas, obtenemos la galaxia completa:

Resultado final cercano de la representación de galaxias con estrellas de resolución completa

Una vez que estuvimos en la pista correcta con la forma, agregamos una capa de nubes, intercambiamos los puntos temporales con los que pintamos en Photoshop y agregamos un color adicional. El resultado fue un Milky Way Galaxy, nuestros equipos de arte e ingeniería se sentían bien y cumplen nuestros objetivos de tener profundidad, volumen y movimiento, todo sin impuestos a la CPU.

Nuestra última galaxia milky way en 3D.

Nuestra última galaxia milky way en 3D.

Más información para explorar

Hemos abierto el código de la aplicación Galaxy Explorer y lo hemos puesto a disposición de los desarrolladores en GitHub para que se compilen.

¿Te interesa obtener más información sobre el proceso de desarrollo para Galaxy Explorer? Echa un vistazo a todas nuestras últimas actualizaciones del proyecto en el canal de YouTube de Microsoft HoloLens.

Acerca de los autores

Imagen de Lucía Luccin en su escritorio Azure Luccin es un ingeniero de software y entusiastas de los objetos visuales. Fue ingeniero gráfico para Galaxy Explorer.
Foto del director de arte Andy Zibits Andy Zibits es un entusiasta del arte y del espacio que administraba el equipo de modelado 3D para Galaxy Explorer y luchó por aún más partículas.

Consulte también