Un método simple para crear una máscara de mapa de isla

21


Estoy buscando una manera agradable y fácil de generar una máscara para un mapa de isla con C #.


Básicamente estoy usando un mapa de altura aleatorio generado con ruido perlin, donde el terreno NO está rodeado de agua.

ingrese la descripción de la imagen aquí

El siguiente paso sería generar una máscara, para garantizar que las esquinas y los bordes sean solo agua.

ingrese la descripción de la imagen aquí

Entonces puedo restar la máscara de la imagen de ruido perlin para obtener una isla.

ingrese la descripción de la imagen aquí

y jugando con el contraste ...

ingrese la descripción de la imagen aquí

y la curva de gradiente, puedo obtener un mapa de altura de la isla tal como lo quiero ...

ingrese la descripción de la imagen aquí

(estos son solo ejemplos, por supuesto)

como puede ver, los "bordes" de la isla simplemente están cortados, lo que no es un gran problema si el valor del color no es demasiado blanco, porque dividiré la escala de grises en 4 capas (agua, arena, hierba y rock).

Mi pregunta es, ¿cómo puedo generar una máscara atractiva como en la segunda imagen?


ACTUALIZAR

He encontrado esta técnica, parece ser un buen punto de partida para mí, pero no estoy seguro de qué tan bien puedo implementarla para obtener el resultado deseado. http://mrl.nyu.edu/~perlin/experiments/puff/


ACTUALIZACIÓN 2

Esta es mi solución final.

He implementado la makeMask()función dentro de mi ciclo de normalización de esta manera:

        //normalisation
        for( int i = 0; i < width; i++ ) {
            for( int j = 0; j < height; j++ ) {
                perlinNoise[ i ][ j ] /= totalAmplitude;
                perlinNoise[ i ][ j ] = makeMask( width, height, i, j, perlinNoise[ i ][ j ] );
            }
        }

y esta es la función final:

    public static float makeMask( int width, int height, int posX, int posY, float oldValue ) {
        int minVal = ( ( ( height + width ) / 2 ) / 100 * 2 );
        int maxVal = ( ( ( height + width ) / 2 ) / 100 * 10 );
        if( getDistanceToEdge( posX, posY, width, height ) <= minVal ) {
            return 0;
        } else if( getDistanceToEdge( posX, posY, width, height ) >= maxVal ) {
            return oldValue;
        } else {
            float factor = getFactor( getDistanceToEdge( posX, posY, width, height ), minVal, maxVal );
            return oldValue * factor;
        }
    }

    private static float getFactor( int val, int min, int max ) {
        int full = max - min;
        int part = val - min;
        float factor = (float)part / (float)full;
        return factor;
    }

    public static int getDistanceToEdge( int x, int y, int width, int height ) {
        int[] distances = new int[]{ y, x, ( width - x ), ( height - y ) };
        int min = distances[ 0 ];
        foreach( var val in distances ) {
            if( val < min ) {
                min = val;
            }
        }
        return min;
    }

esto dará una salida como en la imagen # 3.

con un poco de cambio en el código, puede obtener el resultado deseado originalmente como en la imagen # 2 ->

    public static float makeMask( int width, int height, int posX, int posY, float oldValue ) {
        int minVal = ( ( ( height + width ) / 2 ) / 100 * 2 );
        int maxVal = ( ( ( height + width ) / 2 ) / 100 * 20 );
        if( getDistanceToEdge( posX, posY, width, height ) <= minVal ) {
            return 0;
        } else if( getDistanceToEdge( posX, posY, width, height ) >= maxVal ) {
            return 1;
        } else {
            float factor = getFactor( getDistanceToEdge( posX, posY, width, height ), minVal, maxVal );
            return ( oldValue + oldValue ) * factor;
        }
    }
As
fuente
¿Hay alguna posibilidad de que pueda vincular a su fuente completa?
estoico

Respuestas:

12

Genere ruido regular con un sesgo para valores más altos hacia el centro. Si desea formas de isla cuadradas como muestra en su ejemplo, usaría la distancia al borde más cercano como su factor.

ingrese la descripción de la imagen aquí

Con ese factor, puede usar algo como lo siguiente al generar el ruido de máscara:

maxDVal = (minIslandSolid - maxIslandSolid)

getMaskValueAt(x, y)
    d = distanceToNearestEdge(x,y)
    if(d < maxIslandSolid)
        return isSolid(NonSolidValue) //always non-solid for outside edges
    else if(d < minIslandSolid)
        //noisy edges
        return isSolid((1 - (d/maxDVal)) * noiseAt(x,y))
    else
        return isSolid(SolidValue) //always return solid for center of island

Donde distanceToNearestEdgedevuelve la distancia al borde más cercano del mapa desde esa posición. Y isSoliddecide si un valor entre 0 y 1 es sólido (cualquiera que sea su corte). Es una función muy simple, podría verse así:

valor de retorno isSolid (valor flotante) <solidCutOffValue

¿Dónde solidCutOffValueestá el valor que está utilizando para decidir entre sólido o no? Podría ser .5incluso para dividir, o .75para más sólido o .25para menos sólido.

Finalmente, este poquito (1 - (d/maxDVal)) * noiseAt(x,y). Primero, obtenemos un factor entre 0 y 1 con esto:

(1 - (d/maxDVal))

ingrese la descripción de la imagen aquí

Donde 0está en el borde exterior y 1está en el borde interior. Esto significa que es más probable que nuestro ruido sea sólido en el interior y no sólido en el exterior. Este es el factor que aplicamos al ruido que obtenemos noiseAt(x,y).

Aquí hay una representación más visual de cuáles son los valores, ya que los nombres pueden ser engañosos con los valores reales:

ingrese la descripción de la imagen aquí

MichaelHouse
fuente
Gracias por esa respuesta rápida, intentaré implementar esta técnica. Espero obtener el resultado deseado con esto.
As
Es probable que se necesiten algunos ajustes para obtener los bordes ruidosos de la forma en que los desea. Pero esto debería ser una base sólida para usted. ¡Buena suerte!
MichaelHouse
hasta ahora tengo la implementación base para trabajar, ¿pueden darme un ejemplo de la función IsSolid? No sé cómo puedo obtener un valor entre 0 y 1 en función de la distancia mínima y máxima desde el borde. vea mi actualización para mi código hasta ahora.
As
Tenía algo de lógica confusa allí. Lo arreglé para que tenga más sentido. Y proporcionó un ejemplo deisSolid
MichaelHouse
Para obtener un valor entre 0 y 1, solo descubra cuál puede ser el valor máximo y divida su valor actual entre eso. Luego resté eso de uno, porque quería que cero estuviera en el borde exterior y 1 en el borde interior.
MichaelHouse
14

Si está dispuesto a ahorrar algo de poder computacional para esto, entonces podría usar una técnica similar a la que hizo el autor de este blog . ( Nota: si desea copiar directamente su código, está en ActionScript). Básicamente, genera puntos cuasialeatorios (es decir, se ve relativamente uniforme) y luego los usa para crear polígonos Voronoi .

Polígonos Voronoi

Luego establece los polígonos exteriores en agua e itera a través del resto de los polígonos, haciéndolos agua si un cierto porcentaje de los polígonos adyacentes son agua . Luego te queda una máscara de polígono que representa aproximadamente una isla.

Mapa poligonal

A partir de esto, puede aplicar ruido a los bordes, lo que resulta en algo parecido a esto (los colores son de otro paso no relacionado):

Mapa poligonal con bordes ruidosos

Luego te queda una máscara con forma de isla (bastante) realista, que serviría para tus propósitos. Puede elegir usarlo como una máscara para su ruido Perlin, o puede generar valores de altura basados ​​en la distancia al mar y agregar ruido (aunque eso parece innecesario).

Polar
fuente
Gracias por su respuesta, pero esta fue la primera solución (casi única) que obtuve al buscar en la web. Esta solución parece ser muy agradable, pero me gustaría probar la forma "simple".
As
@Ace lo suficientemente justo, probablemente sea un poco excesivo para lo que sea que vayas a hacer: P Aún así, vale la pena tenerlo en cuenta si alguna vez lo necesitas.
Polar
Me alegra que alguien haya vinculado a esto, esa página siempre está en mi lista de "publicaciones realmente fantásticas sobre cómo alguien implementó algo".
Tim Holt
+1. Esto es genial. ¡Gracias por esto, definitivamente será útil para mí!
Andre
1

Un método muy simple es crear un gradiente radial o esférico inverso con el centro en el ancho / 2 y la altura / 2. Para enmascarar, desea restar el gradiente del ruido en lugar de multiplicarlo. Esto le brinda costas de aspecto más realista con el inconveniente de que las islas no están necesariamente conectadas.

Puede ver la diferencia entre restar y multiplicar el ruido con el gradiente aquí: http://www.vfxpedia.com/index.php?title=Tips_and_Techniques/Natural_Phenomena/Smoke

Si no está seguro de cómo crear un degradado radial, puede usar esto como punto de partida:

    public static float[] CreateInverseRadialGradient(int size, float heightScale = 1)
    {
        float radius = size / 2;

        float[] heightMap = new float[size * size];

        for (int iy = 0; iy < size; iy++)
        {
            int stride = iy * size;
            for (int ix = 0; ix < size; ix++)
            {
                float centerToX = ix - radius;
                float centerToY = iy - radius;

                float distanceToCenter = (float)Math.Sqrt(centerToX * centerToX + centerToY * centerToY);
                heightMap[iy * size + ix] = distanceToCenter / radius * heightScale;
            }
        }

        return heightMap;
    }

No olvide escalar su gradiente a las mismas alturas que su mapa de altura y aún necesita tener en cuenta su línea de flotación de alguna manera.

El problema con este método es que su campo de altura estará centrado alrededor del centro del mapa. Sin embargo, este método debería ayudarlo a agregar características y hacer que su paisaje sea más diverso, ya que puede usar la adición para agregar características a su mapa de altura.

ollipekka
fuente
Gracias por la respuesta. No estoy seguro si lo entiendo bien, pero ¿notó que la máscara no afecta a los datos del mapa de altura original en absoluto, solo se ve afectada en negativo, por lo que solo define los píxeles que se muestran (en%) o no . pero también lo probé con graduantes simples, y no estaba contento con el resultado.
As
1

Secundo la sugerencia de ollipekka: lo que quieres hacer es restar una función de polarización adecuada de tu mapa de altura, de modo que los bordes estén garantizados bajo el agua.

Hay muchas funciones de sesgo adecuadas, pero una bastante simple es:

f(x, y) = 1 / (x * (1-x) * y * (1-y)) - 16

donde x e y son los valores de coordenadas, escalados para estar entre 0 y 1. Esta función toma el valor 0 en el centro del mapa (en x = y = 0.5) y tiende al infinito en los bordes. Por lo tanto, restarlo (escalado por un factor constante adecuado) de su mapa de altura asegura que los valores de altura también tenderán a menos infinito cerca de los bordes del mapa. Simplemente elija la altura arbitraria que desee y llámela nivel del mar.

Como señala Ollipekka, este enfoque no garantizará que la isla sea contigua. Sin embargo, escalar la función de sesgo por un factor de escala bastante pequeño debería hacer que sea principalmente plana en el área central del mapa (por lo tanto, no afectará mucho su terreno), con un sesgo significativo que aparece solo cerca de los bordes. Por lo tanto, hacerlo debería darle una isla cuadrada, en su mayoría contigua, con, a lo sumo, algunas pequeñas subinsulares cerca de los bordes.

Por supuesto, si no le importa la posibilidad de un terreno desconectado, un factor de escala algo mayor debería darle más agua y una forma de isla más natural. El ajuste del nivel del mar y / o la escala de su mapa de altura original también se puede utilizar para variar el tamaño y la forma de la (s) isla (s) resultante (s).

Ilmari Karonen
fuente