Eine einfache Methode zum Erstellen einer Inselkartenmaske


21


Ich suche nach einer schönen und einfachen Möglichkeit, mit C # eine Maske für eine Inselkarte zu generieren.


Grundsätzlich benutze ich eine zufällige Höhenkarte, die mit Perlinrauschen erzeugt wurde, wobei das Gelände NICHT von Wasser umgeben ist.

Bildbeschreibung hier eingeben

Der nächste Schritt wäre, eine Maske zu generieren, um sicherzustellen, dass die Ecken und Ränder nur Wasser sind.

Bildbeschreibung hier eingeben

Dann kann ich einfach die Maske vom Perlin-Rauschbild subtrahieren, um eine Insel zu erhalten.

Bildbeschreibung hier eingeben

und mit dem Kontrast herumspielen ..

Bildbeschreibung hier eingeben

und der Gradientenkurve kann ich eine Inselhöhenkarte bekommen, wie ich es will ..

Bildbeschreibung hier eingeben

(Dies sind nur Beispiele natürlich)

Wie Sie sehen können, werden die "Ränder" der Insel einfach abgeschnitten, was kein großes Problem ist, wenn der Farbwert nicht zu weiß ist, da ich die Graustufen einfach in 4 Schichten (Wasser, Sand, Gras und Rock).

Meine Frage ist, wie kann ich eine gut aussehende Maske wie im zweiten Bild erzeugen?


AKTUALISIEREN

Ich habe diese Technik gefunden, sie scheint ein guter Ausgangspunkt für mich zu sein, aber ich bin mir nicht sicher, wie genau ich sie implementieren kann, um die gewünschte Ausgabe zu erzielen. http://mrl.nyu.edu/~perlin/experiments/puff/


UPDATE 2

Das ist meine endgültige Lösung.

Ich habe die makeMask()Funktion in meiner Normalisierungsschleife folgendermaßen implementiert :

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

und das ist die letzte Funktion:

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

Dies ergibt eine Ausgabe wie in Bild 3.

Mit ein wenig Änderung im Code können Sie die ursprünglich gewünschte Ausgabe wie in Bild 2 erhalten ->

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

Gibt es eine Möglichkeit, auf Ihre vollständige Quelle zu verlinken?
Stoic

Antworten:


12

Erzeugen Sie regelmäßiges Rauschen mit einer Neigung für höhere Werte zur Mitte. Wenn Sie quadratische Inselformen wie in Ihrem Beispiel wünschen, würde ich den Abstand zur nächsten Kante als Faktor verwenden.

Bildbeschreibung hier eingeben

Mit diesem Faktor können Sie beim Erzeugen des Maskenrauschens Folgendes verwenden:

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

Where distanceToNearestEdgegibt die Entfernung von dieser Position zum nächsten Kartenrand zurück. Und isSolidentscheidet, ob ein Wert zwischen 0 und 1 fest ist (unabhängig davon, wie hoch Ihr Cut-Off ist). Es ist eine sehr einfache Funktion, die so aussehen könnte:

isSolid (float value) Rückgabewert <solidCutOffValue

Wo solidCutOffValueist der Wert, mit dem Sie sich zwischen soliden und nicht soliden Werten entscheiden? Es könnte .5für sogar gespalten sein, oder .75für fester oder .25für weniger fest.

Zum Schluss dieses kleine bisschen (1 - (d/maxDVal)) * noiseAt(x,y). Zunächst erhalten wir einen Faktor zwischen 0 und 1:

(1 - (d/maxDVal))

Bildbeschreibung hier eingeben

Wo 0ist an der Außenkante und 1ist an der Innenkante. Dies bedeutet, dass unsere Geräusche im Inneren eher fest und im Äußeren weniger fest sind. Dies ist der Faktor, den wir auf das Geräusch anwenden, von dem wir kommen noiseAt(x,y).

Hier ist eine visuellere Darstellung der Werte, da die Namen möglicherweise zu den tatsächlichen Werten irreführend sind:

Bildbeschreibung hier eingeben


Danke für die schnelle Antwort, ich werde versuchen, diese Technik zu implementieren. hoffe, damit die gewünschte Ausgabe zu bekommen.
Ass

Es wird wahrscheinlich einige Anpassungen erfordern, um die lauten Ränder so zu gestalten, wie Sie es möchten. Dies sollte jedoch eine solide Grundlage für Sie sein. Viel Glück!
MichaelHouse

Bis jetzt hat die Basisimplementierung funktioniert. Können Sie mir nur ein Beispiel für die IsSolid-Funktion geben? Ich weiß nicht, wie ich einen Wert zwischen 0 und 1 basierend auf dem minimalen und maximalen Abstand von der Kante erhalten kann. siehe mein Update für meinen Code bis jetzt.
Ass

Ich hatte da eine durcheinandergebrachte Logik. Ich habe es repariert, um mehr Sinn zu machen. Und lieferte ein Beispiel fürisSolid
MichaelHouse

Um einen Wert zwischen 0 und 1 zu erhalten, müssen Sie nur den Maximalwert ermitteln und Ihren aktuellen Wert durch diesen Wert dividieren. Dann habe ich das von Eins abgezogen, weil ich wollte, dass Null an der Außenkante und 1 an der Innenkante ist.
MichaelHouse

14

Wenn Sie bereit sind, Rechenleistung dafür zu sparen, können Sie eine ähnliche Technik anwenden wie der Autor dieses Blogs . ( Hinweis: Wenn Sie seinen Code direkt kopieren möchten, ist er in ActionScript enthalten.) Grundsätzlich erzeugt er quasi zufällige Punkte (sieht also relativ gleichmäßig aus) und erstellt daraus Voronoi-Polygone .

Voronoi-Polygone

Er setzt dann die äußeren Polygone auf Wasser und iteriert durch die restlichen Polygone, wobei er sie wässern lässt, wenn ein bestimmter Prozentsatz der benachbarten Polygone Wasser ist . Dann bleibt eine Polygonmaske übrig, die ungefähr eine Insel darstellt.

Polygon Map

Von hier aus können Sie Rauschen auf die Kanten anwenden, was zu etwas ähnlichem führt (die Farben stammen von einem anderen, nicht verwandten Schritt):

Polygon Map mit lauten Rändern

Sie werden dann mit einer (ziemlich) realistisch aussehenden inselförmigen Maske zurückgelassen, die Ihren Zwecken dienen würde. Sie können es als Maske für Ihren Perlin-Lärm verwenden oder anhand der Entfernung zum Meer Höhenwerte generieren und Lärm hinzufügen (obwohl dies unnötig erscheint).


Vielen Dank für Ihre Antwort, aber dies war die erste (so ziemlich einzige) Lösung, die ich durch die Suche im Web erhalten habe. diese lösung scheint sehr nett zu sein, aber ich möchte den "einfachen" weg ausprobieren.
Ass

@Ace Fair enough, ist es wahrscheinlich ein bisschen viel des Guten für alles , was Sie tun werden: P Dennoch lohnt es sich unter Berücksichtigung sollten Sie jemals brauchen.
Polar

Ich bin froh, dass jemand darauf verlinkt ist - diese Seite ist immer auf meiner Liste mit "wirklich fantastischen Beiträgen darüber, wie jemand etwas implementiert hat".
Tim Holt

+1. Das ist so cool. Vielen Dank dafür, es wird auf jeden Fall hilfreich für mich sein!
Andre

1

Eine sehr einfache Methode besteht darin, einen inversen radialen oder sphärischen Gradienten mit Mittelpunkt in der Breite / 2 und Höhe / 2 zu erzeugen. Zum Maskieren möchten Sie den Gradienten vom Rauschen abziehen, anstatt ihn zu multiplizieren. Dies gibt Ihnen realistischere Küsten mit dem Nachteil, dass die Inseln nicht unbedingt miteinander verbunden sind.

Sie können den Unterschied zwischen dem Subtrahieren und Multiplizieren des Rauschens mit dem Gradienten hier sehen: http://www.vfxpedia.com/index.php?title=Tips_and_Techniques/Natural_Phenomena/Smoke

Wenn Sie sich nicht sicher sind, wie Sie einen radialen Verlauf erstellen sollen, können Sie dies als Startpunkt verwenden:

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

Vergessen Sie nicht, Ihr Gefälle auf die gleiche Höhe wie Ihre Höhenkarte zu skalieren, und Sie müssen Ihre Wasserlinie trotzdem irgendwie berücksichtigen.

Das Problem bei dieser Methode ist, dass Ihr Höhenfeld um den Mittelpunkt der Karte zentriert wird. Dieses Verfahren ist jedoch, sollten Sie mit dem Hinzufügen von Funktionen und machen Sie Ihre Landschaft vielfältiger , wie Sie beginnen können zusätzlich Funktionen Ihre Höhenkarte hinzuzufügen.


Danke für die Antwort. Ich bin nicht sicher, ob ich Sie richtig verstehe, aber haben Sie bemerkt, dass die Maske die ursprünglichen Heightmap-Daten überhaupt nicht beeinflusst, sie ist nur negativ beeinflusst, so dass sie nur die angezeigten Pixel definiert (in%) oder nicht . aber ich habe es auch mit einfachen gradianten ausprobiert und war mit dem ergebnis nicht zufrieden.
Ass

1

I zweiter Vorschlag von ollipekka: Was Sie tun möchten, ist eine geeignete Bias-Funktion von Ihrer Höhenkarte zu subtrahieren, so dass die Kanten garantiert unter Wasser sind.

Es gibt viele geeignete Bias-Funktionen, aber eine ziemlich einfache ist:

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

Dabei sind x und y die Koordinatenwerte, die so skaliert sind, dass sie zwischen 0 und 1 liegen. Diese Funktion nimmt den Wert 0 in der Kartenmitte an (bei x = y = 0,5) und tendiert an den Rändern zur Unendlichkeit. Durch Subtrahieren (skaliert mit einem geeigneten konstanten Faktor) von Ihrer Höhenkarte wird sichergestellt, dass die Höhenwerte auch in der Nähe der Kartenränder gegen unendlich tendieren. Wählen Sie einfach eine beliebige Höhe aus und nennen Sie sie den Meeresspiegel.

Wie ollipekka feststellt, garantiert dieser Ansatz nicht, dass die Insel zusammenhängend ist. Wenn Sie die Bias-Funktion jedoch mit einem relativ kleinen Skalierungsfaktor skalieren, sollte sie im mittleren Bereich der Karte größtenteils flach sein (was sich nicht wesentlich auf Ihr Gelände auswirkt). Eine signifikante Bias-Funktion wird nur in der Nähe der Ränder angezeigt. Auf diese Weise erhalten Sie eine quadratische, meist zusammenhängende Insel mit höchstens ein paar winzigen Unterinseln in der Nähe der Ränder.

Wenn Sie die Möglichkeit eines unverbundenen Geländes nicht stören, sollte Ihnen ein etwas größerer Skalierungsfaktor mehr Wasser und eine natürlichere Inselform verleihen. Durch Anpassen des Meeresspiegels und / oder des Maßstabs Ihrer ursprünglichen Höhenkarte können Sie auch die Größe und Form der resultierenden Insel (n) ändern.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.