Estoy tratando de escribir un pequeño motor de vóxel porque es divertido, pero me cuesta encontrar la mejor manera de almacenar los vóxeles reales. Soy consciente de que necesitaré trozos de algún tipo, así que no necesito tener todo el mundo en la memoria, y sé que necesito renderizarlos con un rendimiento razonable.
Leí sobre octrees y, por lo que entiendo, comienza con 1 cubo, y en ese cubo puede haber 8 cubos más, y en todos esos 8 cubos puede haber otros 8 cubos, etc. Pero no creo que esto se ajuste a mi motor de vóxel porque mis cubos / artículos de voxel serán todos exactamente del mismo tamaño.
Entonces, otra opción es simplemente crear una matriz de tamaño 16 * 16 * 16 y hacer que sea un fragmento, y llenarlo con elementos. Y las partes donde no hay ningún artículo tendrán 0 como valor (0 = aire). Pero me temo que esto va a desperdiciar mucha memoria y no será muy rápido.
Luego, otra opción es un vector para cada fragmento y llenarlo con cubos. Y el cubo mantiene su posición en el trozo. Esto ahorra memoria (sin bloqueos de aire), pero hace que la búsqueda de un cubo en una ubicación específica sea mucho más lenta.
Así que realmente no puedo encontrar una buena solución, y espero que alguien pueda ayudarme con eso. Entonces, ¿qué usarías y por qué?
Pero otro problema es el renderizado. Simplemente leer cada fragmento y enviarlo a la GPU usando OpenGL es fácil, pero muy lento. Generar una malla por fragmento sería mejor, pero eso significa que cada vez que rompo un bloque, tengo que reconstruir todo el fragmento, lo que podría llevar un poco de tiempo y causar un hipo menor pero notable, que obviamente tampoco quiero. Entonces eso sería más difícil. Entonces, ¿cómo renderizaría los cubos? Simplemente cree todos los cubos en un búfer de vértices por fragmento y renderícelo, y tal vez intente ponerlo en otro hilo, ¿o hay alguna otra forma?
¡Gracias!
Respuestas:
Almacenar los bloques como las posiciones y los valores es realmente muy ineficiente. Incluso sin ninguna sobrecarga causada por la estructura o el objeto que utiliza, debe almacenar 4 valores distintos por bloque. Solo tendría sentido usarlo sobre el método de "almacenamiento de bloques en arreglos fijos" (el que describió anteriormente) es cuando solo una cuarta parte de los bloques son sólidos, y de esta manera ni siquiera toma otros métodos de optimización en cuenta.
Los octrees son realmente excelentes para juegos basados en voxel, ya que se especializan en almacenar datos con características más grandes (por ejemplo, parches del mismo bloque). Para ilustrar esto, usé un quadtree (básicamente octrees en 2d):
Este es mi conjunto inicial que contiene mosaicos de 32x32, que equivaldrían a 1024 valores:
Almacenar esto como 1024 valores separados no parece tan ineficiente, pero una vez que alcanza tamaños de mapas similares a los juegos, como Terraria , cargar pantallas tomaría varios segundos. Y si lo aumenta a la tercera dimensión, comienza a utilizar todo el espacio del sistema.
Los quadtrees (o octrees en 3d) pueden ayudar a la situación. Para crear uno, puede ir desde los mosaicos y agruparlos, o desde una celda enorme y dividirlo hasta llegar a los mosaicos. Usaré el primer enfoque, porque es más fácil de visualizar.
Entonces, en la primera iteración, agrupa todo en celdas 2x2, y si una celda solo contiene mosaicos del mismo tipo, suelta los mosaicos y simplemente almacena el tipo. Después de una iteración, nuestro mapa se verá así:
Las líneas rojas marcan lo que almacenamos. Cada cuadrado es solo 1 valor. Esto redujo el tamaño de 1024 valores a 439, que es una disminución del 57%.
Pero conoces el mantra . Avancemos un paso más y agrupemos estos en celdas:
Esto redujo la cantidad de valores almacenados a 367. Eso es solo el 36% del tamaño original.
Obviamente, necesita hacer esta división hasta que cada 4 celdas adyacentes (8 bloques adyacentes en 3d) dentro de un fragmento se almacene dentro de una celda, convirtiendo esencialmente un fragmento en una celda grande.
Esto también tiene algunos otros beneficios, principalmente cuando se produce una colisión, pero es posible que desee crear un octree separado para eso, que solo se preocupa por si un solo bloque es sólido o no. De esa manera, en lugar de verificar la colisión para cada bloque dentro de un trozo, puede hacerlo contra las celdas.
fuente
Existen octrees para resolver exactamente el problema que describe, lo que permite el almacenamiento denso de datos escasos sin grandes tiempos de búsqueda.
El hecho de que tus vóxeles sean del mismo tamaño solo significa que tu octree tiene una profundidad fija. p.ej. para un trozo de 16x16x16, necesita como máximo 5 niveles de árbol:
Esto significa que tiene como máximo 5 pasos para averiguar si hay un vóxel en una posición particular del bloque:
¡Mucho más corto que escanear incluso el 1% del camino a través de una matriz de hasta 4096 vóxeles!
Tenga en cuenta que esto nos permite comprimir los datos donde haya un octante completo del mismo valor, ya sea que ese valor sea todo aire o roca o cualquier otra cosa. Es solo donde los octantes contienen valores mixtos que necesitamos subdividir aún más, hasta el límite de los nodos de hoja de un solo vóxel.
Para dirigirse a los hijos de un fragmento, normalmente procederemos en el orden de Morton , algo como esto:
Entonces, nuestra navegación del nodo Octree podría verse así:
fuente