Une méthode simple pour créer un masque de carte de l'île

21


Je cherche un moyen agréable et facile de générer un masque pour une carte d'île avec C #.


Fondamentalement, j'utilise avec une carte d'altitude aléatoire générée avec du bruit perlin, où le terrain n'est PAS entouré d'eau.

entrez la description de l'image ici

La prochaine étape serait de générer un masque, pour s'assurer que les coins et les bordures ne sont que de l'eau.

entrez la description de l'image ici

Ensuite, je peux simplement soustraire le masque de l'image de bruit perlin pour obtenir une île.

entrez la description de l'image ici

et jouer avec le contraste ..

entrez la description de l'image ici

et la courbe de gradient, je peux obtenir une carte de hauteur d'île comme je le veux ..

entrez la description de l'image ici

(ce ne sont bien sûr que des exemples)

comme vous pouvez le voir, les "bords" de l'île sont juste coupés, ce qui n'est pas un gros problème si la valeur de la couleur n'est pas trop blanche, car je vais juste diviser l'échelle de gris en 4 couches (eau, sable, herbe et Roche).

Ma question est, comment puis-je générer un beau masque comme dans la deuxième image?


MISE À JOUR

J'ai trouvé cette technique, elle semble être un bon point de départ pour moi, mais je ne sais pas exactement comment je peux l'implémenter pour obtenir la sortie souhaitée. http://mrl.nyu.edu/~perlin/experiments/puff/


MISE À JOUR 2

c'est ma dernière solution.

J'ai implémenté la makeMask()fonction dans ma boucle de normalisation comme ceci:

        //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 ] );
            }
        }

et c'est la fonction finale:

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

cela donnera une sortie comme dans l'image # 3.

avec un peu de changement dans le code, vous pouvez obtenir la sortie initialement souhaitée comme dans l'image # 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;
        }
    }
Ace
la source
avez-vous une chance de créer un lien vers votre source complète?
stoïque

Réponses:

12

Génère un bruit régulier avec un biais pour des valeurs plus élevées vers le centre. Si vous voulez des formes d'îles carrées comme vous le montrez dans votre exemple, j'utiliserais la distance jusqu'au bord le plus proche comme facteur.

entrez la description de l'image ici

Avec ce facteur, vous pouvez utiliser quelque chose comme ce qui suit lors de la génération du bruit de masque:

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

distanceToNearestEdgerenvoie la distance au bord le plus proche de la carte à partir de cette position. Et isSoliddécide si une valeur entre 0 et 1 est solide (quel que soit votre seuil). C'est une fonction très simple, elle pourrait ressembler à ceci:

isSolid (valeur flottante) valeur de retour <solidCutOffValue

Quelle solidCutOffValueest la valeur que vous utilisez pour choisir entre solide ou non. Il peut s'agir .5de fentes égales, .75de plus solides ou .25de moins solides.

Enfin, ce petit bout (1 - (d/maxDVal)) * noiseAt(x,y). Tout d'abord, nous obtenons un facteur entre 0 et 1 avec ceci:

(1 - (d/maxDVal))

entrez la description de l'image ici

0est sur le bord extérieur et 1sur le bord intérieur. Cela signifie que notre bruit est plus susceptible d'être solide à l'intérieur et non solide à l'extérieur. C'est le facteur que nous appliquons au bruit que nous obtenons noiseAt(x,y).

Voici une représentation plus visuelle de ce que sont les valeurs, car les noms peuvent être trompeurs par rapport aux valeurs réelles:

entrez la description de l'image ici

MichaelHouse
la source
merci pour cette réponse rapide, je vais essayer de mettre en œuvre cette technique. espérons obtenir la sortie souhaitée avec cela.
Ace
Il faudra probablement quelques ajustements pour obtenir les bords bruyants comme vous le souhaitez. Mais cela devrait être une base solide pour vous. Bonne chance!
MichaelHouse
Jusqu'à présent, j'ai mis en œuvre l'implémentation de base, pouvez-vous simplement me donner un exemple de la fonction IsSolid? je ne sais pas comment obtenir une valeur entre 0 et 1 en fonction de la distance min et max du bord. voir ma mise à jour pour mon code jusqu'à présent.
Ace
J'avais une logique mélangée là-dedans. Je l'ai corrigé pour avoir plus de sens. Et fourni un exemple deisSolid
MichaelHouse
Pour obtenir une valeur entre 0 et 1, il vous suffit de découvrir quelle peut être la valeur maximale et de diviser votre valeur actuelle par cela. Ensuite, j'ai soustrait cela d'un, car je voulais que zéro soit sur le bord extérieur et 1 sur le bord intérieur.
MichaelHouse
14

Si vous êtes prêt à épargner un peu de puissance de calcul pour cela, alors vous pouvez utiliser une technique similaire à ce que l'auteur de ce blog a fait. ( NB: si vous souhaitez copier directement son code, c'est en ActionScript). Fondamentalement, il génère des points quasi-aléatoires (c'est-à-dire semble relativement uniforme) et les utilise ensuite pour créer des polygones de Voronoi .

Voronoi Polygons

Il définit ensuite les polygones extérieurs sur l'eau et parcourt le reste des polygones, les faisant arroser si un certain pourcentage des polygones adjacents sont de l'eau . Vous vous retrouvez alors avec un masque polygonal représentant grossièrement une île.

Carte polygone

À partir de cela, vous pouvez appliquer du bruit aux bords, ce qui donne quelque chose qui ressemble à cela (les couleurs proviennent d'une autre étape non liée):

Carte polygonale avec bords bruyants

Vous vous retrouvez alors avec un masque en forme d'île (assez) réaliste, qui répondrait à vos besoins. Vous pouvez choisir de l'utiliser comme masque pour votre bruit Perlin, ou vous pouvez alors générer des valeurs de hauteur basées sur la distance à la mer et ajouter du bruit (bien que cela semble inutile).

Polaire
la source
Merci pour votre réponse, mais c'est la première (à peu près la seule) solution que j'ai obtenue en cherchant sur le Web. cette solution semble être très agréable, mais je voudrais essayer la méthode "simple".
Ace
@ As Assez juste, c'est probablement un peu exagéré pour tout ce que vous allez faire: P Pourtant, cela vaut la peine de garder à l'esprit si vous en avez besoin.
Polar
Heureux que quelqu'un soit lié à cela - cette page est toujours sur ma liste de "messages vraiment fantastiques sur la façon dont quelqu'un a mis en œuvre quelque chose".
Tim Holt
+1. C'est trop cool. Merci pour cela, ça va certainement être utile pour moi!
Andre
1

Une méthode très simple consiste à créer un gradient radial ou sphérique inverse avec un centre à la largeur / 2 et la hauteur / 2. Pour le masquage, vous voulez soustraire le dégradé du bruit plutôt que de le multiplier. Cela vous donne des rives plus réalistes avec l'inconvénient que les îles ne sont pas nécessairement connectées.

Vous pouvez voir la différence entre la soustraction et la multiplication du bruit avec le gradient ici: http://www.vfxpedia.com/index.php?title=Tips_and_Techniques/Natural_Phenomena/Smoke

Si vous ne savez pas comment créer un dégradé radial, vous pouvez l'utiliser comme point de départ:

    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'oubliez pas de mettre à l'échelle votre gradient à la même hauteur que votre carte de hauteur et vous devez toujours prendre en compte votre ligne de flottaison d'une manière ou d'une autre.

Le problème avec cette méthode est que votre champ de hauteur sera centré autour du centre de la carte. Cette méthode, cependant, devrait vous aider à ajouter des entités et à rendre votre paysage plus diversifié, car vous pouvez utiliser l' ajout pour ajouter des entités à votre carte de hauteur.

ollipekka
la source
merci pour la réponse. Je ne sais pas si je vous comprends bien, mais avez-vous remarqué que le masque n'affecte pas du tout les données d'origine de la carte de hauteur, il est juste affecté par le négatif, donc il définit simplement les pixels qui sont affichés (en%) ou non . mais aussi je l'ai essayé avec de simples gradients, et je n'étais pas satisfait du résultat.
Ace
1

J'appuie la suggestion d'ollipekka: ce que vous voulez faire, c'est soustraire une fonction de polarisation appropriée de votre carte de hauteur, de sorte que les bords soient garantis sous l'eau.

Il existe de nombreuses fonctions de polarisation appropriées, mais une fonction assez simple est la suivante:

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

x et y sont les valeurs de coordonnées, mises à l'échelle pour se situer entre 0 et 1. Cette fonction prend la valeur 0 au centre de la carte (à x = y = 0,5) et tend vers l'infini sur les bords. Ainsi, sa soustraction (mise à l'échelle par un facteur constant approprié) de votre carte de hauteur garantit que les valeurs de hauteur auront également tendance à moins l'infini près des bords de la carte. Choisissez simplement la hauteur arbitraire que vous voulez et appelez-la le niveau de la mer.

Comme le note ollipekka, cette approche ne garantira pas que l'île sera contiguë. Cependant, la mise à l'échelle de la fonction de biais par un facteur d'échelle assez petit devrait la rendre principalement plate dans la zone centrale de la carte (n'affectant donc pas beaucoup votre terrain), avec un biais significatif n'apparaissant que près des bords. Ainsi, cela devrait vous donner une île carrée, principalement contiguë avec, au maximum, quelques minuscules sous-îles près des bords.

Bien sûr, si la possibilité d'un terrain déconnecté ne vous dérange pas, un facteur d'échelle un peu plus grand devrait vous donner plus d'eau et une forme d'île plus naturelle. Le réglage du niveau de la mer et / ou de l'échelle de votre carte de hauteur d'origine peut également être utilisé pour faire varier la taille et la forme des îles résultantes.

Ilmari Karonen
la source