Estou procurando uma maneira fácil e agradável de gerar uma máscara para um mapa de ilha com C #.
Basicamente, estou usando um mapa de altura aleatório gerado com ruído permanente, onde o terreno NÃO é cercado por água.
O próximo passo seria gerar uma máscara, para garantir que os cantos e bordas sejam apenas água.
Depois, posso subtrair a máscara da imagem de ruído permanente para obter uma ilha.
e brincando com o contraste ..
e a curva de gradiente, posso obter um mapa de altura da ilha exatamente como eu quero.
(estes são apenas exemplos, é claro)
como você pode ver, as "bordas" da ilha são cortadas, o que não é um grande problema se o valor da cor não for muito branco, porque vou dividir a escala de cinza em 4 camadas (água, areia, grama e Rocha).
Minha pergunta é: como posso gerar uma máscara bonita como na segunda imagem?
ATUALIZAR
Eu encontrei essa técnica, parece ser um bom ponto de partida para mim, mas não tenho certeza de como exatamente posso implementá-la para obter a saída desejada. http://mrl.nyu.edu/~perlin/experiments/puff/
ATUALIZAÇÃO 2
Esta é a minha solução final.
Eu implementei a makeMask()
função dentro do meu loop de normalização assim:
//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 ] );
}
}
e esta é a função 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;
}
isso dará uma saída como na imagem # 3.
com um pouco de alteração no código, você pode obter a saída originalmente desejada, como na imagem # 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;
}
}
Respostas:
Gere ruído regular com um viés para valores mais altos em direção ao centro. Se você está querendo formas quadradas de ilha como você mostra no seu exemplo, eu usaria a distância até a borda mais próxima como seu fator.
Com esse fator, você pode usar algo como o seguinte ao gerar o ruído da máscara:
Onde
distanceToNearestEdge
retorna a distância até a borda mais próxima do mapa a partir dessa posição. EisSolid
decide se um valor entre 0 e 1 é sólido (independentemente do seu corte). É uma função muito simples, pode ser assim:valor de retorno isSolid (valor flutuante) <solidCutOffValue
Onde
solidCutOffValue
está o valor que você está usando para decidir entre sólido ou não. Pode ser.5
para divisão uniforme ou.75
para mais sólido ou.25
menos sólido.Finalmente, este pouco
(1 - (d/maxDVal)) * noiseAt(x,y)
. Primeiro, obtemos um fator entre 0 e 1 com isso:(1 - (d/maxDVal))
Onde
0
está na borda externa e1
na borda interna. Isso significa que nosso ruído tem maior probabilidade de ser sólido por dentro e não sólido por fora. Esse é o fator que aplicamos ao barulho que recebemosnoiseAt(x,y)
.Aqui está uma representação mais visual de quais são os valores, pois os nomes podem ser enganosos para os valores reais:
fonte
isSolid
Se você estiver disposto a poupar algum poder computacional para isso, poderá usar uma técnica semelhante à que o autor deste blog fez. ( Nota: se você deseja copiar diretamente o código dele, está no ActionScript). Basicamente, ele gera pontos quase aleatórios (ou seja, parece relativamente uniforme) e depois os usa para criar polígonos de Voronoi .
Em seguida, ele define os polígonos externos para regar e itera pelo restante dos polígonos, tornando-os água se uma certa porcentagem dos polígonos adjacentes for água . Você fica com uma máscara de polígono representando aproximadamente uma ilha.
Com isso, você pode aplicar ruído às bordas, resultando em algo parecido com isso (as cores são de outra etapa não relacionada):
Você fica com uma máscara em forma de ilha (bastante) de aparência realista, que serviria a seus propósitos. Você pode optar por usá-lo como uma máscara para o seu ruído Perlin, ou então gerar valores de altura com base na distância do mar e adicionar ruído (embora isso pareça desnecessário).
fonte
Um método muito simples é criar gradiente radial ou esférico inverso com centro na largura / 2 e altura / 2. Para mascarar, você deseja subtrair o gradiente do ruído em vez de multiplicá-lo. Isso proporciona margens mais realistas, com a desvantagem de que as ilhas não estão necessariamente conectadas.
Você pode ver a diferença entre subtrair e multiplicar o ruído com o gradiente aqui: http://www.vfxpedia.com/index.php?title=Tips_and_Techniques/Natural_Phenomena/Smoke
Se você não tiver certeza de como criar gradiente radial, poderá usá-lo como ponto de partida:
Não se esqueça de dimensionar seu gradiente para a mesma altura que o seu mapa de altura e você ainda precisará fatorar sua linha de água de alguma forma.
O problema com esse método é que seu campo de altura será centralizado no centro do mapa. Este método, no entanto, deve começar com a adição de recursos e tornar a sua paisagem mais diversificada como você pode usar disso para adicionar funcionalidades ao seu mapa de altura.
fonte
Segundo a sugestão de ollipekka: o que você quer fazer é subtrair uma função de viés adequada do seu mapa de altura, para que as arestas sejam garantidas debaixo d'água.
Existem muitas funções de viés adequadas, mas uma bastante simples é:
onde x e y são os valores das coordenadas, redimensionados para ficar entre 0 e 1. Essa função pega o valor 0 no centro do mapa (em x = y = 0,5) e tende ao infinito nas bordas. Assim, subtraí-lo (dimensionado por um fator constante adequado) do seu mapa de altura garante que os valores de altura também tenderão a menos infinito próximo às bordas do mapa. Basta escolher qualquer altura arbitrária desejada e chamar o nível do mar.
Como observa ollipekka, essa abordagem não garante que a ilha seja contígua. No entanto, escalar a função de viés por um fator de escala razoavelmente pequeno deve torná-lo praticamente plano na área central do mapa (não afetando muito o terreno), com um viés significativo aparecendo apenas perto das bordas. Portanto, fazer isso deve fornecer uma ilha quadrada, quase sempre contígua, com, no máximo, algumas sub-ilhas minúsculas próximas às bordas.
Obviamente, se você não se importa com a possibilidade de terrenos desconectados, um fator de escala um pouco maior deve fornecer mais água e uma forma de ilha com aparência mais natural. O ajuste do nível do mar e / ou da escala do seu mapa de altura original também pode ser usado para variar o tamanho e a forma da (s) ilha (s) resultante (s).
fonte