Crea tu Minecraft: genera niveles 3D a partir de cubos


En parte debido a la popularidad de Minecraft , recientemente ha habido un creciente interés en la idea de un juego que tiene lugar en un mundo en cubos construido en relieve 3D y lleno de elementos como cuevas, acantilados, etc. Tal mundo es una aplicación ideal para el ruido generado al estilo de mi biblioteca ANL . Este artículo surgió de las discusiones de mis intentos anteriores de implementar esta técnica. Desde entonces, han aparecido cambios menores en la estructura de la biblioteca.

En publicaciones anteriores, hablé sobre el uso de características de ruido 3D para implementar terrenos al estilo Minecraft. Después de eso, la biblioteca evolucionó un poco, así que decidí volver a este tema. Como tuve que responder muchas preguntas sobre este sistema, intentaré hablar más sobre los conceptos involucrados. Para aclarar los conceptos básicos, comenzaré con la idea de generar un terreno 2D utilizado en juegos como Terraria y King Arthur's Gold, y luego expandiré el sistema a ejemplos 3D como Minecraft. Esto me permitirá demostrar conceptos de manera más efectiva utilizando imágenes como ejemplo.

Este sistema se desarrolló teniendo en cuenta el siguiente objetivo abstracto: deberíamos poder pasar la coordenada de un determinado punto o celda al sistema y determinar qué tipo de bloque debería estar en esta ubicación. Queremos que el sistema sea una "caja negra": le pasamos un punto, devolvemos el tipo de bloque. Por supuesto, esto se aplica solo a la generación inicial del mundo. Los bloqueos en dichos juegos pueden ser cambiados por las acciones del jugador, y será inconveniente tratar de describir dichos cambios usando el mismo sistema. Dichos cambios deben ser rastreados de alguna otra manera. Este sistema genera el mundo original, prístino e intacto por las manos del jugador y otros personajes.

Quizás esta técnica no sea adecuada para modelar sistemas como el césped u otras entidades biológicas, dado que dichos sistemas son entidades complejas que no son tan fáciles de modelar implícitamente. Lo mismo se aplica a sistemas como la caída de nieve, la formación de hielo, etc. La técnica descrita en el artículo es un método implícito , es decir. uno que puede estimarse en un punto y cuyo valor en un punto dado no depende de los valores circundantes. Los sistemas biológicos y de otro tipo para realizar simulaciones precisas generalmente necesitan considerar los valores ambientales. Por ejemplo: ¿cuánta luz solar cae sobre un bloque? ¿Hay agua cerca? Estas y otras preguntas deben responderse para simular el crecimiento y la propagación de los sistemas biológicos, así como, en menor medida, otros tipos de sistemas relacionados con el clima. Además, esta técnica no es adecuada para modelar agua. En este sistema no existe un concepto de flujo, conocimiento de mecánica de fluidos o gravedad. El agua es un tema complejo que requiere muchos cálculos complejos.

Entonces, solo estamos modelando la tierra y las piedras. Necesitamos una función que le indique cuál debe ser la ubicación dada: tierra, arena, aire, oro, hierro, carbón, etc. ... Pero comenzaremos con la más simple. Necesitamos una función que indique si el bloque es sólido o hueco (lleno de aire). Esta función debería simular la tierra que nos rodea. Es decir, el cielo está arriba, la tierra está abajo. Entonces, asumamos la tarea bíblica y separemos el cielo de la tierra. Para hacer esto, estudiamos la función Gradiente . La función Gradiente pasa un segmento de línea en un espacio N-dimensional (es decir, en cualquier espacio de coordenadas, ya sea 2D, 3D o superior), y calcula el campo de gradiente a lo largo de este segmento. Las coordenadas entrantes se proyectan en este segmento y su valor de gradiente se calcula en función de dónde se encuentran en relación con los puntos finales del segmento. Los puntos proyectados son valores asignados en el intervalo (-1.1). Y este será un buen comienzo para nosotros. Podemos definir la función Gradiente a lo largo del eje Y. En la parte superior del intervalo, comparamos el campo de gradiente con -1 (aire) y en la parte inferior con 1 (tierra).

 terraintree =
 {
	 {name = "ground_gradient", type = "gradiente", x1 = 0, x2 = 0, y1 = 0, y2 = 1}
 } 

(Explicaré la entrada brevemente. El código de los ejemplos está escrito en la tabla de declaración de Lua. Para obtener más información sobre el formato, consulte la sección sobre integración de Lua . En esencia, el formato está diseñado para que una clase especial lo analice y los convierta en árboles de instancias de módulos de ruido. Prefiero esto el formato es más detallado paso a paso en formato C ++, porque es más compacto y limpio. En mi opinión, el código fuente es más legible y comprimido que el código C ++. En su mayor parte, las declaraciones son fáciles de leer y comprender. Los módulos tienen nombres, las fuentes se especifican nombre o valor. El código Lua utilizado para analizar las declaraciones de la tabla se incluye en el código fuente en caso de que desee utilizar estas declaraciones directamente).

En el caso de 2D, la función Gradient recibe un segmento de línea recta en la forma (x1, x2, y1, y2), y en el caso de 3D, el formato se expande a (x1, x2, y1, y2, z1, z2). El punto formado por (x1, y1) denota el comienzo del segmento de línea asociado con 0. El punto formado (x2, y2) es el final del segmento asociado con 1. Es decir, aquí mapeamos el segmento de línea (0,1) -> ( 0,0) con un gradiente. Por lo tanto, el gradiente estará entre las regiones de la función Y = 1 e Y = 0. Es decir, esta tira forma las dimensiones del mundo en Y. Cualquier parte del mundo estará en esta tira. Podemos ajustar cualquier región a lo largo de X (casi hasta el infinito, pero aquí la precisión double nos limita), pero todo es interesante, es decir. La superficie de la tierra estará dentro de esta banda. Este comportamiento se puede cambiar, pero dentro de él tenemos un gran grado de flexibilidad. Simplemente no olvide que los valores que están por encima o por debajo de esta banda tienen más probabilidades de no ser interesantes, ya que los valores anteriores tienen más probabilidades de ser aire, y los valores de abajo son tierra. (Como pronto verá, esta afirmación puede resultar errónea). Para la mayoría de las imágenes de esta serie, coincidiré con la región cuadrada dada por el cuadrado (0,1) -> (1,0) en el espacio 2D. Por lo tanto, al principio nuestro mundo se ve así:


Nada interesante hasta ahora; Además, esta imagen no responde a la pregunta "¿el punto dado es sólido o hueco?". Para responder a esta pregunta, necesitamos aplicar la función de paso (función definida por partes). En lugar de un gradiente suave, necesitamos una separación clara, en la que todas las ubicaciones de un lado sean huecas y todas las ubicaciones del otro lado sean sólidas. En ANL, esto se puede implementar usando la función Seleccionar . La función Seleccionar recibe dos funciones o valores entrantes (en este caso serán iguales a "sólido" y "Hueco" (Abierto)), y los selecciona en función del valor de la función de control (en este caso, Gradiente). El módulo Seleccionar tiene dos parámetros adicionales, umbral y caída , que afectan este proceso. En esta etapa, la caída no es deseable, por lo que la estableceremos en 0. El parámetro umbral decide a dónde irá la línea divisoria entre Sólido y Abierto. Todo lo que sea mayor que este valor en la función Gradiente se convertirá en Sólido, y todo lo que sea menor que el umbral se abrirá. Como Gradient compara el intervalo con valores de 0 y 1, sería lógico colocar el umbral en 0.5. Entonces dividimos el espacio exactamente por la mitad. El valor 1 será una ubicación sólida y el valor 0 será hueco. Es decir, definimos la función del plano terrestre de la siguiente manera:

 terraintree =
 {
	 {name = "ground_gradient", type = "gradiente", x1 = 0, x2 = 0, y1 = 0, y2 = 1},
	 {name = "ground_select", type = "select", low = 0, high = 1, umbral = 0.5, control = "ground_gradient"}
 }

Al comparar la misma área de la función que antes, obtenemos algo similar:


Esta imagen responde claramente a la pregunta de si el punto dado es sólido o hueco. Podemos llamar a la función con cualquier coordenada posible del espacio 2D, y su resultado será 1 o 0, dependiendo de dónde esté el punto en relación con la superficie de la tierra. Sin embargo, tal función no es particularmente interesante, es solo una línea plana que se extiende hasta el infinito. Para revivir la imagen, utilizamos una técnica llamada "turbulencia".

La “turbulencia” es una designación compleja del concepto de agregar valores a las coordenadas entrantes de una función. Imagine que llamamos a la función anterior de la tierra con la coordenada (0,1). Se encuentra sobre el plano del suelo, porque en Y = 1 el gradiente tiene un valor de 0, que es menor que el umbral = 0.5. Es decir, este punto se calculará como Abierto. Pero, ¿qué pasa si, antes de invocar la función de la tierra, de alguna manera transformamos este punto? Supongamos que restamos un valor aleatorio de la coordenada Y, por ejemplo, 3. Restamos 3 y obtenemos la coordenada (0, -2). Si ahora llamamos a la función base para este punto, entonces el punto se considerará sólido, porque Y = -2 se encuentra debajo del segmento Gradiente correspondiente a 1. De repente, el punto hueco (0,1) se convierte en un sólido. Obtendremos un bloque de piedra sólida que cuelga en el aire. Esto se puede hacer con cualquier punto de la función sumando o restando un número aleatorio de la coordenada Y del punto entrante antes de llamar a la función ground_select. Aquí hay una imagen de la función ground_select que muestra esto. Antes de llamar a la función ground_select, el valor en el intervalo (-0.25, 0.25) se agrega a la coordenada Y de cada punto.


Esto es más interesante que una línea plana, pero no muy similar a la tierra, porque cada punto se mueve a un valor completamente aleatorio, lo que crea un patrón caótico. Sin embargo, si usamos una función aleatoria continua, por ejemplo, Fractal de la biblioteca ANL , entonces, en lugar de un patrón aleatorio, obtenemos algo más controlable. Por lo tanto, conectemos un fractal al plano de la tierra y veamos qué sucede.

 terraintree =
 {
	 {name = "ground_gradient", type = "gradiente", x1 = 0, x2 = 0, y1 = 0, y2 = 1},
	 {name = "ground_shape_fractal", type = "fractal", fractaltype = anl.FBM, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 6, frecuencia = 2},
	 {name = "ground_scale", type = "scaleoffset", scale = 0.5, offset = 0, source = "ground_shape_fractal"},
	 {name = "ground_perturb", type = "translateomain", source = "ground_gradient", ty = "ground_scale"},
	
	 {name = "ground_select", type = "select", low = 0, high = 1, umbral = 0.5, control = "ground_perturb"}
 }

Aquí vale la pena señalar un par de aspectos. Primero, definimos el módulo Fractal y lo encadenamos al módulo ScaleOffset . El módulo ScaleOffset escala los valores fractales de salida a un nivel más conveniente. Parte del relieve puede ser montañoso y requerir una escala mayor, y otra parte más plana y con una escala menor. Hablaremos sobre diferentes tipos de terreno más adelante, pero por ahora los usaremos para demostración. Los valores de salida de la función ahora darán la siguiente imagen:


Esto es más interesante que solo ruido aleatorio, ¿verdad? Al menos, se parece más a la tierra, aunque parte del paisaje parece inusual, y las islas voladoras son completamente extrañas. La razón de esto fue que cada punto individual del mapa de salida se desplaza aleatoriamente por un valor diferente determinado por el fractal. Para ilustrar esto, muestre la salida fractal que realiza la distorsión:


En la imagen de arriba, todos los puntos negros tienen un valor de -0.25, y todos los puntos blancos tienen un valor de 0.25. Es decir, donde el fractal es negro, el punto correspondiente de la función de la Tierra se desplazará "hacia abajo" en 0.25. (0.25 significa 1/4 de la pantalla.) Dado que un punto puede desplazarse ligeramente, y el otro punto sobre él en el espacio puede desplazarse más, existe la posibilidad de protuberancias de rocas e islas voladoras. Las protuberancias en la naturaleza son bastante naturales, en contraste con las islas voladoras. (A menos que estemos en la película "Avatar"). Si tu juego necesita un paisaje tan fantástico, es genial, pero si necesitas un modelo más realista, necesitamos ajustar un poco la función fractal. Afortunadamente, la función ScaleDomain puede hacer esto .

Queremos que la función se comporte como una función de mapa de altura. Imagine un mapa de altura 2D en el que cada punto del mapa representa la altura de un punto en la cuadrícula de puntos de cuadrícula que se elevan hacia arriba o hacia abajo. Los valores blancos del mapa indican colinas altas, negro - valles bajos. Necesitamos un comportamiento similar, pero para lograrlo, necesitamos deshacernos esencialmente de una de las dimensiones. En el caso de un mapa de altura, creamos una elevación 3D a partir de un mapa de altura 2D. Del mismo modo, en el caso del terreno 2D, necesitamos un mapa de altura 1D. Después de hacer que todos los puntos de un fractal con la misma coordenada Y tengan el mismo valor, podemos cambiar todos los puntos con la misma coordenada X en la misma cantidad, por lo que las islas voladoras desaparecen. Para hacer esto, puede usar ScaleDomain, restableciendo el coeficiente de escala. Es decir, antes de llamar a la función ground_shape_fractal, llamamos a ground_scale_y para establecer la coordenada y en 0. Esto asegura que el valor Y no afecte la salida del fractal, esencialmente convirtiéndolo en una función de ruido 1D. Para hacer esto, haremos los siguientes cambios:

 terraintree =
 {
	 {name = "ground_gradient", type = "gradiente", x1 = 0, x2 = 0, y1 = 0, y2 = 1},
	 {name = "ground_shape_fractal", type = "fractal", fractaltype = anl.FBM, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 6, frecuencia = 2},
	 {name = "ground_scale", type = "scaleoffset", scale = 0.5, offset = 0, source = "ground_shape_fractal"},
	 {name = "ground_scale_y", type = "scaledomain", source = "ground_scale", scaley = 0},
	 {name = "ground_perturb", type = "translateomain", source = "ground_gradient", ty = "ground_scale_y"},
	
	 {name = "ground_select", type = "select", low = 0, high = 1, umbral = 0.5, control = "ground_perturb"}
 }

Encadenaremos la función ScaleDomain con ground_scale y luego modificaremos los datos originales ground_perturb para que sean una función ScaleDomain. Esto cambiará el fractal que desplaza la tierra y la convierte en algo así:


Ahora, si echamos un vistazo a la salida, obtenemos el resultado:


Mucho mejor Las islas voladoras han desaparecido por completo, y el relieve es más parecido a montañas y colinas. Desafortunadamente, perdimos salientes y acantilados. Ahora toda la tierra es continua e inclinada. Si lo desea, puede arreglar esto de varias maneras.

Primero, puede usar otra función TranslateDomain , junto con otra función Fractal . Si aplicamos una pequeña cantidad de turbulencia fractal a la dirección X, podemos distorsionar ligeramente los bordes y las superficies de las montañas, y esto probablemente será suficiente para formar precipicios y salientes. Miremos en acción.

 terraintree =
 {
	 {name = "ground_gradient", type = "gradiente", x1 = 0, x2 = 0, y1 = 0, y2 = 1},
	 {name = "ground_shape_fractal", type = "fractal", fractaltype = anl.FBM, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 6, frecuencia = 2},
	 {name = "ground_scale", type = "scaleoffset", scale = 0.5, offset = 0, source = "ground_shape_fractal"},
	 {name = "ground_scale_y", type = "scaledomain", source = "ground_scale", scaley = 0},
	 {name = "ground_perturb", type = "translateomain", source = "ground_gradient", ty = "ground_scale_y"},
	 {name = "ground_overhang_fractal", type = "fractal", fractaltype = anl.FBM, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octavas = 6, frecuencia = 2},
	 {name = "ground_overhang_scale", type = "scaleoffset", source = "ground_overhang_fractal", scale = 0.2, offset = 0},
	 {name = "ground_overhang_perturb", type = "translateomain", source = "ground_perturb", tx = "ground_overhang_scale"},
	
	 {name = "ground_select", type = "select", low = 0, high = 1, umbral = 0.5, control = "ground_overhang_perturb"}
 }

Y aquí está el resultado:


La segunda forma: simplemente puede establecer el parámetro de escala de la función ground_scale_y en un valor mayor que 0. Si deja una escala pequeña en Y, obtendremos una fracción de la variabilidad, sin embargo, cuanto mayor sea la escala, más fuerte será el relieve que se asemejará a la versión anterior sin escala.


Los resultados parecen mucho más interesantes que las montañas en pendiente ordinarias. Sin embargo, no importa cuán interesantes sean, el jugador aún se aburrirá de explorar el relieve con el mismo patrón, extendiéndose por muchos kilómetros. Además, tal alivio sería muy poco realista. En el mundo real, hay mucha variabilidad que hace que el terreno sea más interesante. Veamos qué se puede hacer para hacer que el mundo sea más diverso.

Mirando el ejemplo de código anterior, puede ver un patrón específico en él. Tenemos una función de gradiente, que es controlada por funciones que le dan forma a la tierra, después de lo cual se aplica una función definida por partes y la tierra se llena. Es decir, será más lógico complicar el alivio en la etapa de dar forma a la tierra. En lugar de un fractal que se desplaza a lo largo de Y y otro que se desplaza a lo largo de X, podemos lograr el grado de complejidad requerido (teniendo en cuenta el rendimiento: cada fractal requiere costos computacionales adicionales, por lo que debemos tratar de ser conservadores). Podemos especificar las formas de la tierra, que son montañas, estribaciones , tierras bajas planas, tierras baldías, etc., y use la salida de las diversas funciones de selección encadenadas con fractales de baja frecuencia para delinear áreas de cada tipo. Entonces, veamos cómo puede implementar diferentes tipos de terreno.

Para ilustrar el principio, distinguimos tres tipos de relieve: mesetas (colinas de pendiente suave), montañas y tierras bajas (en su mayoría planas). Para cambiar entre ellos, utilizamos un sistema basado en selección y los combinamos en un lienzo complejo. Así que aquí vamos ...

Estribaciones:

Con ellos, todo es simple. Podemos tomar el esquema utilizado anteriormente, reducir ligeramente la amplitud de las colinas, tal vez incluso hacerlas más sustractivas que aditivas. para bajar las alturas medias. También podemos reducir el recuento de octavas para suavizarlas.

 {name = "lowland_shape_fractal", type = "fractal", fractaltype = anl.FBM, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 2, frecuencia = 1},
 {name = "lowland_autocorrect", type = "autocorrect", source = "lowland_shape_fractal", low = 0, high = 1},
 {name = "lowland_scale", type = "scaleoffset", source = "lowland_autocorrect", scale = 0.2, offset = -0.25},
 {name = "lowland_y_scale", type = "scaledomain", source = "lowland_scale", scaley = 0},
 {name = "lowland_terrain", type = "translateomain", source = "ground_gradient", ty = "lowland_y_scale"},

Tierras altas:

Con ellos, también, todo es simple. (De hecho, ninguno de estos tipos de terreno es difícil). Sin embargo, utilizamos una base diferente para hacer que las colinas se vean como dunas.

 {name = "highland_shape_fractal", type = "fractal", fractaltype = anl.RIDGEDMULTI, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octavas = 2, frecuencia = 2},
 {name = "highland_autocorrect", type = "autocorrect", source = "highland_shape_fractal", low = 0, high = 1},
 {name = "highland_scale", type = "scaleoffset", source = "highland_autocorrect", scale = 0.45, offset = 0},
 {name = "highland_y_scale", type = "scaledomain", source = "highland_scale", scaley = 0},
 {name = "highland_terrain", type = "translateomain", source = "ground_gradient", ty = "highland_y_scale"},

Montañas:

 {name = "mountain_shape_fractal", type = "fractal", fractaltype = anl.BILLOW, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 4, frecuencia = 1},
 {name = "mountain_autocorrect", type = "autocorrect", source = "mountain_shape_fractal", low = 0, high = 1},
 {name = "mountain_scale", type = "scaleoffset", source = "mountain_autocorrect", scale = 0.75, offset = 0.25},
 {name = "mountain_y_scale", type = "scaledomain", source = "mountain_scale", scaley = 0.1},
 {name = "mountain_terrain", type = "translateomain", source = "ground_gradient", ty = "mountain_y_scale"},

Por supuesto, puede abordar este proceso de manera aún más creativa, pero en general el patrón será así. Destacamos las características del tipo de relieve y seleccionamos funciones de ruido para ellos. Para todo esto, se aplican los mismos principios; Las principales diferencias son la escala. Ahora, para conectarlos, prepararemos fractales adicionales que controlarán la función Seleccionar . Luego, encadenamos los módulos Select para generar todo el terreno.

 {nombre = "terreno_tipo_fractal", tipo = "fractal", fractaltipo = anl.FBM, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octavas = 3, frecuencia = 0.5},
 {nombre = "terreno_autocorrección", tipo = "autocorrección", fuente = "terreno_tipo_fractal", bajo = 0, alto = 1},
 {nombre = "terrenos_tipo_caché", tipo = "caché", fuente = "relieve_autocorrección"},
 {name = "highland_mountain_select", type = "select", low = "highland_terrain", high = "mountain_terrain", control = "terrenos_tipo_caché", umbral = 0.55, caída = 0.15},
 {name = "highland_lowland_select", type = "select", low = "lowland_terrain", high = "highland_mountain_select", control = "terrenos_tipo_caché", umbral = 0.25, caída = 0.15},

Entonces, aquí definimos tres tipos principales de terreno: tierras bajas, tierras altas y montañas. Usamos un fractal para seleccionar uno de ellos, de modo que haya transiciones naturales (tierras bajas-> tierras altas-> montañas). Luego usamos otro fractal para insertar aleatoriamente tierras baldías en el mapa. Así es como se ve la cadena de módulos terminada:

 terraintree =
 {
	 {name = "lowland_shape_fractal", type = "fractal", fractaltype = anl.FBM, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 2, frecuencia = 1},
	 {name = "lowland_autocorrect", type = "autocorrect", source = "lowland_shape_fractal", low = 0, high = 1},
	 {name = "lowland_scale", type = "scaleoffset", source = "lowland_autocorrect", scale = 0.2, offset = -0.25},
	 {name = "lowland_y_scale", type = "scaledomain", source = "lowland_scale", scaley = 0},
	 {name = "lowland_terrain", type = "translateomain", source = "ground_gradient", ty = "lowland_y_scale"},
	 {name = "ground_gradient", type = "gradiente", x1 = 0, x2 = 0, y1 = 0, y2 = 1},
	 {name = "highland_shape_fractal", type = "fractal", fractaltype = anl.RIDGEDMULTI, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octavas = 2, frecuencia = 2},
	 {name = "highland_autocorrect", type = "autocorrect", source = "highland_shape_fractal", low = 0, high = 1},
	 {name = "highland_scale", type = "scaleoffset", source = "highland_autocorrect", scale = 0.45, offset = 0},
	 {name = "highland_y_scale", type = "scaledomain", source = "highland_scale", scaley = 0},
	 {name = "highland_terrain", type = "translateomain", source = "ground_gradient", ty = "highland_y_scale"},

	 {name = "mountain_shape_fractal", type = "fractal", fractaltype = anl.BILLOW, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 4, frecuencia = 1},
	 {name = "mountain_autocorrect", type = "autocorrect", source = "mountain_shape_fractal", low = 0, high = 1},
	 {name = "mountain_scale", type = "scaleoffset", source = "mountain_autocorrect", scale = 0.75, offset = 0.25},
	 {name = "mountain_y_scale", type = "scaledomain", source = "mountain_scale", scaley = 0.1},
	 {name = "mountain_terrain", type = "translateomain", source = "ground_gradient", ty = "mountain_y_scale"},

	 {nombre = "terreno_tipo_fractal", tipo = "fractal", fractaltipo = anl.FBM, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octavas = 3, frecuencia = 0.5},
	 {nombre = "terreno_autocorrección", tipo = "autocorrección", fuente = "terreno_tipo_fractal", bajo = 0, alto = 1},
	 {nombre = "terrenos_tipo_caché", tipo = "caché", fuente = "relieve_autocorrección"},
	 {name = "highland_mountain_select", type = "select", low = "highland_terrain", high = "mountain_terrain", control = "terrenos_tipo_caché", umbral = 0.55, caída = 0.15},
	 {name = "highland_lowland_select", type = "select", low = "lowland_terrain", high = "highland_mountain_select", control = "terrenos_tipo_caché", umbral = 0.25, caída = 0.15},
	 {name = "ground_select", type = "select", low = 0, high = 1, umbral = 0.5, control = "highland_lowland_select"}
 }

Aquí hay algunos ejemplos de los relieves resultantes:




Puede notar que se obtiene una variabilidad bastante alta. En algunos lugares, aparecen elevadas montañas rotas, en otros hay llanuras de pendiente suave. Ahora necesitamos agregar cuevas para poder explorar las maravillas del inframundo.

Para las cuevas, uso el sistema multiplicativo aplicado a ground_select . Es decir, creo una función que genera 1 o 0, y las multiplico por la salida de ground_select . Gracias a esto, cualquier punto de la función se vuelve hueco para el cual el valor de la función de las cuevas es 0. Es decir, donde quiero obtener la cueva, la función de las cuevas debería devolver 0, y donde la cueva no debería estar, la función debería ser 1. En cuanto a la forma cuevas, quiero establecer un sistema de cuevas basado en el Multifractal Ridged de 1 octava.

 {name = "cave_shape", type = "fractal", fractaltype = anl.RIDGEDMULTI, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 1, frecuencia = 2},

El resultado es algo como esto:


Si aplicamos la función Seleccionar como una función definida por partes, como lo hicimos con el gradiente de tierra, implementándola de modo que la parte inferior del umbral de selección sea 1 (no hay cueva), y la parte superior es 0 (hay una cueva), el resultado se verá más o menos así :

 {name = "cave_shape", type = "fractal", fractaltype = anl.RIDGEDMULTI, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 1, frecuencia = 2},
 {name = "cave_select", type = "select", low = 1, high = 0, control = "cave_shape", umbral = 0.8, caída = 0},

Resultado:


Por supuesto, se ve bastante suave, así que agrega un poco de ruido fractal para distorsionar el área.

 {name = "cave_shape", type = "fractal", fractaltype = anl.RIDGEDMULTI, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 1, frecuencia = 2},
 {name = "cave_select", type = "select", low = 1, high = 0, control = "cave_shape", umbral = 0.8, caída = 0},
 {name = "cave_perturb_fractal", type = "fractal", fractaltype = anl.FBM, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 6, frecuencia = 3},
 {name = "cave_perturb_scale", type = "scaleoffset", source = "cave_perturb_fractal", scale = 0.25, offset = 0},
{name = "cave_perturb", type = "translateomain", source = "cave_select", tx = "cave_perturb_scale"},

Resultado:


Esto hace que las cuevas sean un poco ruidosas y que no sean tan suaves. Veamos ahora qué sucede si aplicas las cuevas al relieve:


Al experimentar con el valor umbral en cave_select , podemos hacer que las cuevas sean más delgadas o más gruesas. Pero lo principal que debemos intentar es asegurarnos de que las cuevas no carcoman esos enormes fragmentos del relieve superficial. Para hacer esto, podemos volver a la función highland_lowland_select , que, como recordamos, es la última función de alivio que distorsiona el gradiente de la tierra. Lo que es útil en esta función es que sigue siendo un gradiente, lo que aumenta el valor cuando la función se profundiza en el suelo. Podemos usar el gradiente para debilitar la función de las cuevas para que las cuevas aumenten a medida que se adentran en el suelo. Afortunadamente para nosotros, esta atenuación se puede lograr simplemente multiplicando la salida de la función highland_lowland_selecta la salida de cave_shape , y luego pasa el resultado al resto de la cadena de funciones. A continuación, haremos un cambio importante aquí: agregue la función Caché . La función de almacenamiento en caché guarda el resultado de la función para la coordenada entrante dada, y si la función se llama repetidamente con la misma coordenada, devolverá la copia en caché y no calculará el resultado nuevamente. Esto es útil en situaciones donde una función compleja ( highland_lowland_select ) en una cadena de funciones se llama varias veces. Sin un caché, la cadena completa de una función compleja se recalcula con cada llamada. Para agregar el caché, primero debemos hacer los siguientes cambios:

{name = "highland_lowland_select", type = "select", low = "lowland_terrain", high = "highland_mountain_select", control = "terrenos_tipo_caché", umbral = 0.25, caída = 0.15},
{name = "highland_lowland_select_cache", type = "cache", source = "highland_lowland_select"},
{name = "ground_select", type = "select", low = 0, high = 1, umbral = 0.5, control = "highland_lowland_select_cache"},

Por lo tanto, agregamos Cache y luego redirigimos la entrada a ground_select para que se tomara del caché y no directamente de la función. Luego podemos cambiar el código de las cuevas para agregar atenuación:

{name = "cave_shape", type = "fractal", fractaltype = anl.RIDGEDMULTI, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 1, frecuencia = 4},
{name="cave_attenuate_bias", type="bias", source="highland_lowland_select_cache", bias=0.45},
{name="cave_shape_attenuate", type="combiner", operation=anl.MULT, source_0="cave_shape", source_1="cave_attenuate_bias"},
{name="cave_perturb_fractal", type="fractal", fractaltype=anl.FBM, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=6, frequency=3},
{name="cave_perturb_scale", type="scaleoffset", source="cave_perturb_fractal", scale=0.5, offset=0},
{name="cave_perturb", type="translatedomain", source="cave_shape_attenuate", tx="cave_perturb_scale"},
{name="cave_select", type="select", low=1, high=0, control="cave_perturb", threshold=0.48, falloff=0},

En primer lugar, agregamos la función Bias . Esto es por conveniencia, porque nos permite ajustar el intervalo de la función de atenuación de gradiente. Luego se agrega la función cave_shape_attenuate , que es un Combinador de tipo anl :: MULT . Ella multiplica el gradiente por cave_shape . Luego, el resultado de esta operación se pasa a la función cave_perturb . El resultado se parece a esto:


Vemos que más cerca de la superficie de la tierra se han vuelto más delgados. (No preste atención a la parte superior, esto es solo un artefacto de valores de gradiente negativos, no afecta a las cuevas terminadas. Si esto se convierte en un problema, digamos que si usamos esta función para otra cosa, entonces podemos limitar el gradiente al intervalo (0, 1).) Es un poco difícil ver cómo funciona esto en relación con el terreno, así que avancemos y organicemos todo para ver qué sucede. Aquí está toda la cadena de funciones que hemos creado hasta ahora.

terraintree =
 {
	{name = "ground_gradient", type = "gradiente", x1 = 0, x2 = 0, y1 = 0, y2 = 1},
	
	{name = "lowland_shape_fractal", type = "fractal", fractaltype = anl.BILLOW, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 2, frecuencia = 0.25},
	{name="lowland_autocorrect", type="autocorrect", source="lowland_shape_fractal", low=0, high=1},
	{name="lowland_scale", type="scaleoffset", source="lowland_autocorrect", scale=0.125, offset=-0.45},
	{name="lowland_y_scale", type="scaledomain", source="lowland_scale", scaley=0},
	{name="lowland_terrain", type="translatedomain", source="ground_gradient", ty="lowland_y_scale"},
	
	{name="highland_shape_fractal", type="fractal", fractaltype=anl.FBM, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=4, frequency=2},
	{name="highland_autocorrect", type="autocorrect", source="highland_shape_fractal", low=-1, high=1},
	{name="highland_scale", type="scaleoffset", source="highland_autocorrect", scale=0.25, offset=0},
	{name="highland_y_scale", type="scaledomain", source="highland_scale", scaley=0},
	{name="highland_terrain", type="translatedomain", source="ground_gradient", ty="highland_y_scale"},

	{name="mountain_shape_fractal", type="fractal", fractaltype=anl.RIDGEDMULTI, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=8, frequency=1},
	{name="mountain_autocorrect", type="autocorrect", source="mountain_shape_fractal", low=-1, high=1},
	{name="mountain_scale", type="scaleoffset", source="mountain_autocorrect", scale=0.45, offset=0.15},
	{name="mountain_y_scale", type="scaledomain", source="mountain_scale", scaley=0.25},
	{name="mountain_terrain", type="translatedomain", source="ground_gradient", ty="mountain_y_scale"},

	{name="terrain_type_fractal", type="fractal", fractaltype=anl.FBM, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=3, frequency=0.125},
	{name="terrain_autocorrect", type="autocorrect", source="terrain_type_fractal", low=0, high=1},
	{name="terrain_type_y_scale", type="scaledomain", source="terrain_autocorrect", scaley=0},
	{name="terrain_type_cache", type="cache", source="terrain_type_y_scale"},
	{name="highland_mountain_select", type="select", low="highland_terrain", high="mountain_terrain", control="terrain_type_cache", threshold=0.55, falloff=0.2},
	{name="highland_lowland_select", type="select", low="lowland_terrain", high="highland_mountain_select", control="terrain_type_cache", threshold=0.25, falloff=0.15},
	{name="highland_lowland_select_cache", type="cache", source="highland_lowland_select"},
	{name="ground_select", type="select", low=0, high=1, threshold=0.5, control="highland_lowland_select_cache"},
	
	{name="cave_shape", type="fractal", fractaltype=anl.RIDGEDMULTI, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=1, frequency=4},
	{name="cave_attenuate_bias", type="bias", source="highland_lowland_select_cache", bias=0.45},
	{name="cave_shape_attenuate", type="combiner", operation=anl.MULT, source_0="cave_shape", source_1="cave_attenuate_bias"},
	{name="cave_perturb_fractal", type="fractal", fractaltype=anl.FBM, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=6, frequency=3},
	{name="cave_perturb_scale", type="scaleoffset", source="cave_perturb_fractal", scale=0.5, offset=0},
	{name="cave_perturb", type="translatedomain", source="cave_shape_attenuate", tx="cave_perturb_scale"},
	{name="cave_select", type="select", low=1, high=0, control="cave_perturb", threshold=0.48, falloff=0},
	
	{name = "ground_cave_multiply", type = "combiner", operation = anl.MULT, source_0 = "cave_select", source_1 = "ground_select"}
}

Aquí hay ejemplos de tarjetas aleatorias derivadas de esta función:




Ahora todo se ve bastante bien. Todas las cuevas son cavernas bastante grandes en las profundidades del subsuelo, pero más cerca de la superficie generalmente se convierten en pequeños túneles. Esto ayuda a crear una atmósfera de misterio. Al explorar la superficie, encontrarás una pequeña entrada a la cueva. ¿A dónde va ella? ¿Qué tan profundo se extiende? No podemos saber esto, pero en el proceso de estudio comienza a expandirse, convirtiéndose en un extenso sistema de cavernas llenas de oscuridad y peligros. Y botín, por supuesto. Siempre hay mucho botín.

Puede cambiar este sistema de muchas maneras diferentes, obteniendo resultados diferentes. Podemos cambiar los parámetros de umbral para cave_select y los parámetros para cave_attenuate_bias , o reemplazar cave_attenuate_biasotras funciones para hacer coincidir el intervalo de gradiente con otros valores que se adapten mejor a sus necesidades. También puede agregar otro fractal que distorsiona el sistema de cuevas a lo largo del eje Y para eliminar la posibilidad de túneles anormalmente lisos a lo largo del eje X (causado por el hecho de que la forma de la cueva se distorsiona solo a lo largo del eje X). También puede agregar un nuevo fractal como fuente adicional de atenuación, especificar una tercera fuente para cave_shape_attenuate , que escala la atenuación en función de las regiones, de modo que las cuevas en algunas áreas sean más densas (por ejemplo, en las montañas), y con menos frecuencia o completamente ausentes en otras. Esta selección regional se puede crear desde la función del terreno_tipo_fractalpara saber dónde se encuentran las zonas de montaña. Todo se reduce a solo pensar en lo que desea, descubrir qué efecto tendrán las diferentes funciones en la salida y experimentar con los parámetros hasta obtener el resultado deseado. Esta no es una ciencia exacta y, a menudo, el efecto deseado se puede alcanzar de diferentes maneras.

Desventajas


Este método de generación de terreno tiene desventajas. El proceso de generación de ruido puede ser bastante lento. Es importante reducir la cantidad de fractales, la cantidad de octavas de esos fractales que usa y otras operaciones lentas, si es posible. Intente usar fractales varias veces y guarde en caché todas las funciones que se llaman varias veces. En este ejemplo, utilicé libremente fractales, creando uno para cada uno de los tres tipos de relieve. Usando ScaleOffset para cambiar los intervalos y tomando un fractal como base para todos ellos, ahorraría mucho tiempo de procesador. En 2D, no todo es tan malo, pero cuando llegas a 3D e intentas comparar las cantidades de datos, el tiempo de procesamiento aumentará significativamente.

Ir a 3D


Todo esto es genial si creas un juego como Terraria o King Arthur's Gold , pero ¿qué pasa si necesitas algo como Minecraft o Infiniminer?? ¿Qué cambios necesitaremos hacer en la cadena de funciones? De hecho, no hay muchos. La función que se muestra arriba funcionará casi sin modificaciones para el relieve 3D. Será suficiente para que compare el volumen 3D utilizando las variaciones 3D del generador, y también para comparar el eje Y con el eje vertical del volumen, y no con la región 2D. Sin embargo, se requerirá un cambio, a saber, una forma de realizar las cuevas. Como viste, Ridged Multifractal es ideal para un sistema de cuevas 2D, pero en 3D corta muchas conchas curvas, no túneles, y su efecto resulta ser incorrecto. Es decir, en 3D es necesario especificar dos formas fractales de cuevas, ambas son ruido Multifractal Ridged de 1 octava, pero con semillas diferentes. Con Seleccionar, configúrelos en 1 o 0 y multiplíquelos. Por lo tanto, en la intersección de los fractales, aparecerá una cueva,y todo lo demás permanecerá sólido, y la apariencia de los túneles se volverá más natural que usar un solo fractal.

terraintree3d=
 {
	{name="ground_gradient", type="gradient", x1=0, x2=0, y1=0, y2=1},
	
	{name="lowland_shape_fractal", type="fractal", fractaltype=anl.BILLOW, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=2, frequency=0.25},
	{name="lowland_autocorrect", type="autocorrect", source="lowland_shape_fractal", low=0, high=1},
	{name="lowland_scale", type="scaleoffset", source="lowland_autocorrect", scale=0.125, offset=-0.45},
	{name="lowland_y_scale", type="scaledomain", source="lowland_scale", scaley=0},
	{name="lowland_terrain", type="translatedomain", source="ground_gradient", ty="lowland_y_scale"},
	
	{name="highland_shape_fractal", type="fractal", fractaltype=anl.FBM, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=4, frequency=2},
	{name="highland_autocorrect", type="autocorrect", source="highland_shape_fractal", low=-1, high=1},
	{name="highland_scale", type="scaleoffset", source="highland_autocorrect", scale=0.25, offset=0},
	{name="highland_y_scale", type="scaledomain", source="highland_scale", scaley=0},
	{name="highland_terrain", type="translatedomain", source="ground_gradient", ty="highland_y_scale"},

	{name="mountain_shape_fractal", type="fractal", fractaltype=anl.RIDGEDMULTI, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=8, frequency=1},
	{name="mountain_autocorrect", type="autocorrect", source="mountain_shape_fractal", low=-1, high=1},
	{name="mountain_scale", type="scaleoffset", source="mountain_autocorrect", scale=0.45, offset=0.15},
	{name="mountain_y_scale", type="scaledomain", source="mountain_scale", scaley=0.25},
	{name="mountain_terrain", type="translatedomain", source="ground_gradient", ty="mountain_y_scale"},

	{name="terrain_type_fractal", type="fractal", fractaltype=anl.FBM, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=3, frequency=0.125},
	{name="terrain_autocorrect", type="autocorrect", source="terrain_type_fractal", low=0, high=1},
	{name="terrain_type_y_scale", type="scaledomain", source="terrain_autocorrect", scaley=0},
	{name="terrain_type_cache", type="cache", source="terrain_type_y_scale"},
	{name="highland_mountain_select", type="select", low="highland_terrain", high="mountain_terrain", control="terrain_type_cache", threshold=0.55, falloff=0.2},
	{name="highland_lowland_select", type="select", low="lowland_terrain", high="highland_mountain_select", control="terrain_type_cache", threshold=0.25, falloff=0.15},
	{name="highland_lowland_select_cache", type="cache", source="highland_lowland_select"},
	{name="ground_select", type="select", low=0, high=1, threshold=0.5, control="highland_lowland_select_cache"},
	
	{name="cave_attenuate_bias", type="bias", source="highland_lowland_select_cache", bias=0.45},
	{name="cave_shape1", type="fractal", fractaltype=anl.RIDGEDMULTI, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=1, frequency=4},
	{name="cave_shape2", type="fractal", fractaltype=anl.RIDGEDMULTI, basistype=anl.GRADIENT, interptype=anl.QUINTIC, octaves=1, frequency=4},
	{name="cave_shape_attenuate", type="combiner", operation=anl.MULT, source_0="cave_shape1", source_1="cave_attenuate_bias", source_2="cave_shape2"},             
	{name = "cave_perturb_fractal", type = "fractal", fractaltype = anl.FBM, basistype = anl.GRADIENT, interptype = anl.QUINTIC, octaves = 6, frecuencia = 3},
	{name = "cave_perturb_scale", type = "scaleoffset", source = "cave_perturb_fractal", scale = 0.5, offset = 0},
	{name = "cave_perturb", type = "translateomain", source = "cave_shape_attenuate", tx = "cave_perturb_scale"},
	{name = "cave_select", type = "select", low = 1, high = 0, control = "cave_perturb", umbral = 0.48, falloff = 0},
	
	{name = "ground_cave_multiply", type = "combiner", operation = anl.MULT, source_0 = "cave_select", source_1 = "ground_select"}
}

Ejemplos de resultados:



Parece que algunas de las configuraciones requieren ajuste. Puede valer la pena reducir la atenuación o hacer que las cuevas sean más delgadas, reduciendo el número de octavas en el fractal del relieve, para que el relieve se vuelva más suave, etc. ... Repito, todo depende del resultado que se desee obtener.

Source: https://habr.com/ru/post/472574/


All Articles