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.
El siguiente paso sería generar una máscara, para garantizar que las esquinas y los bordes sean solo agua.
Entonces puedo restar la máscara de la imagen de ruido perlin para obtener una isla.
y jugando con el contraste ...
y la curva de gradiente, puedo obtener un mapa de altura de la isla tal como lo quiero ...
(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;
}
}
Respuestas:
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.
Con ese factor, puede usar algo como lo siguiente al generar el ruido de máscara:
Donde
distanceToNearestEdge
devuelve la distancia al borde más cercano del mapa desde esa posición. YisSolid
decide 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
solidCutOffValue
está el valor que está utilizando para decidir entre sólido o no? Podría ser.5
incluso para dividir, o.75
para más sólido o.25
para 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))
Donde
0
está en el borde exterior y1
está 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 obtenemosnoiseAt(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:
fuente
isSolid
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 .
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.
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):
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).
fuente
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:
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.
fuente
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:
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).
fuente