Hidrología de procedimiento: simulación dinámica de ríos y lagos.

Nota: el código fuente completo del proyecto se publica en Github [ aquí ]. El repositorio también contiene información detallada sobre cómo leer y usar el código.

Después de implementar la simulación de erosión hidráulica basada en partículas, decidí que sería posible expandir este concepto para simular otros aspectos de la hidrología superficial.

Investigué los métodos existentes de generación procesal de ríos y lagos, pero los resultados encontrados no me convenían.

El objetivo principal de muchos métodos es crear sistemas fluviales (muy hermosos) usando varios algoritmos (a veces basados ​​en un mapa de elevación creado previamente o un problema inverso), pero carecen de una fuerte relación realista entre la topografía y la hidrología.

Además, el procesamiento del modelo de agua en el relieve en su conjunto se considera en algunos recursos y se utiliza una simulación de fluidos altamente complejos.

En este artículo, demostraré mi intento de superar estos problemas con una técnica que extiende la capacidad de simular la erosión hidráulica basada en partículas. También explicaré cómo, en general, resuelvo el problema del "agua en el alivio".

En mi método, me esfuerzo tanto por la simplicidad como por el realismo a costa de un ligero aumento en la complejidad del sistema de erosión subyacente. Recomiendo leer mi artículo anterior sobre este sistema [ aquí , traducción en Habré], porque el nuevo modelo se basa en él.


Este sistema es capaz de generar rápidamente un terreno de aspecto muy realista con hidrología. Este video fue renderizado en tiempo real. El sistema es capaz de generar un número infinito de tales paisajes.

Explicación: no soy geólogo, así que creé el sistema basado en mi conocimiento.

Concepto de hidrología


Quiero crear un sistema generativo que pueda simular muchos fenómenos geográficos, que incluyen:

  • Migración de ríos y arroyos
  • Cascadas naturales
  • Formación del cañón
  • Hinchazón del suelo y llanuras aluviales.

En consecuencia, los sistemas de hidrología y topografía deben ser dinámicos y estrechamente relacionados. El sistema de erosión hidráulica basado en partículas ya tiene los aspectos básicos necesarios para esto:

  • El alivio afecta el movimiento del agua.
  • La erosión y la sedimentación afectan el terreno.

De hecho, este sistema simula la erosión causada por la lluvia, pero no puede transmitir muchas otras influencias:

  • En una corriente en movimiento , el agua se comporta de manera diferente.
  • En una piscina de pie , el agua se comporta de manera diferente.

Nota: a menudo mencionaré arroyos y piscinas . Se asume en el modelo que estos son fenómenos bidimensionales a gran escala. Reducen en gran medida la complejidad del modelo.

La mayoría de los fenómenos geográficos anteriores pueden transmitirse utilizando el modelo de flujos y cuencas. Idealmente, deberían tomar prestado y mejorar el realismo de un sistema basado en partículas.

Modelo hidrológico simplificado


Almacenar información sobre flujos y agrupaciones en una o más estructuras de datos (gráficos, objetos, etc.) es demasiado complejo y limita nuestras capacidades.

Por lo tanto, nuestro modelo hidrológico consta de dos mapas: mapas de flujo y mapas de cuenca .

Nota: no olvide que están modelados como sistemas 2D.

Mapas de corrientes y piscinas


El mapa de flujo describe el flujo de agua en la superficie (arroyos y ríos). Almacena las posiciones de partículas promediadas en el tiempo en el mapa. La información antigua se está eliminando lentamente.

El mapa de la cuenca describe el agua inmóvil en la superficie (charcos, estanques, lagos, océanos). Almacena la profundidad del agua en la posición correspondiente del mapa.

Nota: los mapas de flujo y agrupación son matrices del mismo tamaño que el mapa de altura.


Alivio con impacto hidrológico. La capa de agua en el renderizado se toma de mapas hidrológicos.


Mapas hidrológicos combinados. El azul claro es un mapa de flujo; el azul oscuro es un mapa de piscina.

Estos mapas son generados y conectados por partículas que mueven el agua a lo largo del terreno. Agregar estos mapas hidrológicos también nos brinda información independiente del tiempo que permite la interacción de las partículas con su simulación por separado. Las partículas pueden interactuar mediante el uso de estas tarjetas para obtener parámetros que afectan su movimiento.

Nota: el mapa de flujo se muestra después de pasar por la función de entrada y salida y se representa en el terreno en función de esto. Para obtener flujos más finos / nítidos (o ignorar valores más bajos en áreas planas amplias), esta pantalla se puede modificar o establecer valores de umbral para ello.

El agua como partícula.


El agua se presenta en forma de una masa discreta ("partículas") que tiene un volumen y se mueve a lo largo de la superficie del relieve. Tiene varios parámetros que afectan su movimiento (fricción, velocidad de evaporación, velocidad de deposición, etc.).

Esta es la estructura básica de datos utilizada para simular la hidrología. Las partículas no se almacenan , pero se usan solo para la interacción entre mapas de alturas, flujos y piscinas.

Nota: el concepto de una partícula se explica con más detalle en una publicación anterior [ traducción en Habré] (y un número infinito de otros recursos).

Ciclo hidrológico e interacción cartográfica.


Los mapas interactúan entre sí a través de un ciclo hidrológico. El ciclo hidrológico consta de los siguientes pasos:

  • Creando una partícula en el terreno
  • (.. ).
  • , .
  • , .
  • , ( ) .
  • .

En todo el sistema, solo hay dos algoritmos: Descenso (descenso) e Inundación (inundación) . Las partículas descendentes cambian el mapa de flujos, y las partículas de inundación cambian el mapa de cuencas. Estos algoritmos se describen en detalle a continuación.


Diagrama unidimensional del modelo hidrológico. Las partículas se crean en el terreno y se procesan cíclicamente mediante dos algoritmos: Descenso e Inundación. En el proceso, los mapas de las cuencas y los flujos cambian, lo que a su vez afecta el movimiento de las partículas.

Implementación


A continuación explicaré la implementación completa del sistema utilizado para generar los resultados y presentaré ejemplos de código.

Nota: solo mostraré piezas de código relevantes. Se puede encontrar más información en el repositorio en Github. Todas las partes relevantes del código están en el archivo "water.h".

Clase de partícula


La estructura de la partícula Drop es idéntica a la estructura del sistema anterior. Descender e inundar ahora son miembros de la estructura porque actúan sobre una sola partícula a la vez.

struct Drop{
  
  //... constructors

  int index;                         //Flat Array Index
  glm::vec2 pos;                     //2D Position
  glm::vec2 speed = glm::vec2(0.0);
  double volume = 1.0;
  double sediment = 0.0;

  //... parameters
  const double volumeFactor = 100.0; //"Water Deposition Rate"

  //Hydrological Cycle Functions
  void descend(double* h, double* stream, double* pool, bool* track, glm::ivec2 dim, double scale);
  void flood(double* h, double* pool, glm::ivec2 dim);
};

Un parámetro adicional es el factor de volumen, que determina cómo las inundaciones transfieren el volumen al nivel del agua.

Algoritmo de descenso


El algoritmo de descenso es casi el mismo que el algoritmo simple de erosión de partículas. Recibe una "pista" de entrada adicional, una matriz en la que escribe todas las posiciones visitadas por él. Se necesita una matriz para construir mapas de flujo en el futuro.

void Drop::descend(double* h, double* stream, double* pool, bool* track, glm::ivec2 dim, double scale){

  glm::ivec2 ipos; 

  while(volume > minVol){

    ipos = pos; //Initial Position
    int ind = ipos.x*dim.y+ipos.y; //Flat Array Index

    //Register Position
    track[ind] = true;

    //...
  }
};

El conjunto de parámetros se modifica mediante los mapas de flujo y agrupación:

//...
  //Effective Parameter Set
  double effD = depositionRate;
  double effF = friction*(1.0-0.5*stream[ind]);
  double effR = evapRate*(1.0-0.2*stream[ind]);
//...

Nota: descubrí que dicho cambio de parámetro funciona bien.

En el sistema anterior, las partículas podían salir de este ciclo y ser destruidas solo con la evaporación completa o yendo más allá de los límites del relieve. Ahora se han agregado dos condiciones de salida adicionales aquí:

//... nind is the next position after moving the particle
  
  //Out-Of-Bounds
  if(!glm::all(glm::greaterThanEqual(pos, glm::vec2(0))) ||
     !glm::all(glm::lessThan((glm::ivec2)pos, dim))){
       volume = 0.0;
       break;
  }

  //Slow-Down
  if(stream[nind] > 0.5 && length(acc) < 0.01)
    break;

  //Enter Pool
  if(pool[nind] > 0.0)
    break;

//...

Si la partícula no tiene suficiente aceleración y está rodeada por otras partículas, o entra directamente a la piscina, entonces completa prematuramente el descenso con todo su volumen restante y pasa al algoritmo de inundación.

Nota: la condición de desbordamiento también restablece el volumen para que las partículas no pasen al algoritmo de inundación.

Algoritmo de inundación


Una partícula con el volumen restante puede inundar desde su posición actual. Esto sucede si deja de descender (no hay aceleración) o entra en un grupo existente.

El algoritmo de inundación transfiere el volumen de la partícula al aumento del nivel del agua, cambiando el mapa de la cuenca. La técnica consiste en aumentar gradualmente el nivel del agua en una fracción del volumen de la partícula utilizando el "plano de prueba". A medida que aumenta el nivel del agua, el volumen de partículas disminuye.


Animación del algoritmo de inundación. El plano de prueba y el nivel de agua aumentan gradualmente, reduciendo el volumen de partículas. Si se encuentra una fuga, el volumen restante se mueve al punto de fuga para realizar el descenso.

En cada paso, realizamos un relleno de inundación desde la posición de la partícula (es decir, verificamos recursivamente las posiciones de los vecinos), agregando todas las posiciones ubicadas sobre el plano original (nivel de agua actual) y debajo del plano de prueba al "conjunto de inundación". Esta es el área de alivio que forma parte de la piscina.

Durante el llenado, verificamos si hay fugas. Estos son puntos del conjunto de inundaciones que están debajo del plano de prueba Y del plano original. Si encontramos varios puntos de fuga, seleccionamos el más bajo.

void Drop::flood(double* height, double* pool, glm::ivec2 dim){

  index = (int)pos.x*dim.y + (int)pos.y;
  double plane = height[index] + pool[index];  //Testing Plane
  double initialplane = plane;                 //Water Level

  //Flood Set
  std::vector<int> set;
  int fail = 10; //Just in case...

  //Iterate while particle still has volume
  while(volume > minVol && fail){

    set.clear();
    bool tried[dim.x*dim.y] = {false};

    //Lowest Drain
    int drain;
    bool drainfound = false;

    //Recursive Flood-Fill Function
    std::function<void(int)> fill = [&](int i){

      //Out of Bounds
      if(i/dim.y >= dim.x || i/dim.y < 0) return;
      if(i%dim.y >= dim.y || i%dim.y < 0) return;

      //Position has been tried
      if(tried[i]) return;
      tried[i] = true;

      //Wall / Boundary of the Pool
      if(plane < height[i] + pool[i]) return;

      //Drainage Point
      if(initialplane > height[i] + pool[i]){

        //No Drain yet
        if(!drainfound)
          drain = i;

        //Lower Drain
        else if( pool[drain] + height[drain] < pool[i] + height[i] )
          drain = i;

        drainfound = true;
        return; //No need to flood from here
      }

      //Part of the Pool
      set.push_back(i);
      fill(i+dim.y);    //Fill Neighbors
      fill(i-dim.y);
      fill(i+1);
      fill(i-1);
      fill(i+dim.y+1);  //Diagonals (Improves Drainage)
      fill(i-dim.y-1);
      fill(i+dim.y-1);
      fill(i-dim.y+1);
    };

    //Perform Flood
    fill(index);

    //...

Nota: para simplificar, aquí se usa un algoritmo de relleno de ocho vías . En el futuro, será posible implementarlo de manera más efectiva.

Después de identificar las numerosas inundaciones y puntos de fuga, cambiamos el nivel del agua y el mapa de la piscina.

Si se encuentra un punto de fuga, movemos la partícula (y su volumen de "desbordamiento") al punto de fuga para que pueda comenzar a descender nuevamente. Luego el nivel del agua baja a la altura del punto de fuga.

    //...

    //Drainage Point
    if(drainfound){

      //Set the Particle Position
      pos = glm::vec2(drain/dim.y, drain%dim.y);

      //Set the New Waterlevel (Slowly)
      double drainage = 0.001;
      plane = (1.0-drainage)*initialplane + drainage*(height[drain] + pool[drain]);

      //Compute the New Height
      for(auto& s: set) //Iterate over Set
        pool[s] = (plane > height[s])?(plane-height[s]):0.0;

      //Remove some sediment
      sediment *= 0.1;
      break;
    }

    //...

Nota: cuando el nivel del agua disminuye debido a las fugas, descubrí que esto funciona mejor con una tasa baja de fugas. Además, la eliminación de parte de las rocas sedimentarias ayuda a la implementación.

Debido a esto, las nuevas partículas que ingresan al grupo lleno instantáneamente mueven su volumen al punto de fuga, porque al agregar volumen al grupo se desplaza el mismo volumen.

Si no se encuentra el punto de fuga, calculamos el volumen bajo el plano de prueba y lo comparamos con el volumen de la partícula. Si es menor, eliminamos el volumen de la partícula y ajustamos el nivel del agua. Entonces el avión de prueba se eleva. Si es más grande, entonces se baja el plano de prueba. El proceso se repite hasta que la partícula se queda sin espacio o se encuentra un punto de fuga.

    //...

    //Get Volume under Plane
    double tVol = 0.0;
    for(auto& s: set)
      tVol += volumeFactor*(plane - (height[s]+pool[s]));

    //We can partially fill this volume
    if(tVol <= volume && initialplane < plane){

      //Raise water level to plane height
      for(auto& s: set)
        pool[s] = plane - height[s];

      //Adjust Drop Volume
      volume -= tVol;
      tVol = 0.0;
    }

    //Plane was too high and we couldn't fill it
    else fail--;

    //Adjust Planes
    float approach = 0.5;
    initialplane = (plane > initialplane)?plane:initialplane;
    plane += approach*(volume-tVol)/(double)set.size()/volumeFactor;
  }

  //Couldn't place the volume (for some reason)- so ignore this drop.
  if(fail == 0)
    volume = 0.0;

} //End of Flood Algorithm

La altura del plano se ajusta en proporción a la diferencia en los volúmenes escalados por el área de la superficie de la piscina (es decir, por el tamaño del conjunto). Al usar el coeficiente de proximidad, puede aumentar la estabilidad de cómo el avión alcanza el nivel de agua correcto.

Envoltura de erosión


La clase mundial contiene los tres mapas en forma de matrices ordinarias:

class World {

public:
  void generate();            //Initialize
  void erode(int cycles);     //Erode with N Particles

  //...

  double heightmap[256*256] = {0.0};
  double waterstream[256*256] = {0.0};
  double waterpool[256*256] = {0.0};

};

Nota: El mapa de altura se inicializa utilizando el ruido Perlin.

Cada paso hidrológico para una partícula individual consta de lo siguiente:

//...

//Spawn Particle
glm::vec2 newpos = glm::vec2(rand()%(int)dim.x, rand()%(int)dim.y);
Drop drop(newpos);

int spill = 5;
while(drop.volume > drop.minVol && spill != 0){

  drop.descend(heightmap, waterstream, waterpool, track, dim, scale);

  if(drop.volume > drop.minVol)
    drop.flood(heightmap, waterpool, dim);

  spill--;
}

//...

El parámetro de derrame determina cuántas veces puede entrar una partícula en la piscina y dejarla nuevamente antes de que simplemente se destruya. De lo contrario, las partículas mueren cuando su volumen se agota.

Nota: las partículas rara vez ingresan a las piscinas y las dejan más de una o dos veces antes de que se evaporen por completo durante las etapas de descenso, pero agregué esto por si acaso.

La función de erosión envuelve este código y realiza pasos hidrológicos para las partículas de N, cambiando directamente el mapa de flujo:

void World::erode(int N){

  //Track the Movement of all Particles
  bool track[dim.x*dim.y] = {false};

  //Simulate N Particles
  for(int i = 0; i < N; i++){
   
    //... simulate individual particle

  }

  //Update Path
  double lrate = 0.01;  //Adaptation Rate
  for(int i = 0; i < dim.x*dim.y; i++)
    waterstream[i] = (1.0-lrate)*waterstream[i] + lrate*((track[i])?1.0:0.0);

}

Aquí el conjunto de pistas se pasa a la función de descenso. Descubrí que el seguimiento simultáneo del movimiento de varias partículas y los cambios correspondientes proporcionan mejores resultados para el mapa de flujo. La tasa de adaptación determina qué tan rápido se elimina la información antigua.

Arboles


Solo por diversión, agregué árboles para ver si la simulación de erosión podría mejorarse aún más. Se almacenan en el aula mundial como un vector.

Los árboles se crean aleatoriamente en el mapa en lugares donde no hay piscinas y corrientes fuertes, y el terreno no es muy empinado. También tienen la oportunidad de crear árboles adicionales a su alrededor.

En el proceso de creación de árboles, escriben en el mapa de densidad de la vegetación en un cierto radio a su alrededor. La alta densidad de vegetación reduce la transferencia de masa entre las partículas descendentes y la topografía. Esto es para simular cómo las raíces mantienen el suelo en su lugar.

//... descend function
double effD = depositionRate*max(0.0, 1.0-treedensity[ind]);
//...

Los árboles mueren si están en la piscina, o la corriente debajo de ellos es demasiado poderosa. Además, tienen una probabilidad aleatoria de muerte.


Gracias al sombreado y los mapas normales, incluso los sprites de árboles muy simples hacen que el relieve sea más hermoso.

Nota: el modelo de árbol se puede encontrar en el archivo "vegetacion.h" y en la función "Mundo :: crecer ()".

Otros detalles


Los resultados se visualizan utilizando un contenedor OpenGL casero, que se presenta [ aquí ].

resultados


Las partículas se pueden crear en el mapa de acuerdo con cualquier distribución que necesite. En mis demos, los creé con una distribución uniforme en tarjetas de 256 × 256.

Como se puede ver a continuación, la simulación resultante depende en gran medida de la elección del relieve original. El sistema puede simular una gran cantidad de fenómenos naturales. No pude conseguir adecuadamente solo cañones. Es posible que necesiten una simulación muy larga y lenta.

Se pueden observar otros fenómenos en el sistema, como cascadas, tortuosidad y deltas de ríos, lagos, hinchazón del suelo, etc.

El sistema también es muy bueno para distribuir flujos y piscinas en lugares donde se acumula mucha lluvia, y no en lugares aleatorios. Por lo tanto, la hidrología generada está estrechamente relacionada con el terreno.


Simulación hidrológica en tiempo real en una cuadrícula de 256 × 256. El relieve original es relativamente suave, lo que permite que las corrientes aparezcan rápidamente. Al principio, se puede observar la creación más simple de piscinas y fugas, después de lo cual aparecen y permanecen grandes flujos.

Comparación de los efectos del estrechamiento de los flujos.


Para comparar la diferencia creada al vincular un mapa a un sistema de erosión, puede simular la hidrología en el mismo mapa, incluyendo e inhabilitando varios efectos.

Simulé el mismo terreno tres veces:

  • Erosión basada en partículas (erosión básica) que recibe mapas de flujo y piscinas. Las piscinas aún afectan la generación
  • Erosión básica con parámetros modificados por mapas hidrológicos (en combinación con erosión)
  • Erosión combinada con parámetros modificados por mapas hidrológicos y que afectan la erosión de los árboles.

Nota: este es un sistema bastante caótico, y el tipo de hidrología que aparece depende en gran medida del terreno. Es difícil encontrar un ejemplo "muy revelador" de alivio.


Representación en relieve del sistema base


Render combinado de elevación del sistema


Representación de la topografía de un sistema con árboles

Una observación interesante: la combinación de mapas hidrológicos con parámetros de partículas en realidad estrecha los cauces de los ríos. Particularmente en regiones planas, las partículas están menos distribuidas y se fusionan en una pequeña cantidad de flujos más fuertes.

La fricción y la evaporación reducidas hacen frente con éxito al hecho de que las partículas comienzan a preferir canales ya existentes.

Otros efectos son más visibles con la observación directa del relieve.


Mapa hidrológico del sistema base.


Mapa hidrológico del sistema combinado.


Mapa hidrológico del sistema con árboles

Nota: estos resultados se generaron en exactamente 60 segundos del tiempo de simulación.

Los árboles también afectan el estrechamiento. Mejoran el proceso de cortar caminos despejados en áreas empinadas. Forzan a las corrientes a permanecer en los canales ya establecidos y, por lo tanto, reducen la tortuosidad. Por lo tanto, la ubicación de los árboles afecta el movimiento del agua.


Un ejemplo de cómo la ubicación de los árboles puede ayudar a mantener la ubicación de los arroyos. Este es el mismo alivio que antes, con todos los efectos activados.

Impacto de piscina


El sistema para crear piscinas funciona bien y permite que existan varios cuerpos de agua con diferentes alturas en el mismo relieve. También pueden derramarse entre sí y vaciarse.


Un ejemplo de un video de la formación de piscinas en un relieve basal más áspero con una gran diferencia de elevación. El lago superior está ubicado físicamente sobre el inferior y descarga el agua resultante directamente en el lago inferior.

Nota: obtuve varias semillas, en las que tres lagos fluyeron entre sí secuencialmente, pero no quería perder mucho tiempo buscando la correcta para este artículo. Ya generé demasiadas fotos y videos.

De vez en cuando, el nivel de altura de la piscina salta. Creo que esto sucede cuando el nivel del agua está cerca del nivel de fuga y se agrega mucha agua. Este efecto puede reducirse reduciendo la tasa de fuga en la función de inundación.


Después de otro minuto de generación, aparecieron varias piscinas nuevas por su cuenta.


En un ángulo más pronunciado, la diferencia en las alturas de la cuenca es más notable.


El mapa hidrológico muestra claramente que la cuenca central se fusiona con la cuenca inferior.

La ejecución del algoritmo de inundación lleva al hecho de que las piscinas ven un "muro" en el borde del mapa. Esto se nota en las imágenes que se muestran arriba.

Otra posible mejora podría ser la adición del nivel del mar al mundo para que las piscinas observen fugas en los bordes del mapa al nivel del mar, de lo contrario, simplemente se desbordarán.

Velocidad de simulación


El tiempo de cada paso de descenso e inundación varía de una partícula a otra, pero permanece aproximadamente un orden de magnitud (aproximadamente 1 microsegundo). Con flujos constantes, las partículas se mueven alrededor del mapa más rápido.

El tiempo de inundación varía en proporción al tamaño de la piscina, porque la operación de vertido es el paso más costoso. Cuanto más grandes son las piscinas, más grande es el área para la que necesita elevar el nivel del agua. Las grandes piscinas son definitivamente el cuello de botella del sistema. Si tiene ideas para aumentar la velocidad, avíseme.

El tiempo de descenso varía en proporción al tamaño del mapa y muchos otros parámetros, incluyendo la velocidad de fricción y evaporación.

Todos los videos de este artículo se graban en tiempo real, es decir, en general, la simulación es rápida.

Nota: poco después de la publicación de este artículo, realicé un pequeño cambio en el método de cálculo de las normales de superficie, lo que aumentó la velocidad de simulación. El efecto de esto es muy notable durante la simulación, pero es difícil compararlo debido a las grandes variaciones en el tiempo que lleva lanzarse e inundar. Según mis estimaciones, la velocidad de simulación se ha duplicado.

Más hermosos videos



Simulación hidrológica en terreno vertical irregular.


Un alivio un poco más suave. Algunos lagos resultan un poco fluctuantes.

Simulación hidrológica en un terreno plano y liso.

Se grabaron videos posteriores después de la mejora de la velocidad.


Terreno incluso más plano y liso.


Video con un río formado a partir de varias corrientes conectadas. El río principal tiene dos puntos en los que incorpora flujos más pequeños, y vemos cómo crece en tamaño.


Un relieve más desigual con la formación de ríos.

Puedo seguir por siempre.

Conclusión y trabajo para el futuro.


Esta técnica simple todavía está basada en partículas, pero logra transmitir muchos efectos adicionales. Proporciona una manera fácil de simular el movimiento del agua a gran escala sin simular completamente la dinámica de los fluidos. Además, ella trabaja con un modelo de agua intuitivo.

En las simulaciones, podemos observar la aparición de ríos sinuosos, cascadas, lagos y transfusiones de lagos, ríos que se dividen en áreas planas en deltas, etc.

Sería interesante agregar diferentes tipos de suelo en forma de un mapa de suelo desde el cual se podrían tomar los parámetros de erosión.

También puede cambiar fácilmente el sistema de árboles para crear diferentes tipos de árboles que mantengan el suelo en su lugar.

Es difícil transmitir la variedad de resultados generados en varias imágenes y videos, por lo que debe intentar hacerlo usted mismo, resulta muy interesante. Recomiendo experimentar con los parámetros del mapa original, incluido el ruido de octava y la escala del mapa.

Si tiene preguntas o comentarios, puede contactarme. Trabajé bastante duro en este proyecto, por lo que mis cerebros se estaban sinterizando y estoy seguro de que me perdí algunos aspectos interesantes.

All Articles