Saltar al contenido principal

Marching Voxels

Un sencillo algoritmo para renderizar información volumétrica.

Tras la realización de mi Trabajo de Final de Grado sobre voxelización de mallas 3D seguí interesándome por métodos de renderizado volumétrico. Uno de ellos es el método de los marching cubes, que consiste en dividir el trabajo de dibujado en bloques de 8 vóxeles (un cubo) que puede ser renderizado de manera independiente. Esto permite usar paralelización masiva en el proceso de dibujado, lo que disminuye drásticamente el tiempo de renderizado de la estructura de vóxeles, mientras que se mantiene la simplicidad de solo tener que analizar unos pocos vóxeles en cada iteración. Esta distribución convierte a la función en un candidato perfecto para la aceleración por hardware gráfico (GPU).

Perlin noise 3D renderizado mediante el algoritmo de marching cubes.

El algoritmo Marching Cubes

Marching Cubes es un algoritmo que permite extraer superficies a partir de información volumétrica, como por ejemplo, un campo escalar de densidades. A partir de un valor umbral (isovalue), el algoritmo recorre una versión discreta del campo escalar celda por celda, determinando como la superficie determinada por el isovalue intersecta esa celda en particular. Tras esto, mediante una look-up table precalculada, se generan los triángulos que conforman la geometría de dicha superficie a nivel local.

La implementación del algoritmo se realizó en Unity, usando un Compute Shader que aprovecha la potencia de la GPU para paralelizar el algoritmo, permitiendo su uso en tiempo real de manera eficiente. La herramienta desarrollada te permite generar una densidad en base a otro Compute Shader que samplea un modelo matemático en una grid 3D, que será dibujada mediante el algoritmo de Marching Cubes dados los parámetros introducidos en esta interfaz.

Render final

Interfaz principal de la herramienta de generación de la densidad.

La información volumétrica a renderizar puede ser de muchas índoles diferentes: campos magnéticos, objetos físicos, generaciones mediante SDF (Signed Distance Fields), fluidos, etc. En general, este modo de renderizado es ideal para cualquier entidad que pueda ser fielmente modelada mediante ecuaciones matemáticas, y cuyo modelado o animación resulten complejos, como suele pasar con los fluidos. Lo ideal, por supuesto, es que se pueda computar en tiempo real, ya que aunque potencialmente pueda ser precomputado y guardado como un conjunto de frames de texturas volumétricas, pasaríamos a tener 4 dimensiones, lo que nos llevaría a ocupar grandes cantidades de memoria.

Densidad Simplex Noise

Densidad generada usando Simplex Noise. Los puntos blancos representan valores de 1, los negros valores de 0.

A continuación detallo brevemente las diferentes variables configurables de la implementación que hice en Unity.

GridSize

Esta variable nos permite configurar la resolución en cada eje de la densidad. A mayor resolución, más exacta será la malla que se generará a partir de ella. Sin embargo, el coste de renderizado puede aumentar drásticamente, ya que al tratarse de un volumen, el coste aumenta en el orden O(N^3), tanto temporal como espacialmente.

Render final

Arriba, resolución de 8 por eje. Abajo, la resolución es de 32 por eje.

SurfaceLevel

Este valor parametrizado entre 0 y 1, también llamado isovalue, permite ajustar a qué nivel de la densidad se renderiza la malla. Los puntos donde el valor de densidad sea menor que el SurfaceLevel estarán dentro de la malla; donde sea mayor, estarán fuera de la malla. Potencialmente, se podría variar el valor de SurfaceLevel a lo largo del volumen, con lo que se podrían generar efectos más complejos sin necesidad de modificar el modelo matemático subyacente.

Render final

En la imagen superior, el isovalue es 0.066; en la inferior, 0.238.

SmoothRendering

Por defecto la herramienta genera la malla con el método de triángulos independientes, lo que genera un efecto de superficie plana. Además, la estructura de triángulos independientes es sustancialmente más costosa de almacenar en la memoria. Si se activa la opción SmoothRendering, la malla generada tendrá una estructura de vértices compartidos de un solo grupo, con la normal interpolada entre los triángulos independientes que compartían dicho vértice, lo que hará que el motor de renderizado de Unity interpole la normal para cada fragmento, permitiendo una visualización más suave de la iluminación.

Render final

La imagen muestra una comparación entre ambos tipos de estructura.

CloseBorders

Ofrece la posibilidad de dejar abierta o cerrada la malla que se encuentra en la frontera de la densidad, más allá de la cual no hay información sobre el valor de la misma en cada punto del espacio. Útil cuando deseas generar un efecto que sea visto desde fuera.

Render final

Arriba, CloseBorders está activado. Abajo, la opción está desactivada.

A continuación se puede observar el funcionamiento de la herramienta en vídeo en el propio motor de Unity en tiempo real.

Renderizado de Simplex Noise 3D.

Renderizado de geometría 3D.

Como conclusión, este proyecto me permitió profundizar en técnicas de renderizado volumétrico y en el uso de la GPU como herramienta de cómputo general, afrontando problemas reales de rendimiento, memoria y diseño de datos en tres dimensiones. La implementación de Marching Cubes mediante compute shaders en Unity demuestra cómo algoritmos clásicos pueden adaptarse a arquitecturas modernas para funcionar en tiempo real, manteniendo flexibilidad y calidad visual. Este trabajo resume bien mi interés por los gráficos por ordenador, los sistemas basados en modelos matemáticos y el desarrollo de herramientas técnicas orientadas tanto a la eficiencia como a la experimentación visual.