Prosta metoda tworzenia maski mapy wyspy

21


Szukam przyjemnego i łatwego sposobu na wygenerowanie maski mapy wyspy za pomocą C #.


Zasadniczo używam z losową mapą wysokości generowaną z hałasem perlin, gdzie teren NIE jest otoczony wodą.

wprowadź opis zdjęcia tutaj

Następnym krokiem byłoby wygenerowanie maski, aby rogi i granice były tylko wodą.

wprowadź opis zdjęcia tutaj

Następnie mogę odjąć maskę od obrazu szumu perlin, aby uzyskać wyspę.

wprowadź opis zdjęcia tutaj

i bawić się z kontrastem ...

wprowadź opis zdjęcia tutaj

i krzywą gradientu, mogę uzyskać mapę wysokości wyspy tak, jak chcę.

wprowadź opis zdjęcia tutaj

(są to tylko przykłady)

więc, jak widać, „krawędzie” wyspy są po prostu odcięte, co nie jest dużym problemem, jeśli wartość koloru nie jest zbyt biała, ponieważ po prostu podzielę skalę szarości na 4 warstwy (woda, piasek, trawa i skała).

Moje pytanie brzmi: jak mogę wygenerować dobrze wyglądającą maskę, jak na drugim zdjęciu?


AKTUALIZACJA

Znalazłem tę technikę, wydaje mi się, że jest to dobry punkt wyjścia dla mnie, ale nie jestem pewien, jak dokładnie mogę ją wdrożyć, aby uzyskać pożądany efekt. http://mrl.nyu.edu/~perlin/experiments/puff/


AKTUALIZACJA 2

to jest moje ostateczne rozwiązanie.

Zaimplementowałem makeMask()funkcję w mojej pętli normalizacyjnej w następujący sposób:

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

i to jest ostatnia funkcja:

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

da to wynik jak na obrazku nr 3.

z niewielką zmianą w kodzie, możesz uzyskać pierwotnie poszukiwany wynik, jak na obrazku # 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;
        }
    }
As
źródło
jest jakaś szansa, że ​​możesz połączyć się z pełnym źródłem?
stoic

Odpowiedzi:

12

Generuj regularny hałas z nastawieniem na wyższe wartości w kierunku centrum. Jeśli chcesz mieć kwadratowe kształty wysp, takie jak pokazano w twoim przykładzie, użyłbym odległości do najbliższej krawędzi jako czynnika.

wprowadź opis zdjęcia tutaj

Dzięki temu współczynnikowi podczas generowania szumu maski można użyć czegoś takiego:

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

Gdzie distanceToNearestEdgezwraca odległość do najbliższej krawędzi mapy od tej pozycji. I isSoliddecyduje, czy wartość między 0 a 1 jest stała (niezależnie od tego, jaka jest twoja wartość odcięcia). Jest to bardzo prosta funkcja, może wyglądać następująco:

isSolid (liczba zmiennoprzecinkowa) zwraca wartość <solidCutOffValue

Gdzie solidCutOffValuejest jakakolwiek wartość, której używasz, aby zdecydować się na solidny lub nie. Może to być .5równomierny podział, lub .75bardziej solidny lub .25mniej solidny.

Wreszcie to trochę (1 - (d/maxDVal)) * noiseAt(x,y). Po pierwsze, otrzymujemy współczynnik od 0 do 1 z tego:

(1 - (d/maxDVal))

wprowadź opis zdjęcia tutaj

Gdzie 0jest na zewnętrznej krawędzi i 1na wewnętrznej krawędzi. Oznacza to, że nasz hałas jest bardziej solidny w środku i nie jest stały na zewnątrz. Jest to czynnik, który wpływamy na hałas, który otrzymujemy noiseAt(x,y).

Oto bardziej wizualna reprezentacja wartości, ponieważ nazwy mogą wprowadzać w błąd w stosunku do rzeczywistych wartości:

wprowadź opis zdjęcia tutaj

MichaelHouse
źródło
dzięki za szybką odpowiedź postaram się wdrożyć tę technikę. mam nadzieję uzyskać dzięki temu pożądaną wydajność.
Ace
Zapewne zajmie trochę ulepszeń, aby uzyskać głośne krawędzie tak, jak chcesz. Ale to powinno być dla ciebie solidną podstawą. Powodzenia!
MichaelHouse
do tej pory mam podstawową implementację do działania, czy możesz mi tylko podać przykład funkcji IsSolid? nie wiem, jak mogę uzyskać wartość od 0 do 1 na podstawie minimalnej i maksymalnej odległości od krawędzi. zobacz moją aktualizację mojego kodu do tej pory.
Ace
Miałem w tym trochę logiki. Naprawiłem to, aby mieć więcej sensu. I podał przykładisSolid
MichaelHouse
Aby uzyskać wartość z zakresu od 0 do 1, po prostu dowiedz się, jaka może być maksymalna wartość, i podziel przez to swoją bieżącą wartość. Następnie odjąłem to od jednego, ponieważ chciałem, aby zero było na zewnętrznej krawędzi, a 1 na wewnętrznej krawędzi.
MichaelHouse
14

Jeśli chcesz poświęcić na to trochę mocy obliczeniowej, możesz użyć techniki podobnej do tej, którą zrobił autor tego bloga . ( Uwaga: jeśli chcesz bezpośrednio skopiować jego kod, jest on w języku ActionScript). Zasadniczo generuje quasi-losowe punkty (tzn. Wygląda względnie jednolicie), a następnie wykorzystuje je do tworzenia wielokątów Voronoi .

Wielokąty Voronoi

Następnie ustawia zewnętrzne wielokąty na wodę i iteruje przez resztę wielokątów, czyniąc je wodami, jeśli pewien procent sąsiadujących wielokątów jest wodą . Następnie pozostaje maska ​​wielokąta z grubsza reprezentująca wyspę.

Mapa wielokątów

Na tej podstawie możesz zastosować szum do krawędzi, w wyniku czego coś będzie to przypominać (kolory pochodzą z innego, niezwiązanego kroku):

Mapa wielokątów z głośnymi krawędziami

Pozostaje ci (całkiem) realistycznie wyglądająca maska ​​w kształcie wyspy, która spełni twoje oczekiwania. Możesz użyć go jako maski dla hałasu Perlin, lub możesz wygenerować wartości wysokości na podstawie odległości od morza i dodać hałas (choć wydaje się to niepotrzebne).

Polarny
źródło
dziękuję za odpowiedź, ale to pierwsze (prawie tylko) rozwiązanie, które uzyskałem po przeszukaniu sieci. to rozwiązanie wydaje się bardzo miłe, ale chciałbym wypróbować „prosty” sposób.
Ace
@Ace W porządku, prawdopodobnie jest to trochę przesada w stosunku do wszystkiego, co zamierzasz zrobić: P Mimo to warto pamiętać, jeśli będziesz tego potrzebować.
Polar
Ciesz się, że ktoś do tego linkuje - ta strona jest zawsze na mojej liście „naprawdę fantastycznych postów o tym, jak ktoś coś zaimplementował”.
Tim Holt
+1. To jest takie fajne. Dziękuję za to, zdecydowanie mi się przyda!
Andre
1

Jedną z bardzo prostych metod jest utworzenie odwrotnego gradientu promieniowego lub sferycznego ze środkiem na szerokości / 2 i wysokości / 2. W celu maskowania chcesz odjąć gradient od szumu zamiast go pomnożyć. Daje to bardziej realistycznie wyglądające brzegi z wadą polegającą na tym, że wyspy niekoniecznie są połączone.

Różnicę między odejmowaniem a mnożeniem hałasu przez gradient można zobaczyć tutaj: http://www.vfxpedia.com/index.php?title=Tips_and_Techniques/Natural_Phenomena/Smoke

Jeśli nie masz pewności, jak utworzyć gradient radialny, możesz użyć tego jako punktu początkowego:

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

Nie zapomnij przeskalować gradientu do tej samej wysokości, co mapa wysokości, a nadal musisz w jakiś sposób uwzględnić linię wodną.

Problem z tą metodą polega na tym, że twoje pole wysokości będzie wyśrodkowane wokół środka mapy. Metoda ta jednak powinna Ci zacząć z dodawaniem funkcji i podejmowaniu krajobraz bardziej różnorodna, jak można wykorzystać dodatek dodawać funkcje do swojego wzrostu mapie.

ollipekka
źródło
dziękuję za odpowiedź. nie jestem pewien, czy dobrze cię rozumiem, ale czy zauważyłeś, że maska ​​w ogóle nie wpływa na pierwotne dane mapy wysokości, jest to tylko negatywne, więc po prostu definiuje piksele, które są wyświetlane (w%), czy nie . ale też wypróbowałem to z prostymi gradiantami i nie byłem zadowolony z wyniku.
Ace
1

Druga sugestia Ollipekki: chcesz odjąć odpowiednią funkcję odchylenia od mapy wysokości, aby zagwarantować, że krawędzie są pod wodą.

Istnieje wiele odpowiednich funkcji odchylenia, ale jedna dość prosta to:

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

gdzie x i y to wartości współrzędnych, skalowane leżeć w zakresie od 0 do 1. Funkcja ta przyjmuje wartość 0 w środku mapy (przy x = y = 0,5) i ma tendencję do nieskończoności na brzegach. W ten sposób odjęcie go (skalowane odpowiednim współczynnikiem stałym) od mapy wysokości zapewnia, że ​​wartości wysokości będą również miały tendencję do minus nieskończoności w pobliżu krawędzi mapy. Wystarczy wybrać dowolną dowolną wysokość i nazwać ją poziomem morza.

Jak zauważa ollipekka, takie podejście nie gwarantuje, że wyspa będzie przyległa. Jednak przeskalowanie funkcji odchylania za pomocą dość małego współczynnika skali powinno sprawić, że będzie ona głównie płaska w środkowej części mapy (a zatem nie wpłynie znacząco na twój teren), a znaczące odchylenie pojawi się tylko w pobliżu krawędzi. Dlatego zrobienie tego powinno dać ci kwadratową, w większości przylegającą wyspę z co najwyżej możliwym kilkoma małymi sub wyspami w pobliżu krawędzi.

Oczywiście, jeśli nie przeszkadza ci możliwość odłączenia terenu, nieco większy współczynnik skalowania powinien dać ci więcej wody i bardziej naturalny kształt wyspy. Dostosowania poziomu morza i / lub skali oryginalnej mapy wysokości można także użyć do zmiany wielkości i kształtu powstałej wyspy (wysp).

Ilmari Karonen
źródło