¿Cómo cargar trozos de apilamiento sobre la marcha?

8

Actualmente estoy trabajando en un mundo infinito, inspirado principalmente en Minecraft.
Un trozo consta de bloques de 16x16x16. Un bloque (cubo) es 1x1x1.

Esto funciona muy bien con un ViewRange de 12 Chunks (12x16) en mi computadora. Multa.
Cuando cambio la altura del fragmento a 256, esto se convierte, obviamente, en un retraso increíble.

Entonces, lo que básicamente quiero hacer es apilar trozos. Eso significa que mi mundo podría ser [∞, 16, ∞] pedazos grandes.

La pregunta ahora es cómo generar trozos sobre la marcha.
Por el momento, genero fragmentos no existentes circulares alrededor de mi posición (de cerca a lejos). Como todavía no apilo fragmentos, esto no es muy complejo.

Como nota al margen importante aquí: también quiero tener biomas, con diferente altura mínima / máxima. Entonces, en Biome Flatlands, la capa más alta con bloques sería 8 (8x16); en Biome Mountains, la capa más alta con bloques sería 14 (14x16). Solo como ejemplo.

Lo que podría hacer sería cargar 1 trozo encima y debajo de mí, por ejemplo.
Pero aquí el problema sería que las transiciones entre diferentes bioms podrían ser mayores que una porción en y.




Transiciones entre biomas




Mi trozo actual cargando en acción

Ejemplo de carga de trozos



Para completar aquí mi "algoritmo" actual de carga de fragmentos

private IEnumerator UpdateChunks(){
    for (int i = 1; i < VIEW_RANGE; i += ChunkWidth) {
        float vr = i;
        for (float x = transform.position.x - vr; x < transform.position.x + vr; x += ChunkWidth) {
            for (float z = transform.position.z - vr; z < transform.position.z + vr; z += ChunkWidth) {

                _pos.Set(x, 0, z); // no y, yet
                _pos.x = Mathf.Floor(_pos.x/ChunkWidth)*ChunkWidth;
                _pos.z = Mathf.Floor(_pos.z/ChunkWidth)*ChunkWidth;

                Chunk chunk = Chunk.FindChunk(_pos);

                // If Chunk is already created, continue
                if (chunk != null)
                    continue;

                // Create a new Chunk..
                chunk = (Chunk) Instantiate(ChunkFab, _pos, Quaternion.identity);
            }
        }
        // Skip to next frame
        yield return 0;
    }
}
Brettetete
fuente

Respuestas:

1

Lo que debe considerar cargar / crear un trozo encima y debajo de la superficie en cualquier pila cuando el jugador está en la superficie, por lo que su algoritmo de generación debe preocuparse por las pilas en el nivel superior en lugar de trozos ... cuando el jugador está debajo del suelo uno por encima y por debajo del nivel de fragmento actual está bien. Para aclarar, una pila es una columna vertical de trozos desde la roca madre hasta la estratosfera :)

Otra forma de verlo sería decir si la superficie está por debajo del nivel de fragmento actual del jugador: generar la superficie y una debajo, de lo contrario generar el nivel actual y uno arriba y abajo.

Digamos que su mundo tendrá 256 fragmentos de altura (* 16 = 4096 bloques de vóxel), y en cualquier momento si una pila está dentro del rango de visión, tendrá de 1 a 3 fragmentos en esa pila cargados y renderizados.

Los biomas introducen un problema adicional de fusión de alturas en los bordes, pero puede manejar eso en el código específico del bioma que se llamará para generar las características de superficie y subsuperficie. Si está utilizando ruido perlin / simplex para generar alturas, si un fragmento bordea un fragmento que es un bioma diferente, puede obtener los valores de ruido que generarían ambos tipos de bioma, luego promediarlos.

Ascensión
fuente
0

Lo que puede hacer es hacer un trozo de 256 metros de alto en la dirección y y dividirlo en 16 secciones, cada una con 16 bloques de alto. Luego genera los datos para el fragmento y construye la geometría dentro de las secciones.

Una ventaja sería que tiene acceso a los datos de un fragmento completo, lo que facilita el acceso a los datos arriba y abajo de una sección.

Esto también tiene la ventaja de poder eliminar fácilmente mucha geometría que no se encuentra dentro del tronco de visualización. También habrá muchas secciones que no tienen geometría alguna.

Si aún no lo usa, cargar los fragmentos en otro subproceso también puede proporcionarle mejores velocidades de cuadros, al tiempo que genera los datos para cada fragmento.

user000user
fuente
1
Bueno, tal vez me he explicado un poco vago. Ya he planeado separar un trozo en 16 capas. Pero, ¿cómo decido qué capas cargar? Cada bioma tiene su propia altura mínima / máxima. Imagine Bioma A [100,20] Bioma B [160,200] - Estoy al borde de ambos biomas. Tienen una transición suave. ¿Cómo decidir qué capas cargar? Pero al escribir esto, creo que solo tengo que verificar cada capa (de arriba a abajo) y crearla, cuando la capa de arriba está vacía / transparente, y detenerme cuando se crea la primera capa. Espero que obtengas esto;) Es molesto que no puedas agregar líneas en blanco en los comentarios
Brettetete
3
Entiendo tu argumento. Pero si implementa un algoritmo como mallado codicioso, debería poder cargar todas las capas sin una gran pérdida de rendimiento. Puede encontrar un artículo sobre mallas codiciosas aquí . Hago lo mismo en mi motor de vóxel y hasta ahora funciona bien.
user000user
Esta técnica es simplemente asombrosa. Intentaré implementar esto en mi motor. El código dado es un poco extraño, ¿compartirías tu implementación? :)
Brettetete
2
Pegué mi implementación de C ++ aquí y agregué algunos comentarios. Espero que ayude :)
user000user
Impresionante, esto parece ser muy útil. ¡Gracias! Intentaré traducir esto ahora. Pero en el # 67 - # 68 hay un doble && - ¿se perdió / duplicó accidentalmente algo para copiar? :) ¿Y podría explicar / publicar brevemente los métodos SHIFT_ ? Tampoco veo ninguna declaración / uso de tmp - Bueno, lo siento por todas esas preguntas
Brettetete
0

No creo que pueda hacer esto simplemente cargando ciertas capas debido al problema de las transiciones.

Mi inclinación sería almacenar algunos metadatos con cada fragmento:

1) ¿El bloque es completamente aéreo? Si es así, no hay necesidad de renderizarlo.

2) Para cada cara del bloque es opaco. Una cara opaca significa que no necesita considerar el siguiente fragmento. (Tenga en cuenta, sin embargo, que dependiendo de dónde esté el trozo podría haber hasta tres caras involucradas; se debe procesar un trozo si alguna de las tres no es opaca. Sospecho que esto es mejor precalculado). b1 es visible y tiene una cara no opaca f1 o b2 es visible tiene una cara no opaca f2 o b3 es visible tiene una cara no opaca f3).

Desafortunadamente, hay más de 7000 fragmentos dentro de su rango de visión de 12 fragmentos. Sin embargo, esperaría que algunas ubicaciones tengan más de tres fragmentos verticales que en realidad se deben procesar utilizando este enfoque que reduce el recuento de fragmentos a probablemente no más de 1500.

Aplicaría el mismo tipo de lógica dentro de un trozo: cuando cargue un trozo, calcule qué uniones son transparentes y qué uniones son opacas tocando opacas, solo necesita representar caras donde alguien pueda verlas. (Tenga en cuenta que en Minecraft tiene tres tipos de bloque: transparente, opaco y que altera la visión: vidrio, puertas, cercas, etc. Solo puede omitir transparente-transparente y opaco-opaco).

Loren Pechtel
fuente