Um método simples para criar máscara de mapa de ilha

21


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.

insira a descrição da imagem aqui

O próximo passo seria gerar uma máscara, para garantir que os cantos e bordas sejam apenas água.

insira a descrição da imagem aqui

Depois, posso subtrair a máscara da imagem de ruído permanente para obter uma ilha.

insira a descrição da imagem aqui

e brincando com o contraste ..

insira a descrição da imagem aqui

e a curva de gradiente, posso obter um mapa de altura da ilha exatamente como eu quero.

insira a descrição da imagem aqui

(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;
        }
    }
Ás
fonte
Alguma chance de você criar um link para o seu código fonte completo?
estóico 17/05

Respostas:

12

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.

insira a descrição da imagem aqui

Com esse fator, você pode usar algo como o seguinte ao gerar o ruído da 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

Onde distanceToNearestEdgeretorna a distância até a borda mais próxima do mapa a partir dessa posição. E isSoliddecide 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 solidCutOffValueestá o valor que você está usando para decidir entre sólido ou não. Pode ser .5para divisão uniforme ou .75para mais sólido ou .25menos sólido.

Finalmente, este pouco (1 - (d/maxDVal)) * noiseAt(x,y). Primeiro, obtemos um fator entre 0 e 1 com isso:

(1 - (d/maxDVal))

insira a descrição da imagem aqui

Onde 0está na borda externa e 1na 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 recebemos noiseAt(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:

insira a descrição da imagem aqui

MichaelHouse
fonte
thx para essa resposta rápida, vou tentar implementar esta técnica. espero conseguir a saída desejada com isso.
Ace
Provavelmente, serão necessários alguns ajustes para obter as bordas barulhentas da maneira que você deseja. Mas isso deve ser uma base sólida para você. Boa sorte!
MichaelHouse
Até agora, consegui que a implementação base funcionasse. Você pode me dar um exemplo da função IsSolid? Eu não sei como posso obter um valor entre 0 e 1 com base na distância mínima e máxima da borda. veja minha atualização para o meu código até agora.
Ace
Eu tinha uma lógica confusa lá. Corrigi-o para fazer mais sentido. E forneceu um exemplo deisSolid
MichaelHouse
Para obter um valor entre 0 e 1, basta descobrir qual pode ser o valor máximo e dividir seu valor atual por isso. Subtraí isso de um, porque queria que zero estivesse na borda externa e 1 estivesse na borda interna.
MichaelHouse
14

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 .

Voronoi Polygons

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.

Mapa do polígono

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):

Mapa de polígono com bordas barulhentas

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).

Polar
fonte
thx pela sua resposta, mas essa é a primeira (praticamente única) solução que obtive ao pesquisar na web. esta solução parece ser muito boa, mas eu gostaria de tentar da maneira "simples".
Ace
@ Ace Fair, provavelmente é um pouco exagerado para o que você vai fazer: P Ainda assim, vale a pena ter em mente se você precisar.
Polar
Ainda bem que alguém vinculado a isso - essa página está sempre na minha lista de "postagens realmente fantásticas sobre como alguém implementou algo".
amigos estão dizendo sobre tim
+1. Isso é tão legal. Obrigado por isso, definitivamente será útil para mim!
22413 Andre
1

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:

    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;
    }

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.

ollipekka
fonte
thx pela resposta. não tenho certeza se entendi direito, mas você notou que a máscara não afeta os dados originais do mapa de altura, é apenas afetada pelo negativo, portanto, apenas define os pixels que são exibidos (em%) ou não . mas também tentei com gradiants simples e não fiquei feliz com o resultado.
Ás
1

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 é:

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

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).

Ilmari Karonen
fonte