Un metodo semplice per creare la maschera della mappa dell'isola


21


Sto cercando un modo semplice e piacevole per generare una maschera per una mappa dell'isola con C #.


Fondamentalmente sto usando una mappa di altezza casuale generata con rumore perlin, in cui il terreno NON è circondato dall'acqua.

inserisci qui la descrizione dell'immagine

Il prossimo passo sarebbe quello di generare una maschera, per garantire che angoli e bordi siano solo acqua.

inserisci qui la descrizione dell'immagine

Quindi posso solo sottrarre la maschera dall'immagine del rumore del perlin per ottenere un'isola.

inserisci qui la descrizione dell'immagine

e giocando con il contrasto ..

inserisci qui la descrizione dell'immagine

e la curva del gradiente, posso ottenere una mappa altimetrica dell'isola proprio come la voglio ...

inserisci qui la descrizione dell'immagine

(questi sono solo esempi ovviamente)

come puoi vedere, i "bordi" dell'isola sono semplicemente tagliati, il che non è un grosso problema se il valore del colore non è troppo bianco, perché dividerò la scala di grigi in 4 strati (acqua, sabbia, erba e roccia).

La mia domanda è: come posso generare una maschera di bell'aspetto come nella seconda immagine?


AGGIORNARE

Ho trovato questa tecnica, sembra essere un buon punto di partenza per me, ma non sono sicuro di quanto esattamente posso implementarla per ottenere l'output desiderato. http://mrl.nyu.edu/~perlin/experiments/puff/


AGGIORNAMENTO 2

questa è la mia soluzione finale.

Ho implementato la makeMask()funzione all'interno del mio ciclo di normalizzazione in questo modo:

        //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 questa è la funzione 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;
    }

questo darà un output come nell'immagine n. 3.

con un po 'di cambiamento nel codice, puoi ottenere l'output originariamente desiderato come nell'immagine # 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;
        }
    }

qualche possibilità che puoi collegare alla tua fonte completa?
stoico,

Risposte:


12

Genera rumore regolare con una tendenza per valori più alti verso il centro. Se vuoi forme di isole quadrate come quelle che mostri nel tuo esempio, utilizzerei la distanza dal bordo più vicino come fattore.

inserisci qui la descrizione dell'immagine

Con questo fattore, è possibile utilizzare qualcosa di simile al seguente quando si genera il rumore della maschera:

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

Dove distanceToNearestEdgerestituisce la distanza dal bordo più vicino della mappa da quella posizione. E isSoliddecide se un valore compreso tra 0 e 1 è solido (qualunque sia il valore di taglio). È una funzione molto semplice, potrebbe apparire così:

isSolid (valore float) restituisce valore <solidCutOffValue

Qual solidCutOffValueè il valore che stai usando per decidere tra solido o no. Potrebbe essere .5per dividere uniformemente, o .75per più solido o .25per meno solido.

Finalmente questo poco (1 - (d/maxDVal)) * noiseAt(x,y). Innanzitutto, otteniamo un fattore tra 0 e 1 con questo:

(1 - (d/maxDVal))

inserisci qui la descrizione dell'immagine

Dov'è 0sul bordo esterno e 1sul bordo interno. Ciò significa che è più probabile che il nostro rumore sia solido all'interno e non solido all'esterno. Questo è il fattore che applichiamo al rumore da cui proveniamo noiseAt(x,y).

Ecco una rappresentazione più visiva di quali siano i valori, poiché i nomi potrebbero essere fuorvianti ai valori effettivi:

inserisci qui la descrizione dell'immagine


grazie per quella rapida risposta, cercherò di implementare questa tecnica. spero di ottenere l'output desiderato con questo.
Ace

Probabilmente ci vorranno alcune modifiche per ottenere i bordi rumorosi nel modo desiderato. Ma questa dovrebbe essere una solida base per te. In bocca al lupo!
MichaelHouse

finora ho fatto funzionare l'implementazione di base, puoi darmi solo un esempio della funzione IsSolid? non so come posso ottenere un valore compreso tra 0 e 1 in base alla distanza minima e massima dal bordo. vedi il mio aggiornamento per il mio codice finora.
Ace

Ho avuto un po 'di logica confusa lì dentro. L'ho corretto per avere più senso. E ha fornito un esempio diisSolid
MichaelHouse

Per ottenere un valore compreso tra 0 e 1, scopri semplicemente quale può essere il valore massimo e dividi il valore corrente per quello. Poi l'ho sottratto da uno, perché volevo che zero fosse sul bordo esterno e 1 sul bordo interno.
MichaelHouse

14

Se sei disposto a risparmiare un po 'di potere computazionale per questo, allora potresti usare una tecnica simile a quella dell'autore di questo blog . ( NB: se desideri copiare direttamente il suo codice, è in ActionScript). Fondamentalmente, genera punti quasi casuali (cioè sembra relativamente uniforme) e quindi li utilizza per creare poligoni Voronoi .

Poligoni Voronoi

Quindi imposta i poligoni esterni sull'acqua e scorre il resto dei poligoni, facendoli irrigare se una certa percentuale dei poligoni adiacenti è acqua . Ti rimane quindi una maschera poligonale che rappresenta all'incirca un'isola.

Mappa poligonale

Da questo puoi applicare un disturbo ai bordi, risultando in qualcosa di simile a questo (i colori provengono da un altro passaggio non correlato):

Mappa poligonale con bordi rumorosi

Ti viene quindi lasciata una maschera a forma di isola (abbastanza) dall'aspetto realistico, che servirebbe ai tuoi scopi. Potresti scegliere di usarlo come maschera per il tuo rumore Perlin, oppure potresti generare valori di altezza in base alla distanza dal mare e aggiungere rumore (anche se questo sembra inutile).


grazie per la tua risposta, ma questa è la prima (praticamente unica) soluzione ottenuta dalla ricerca sul web. questa soluzione sembra essere molto bella, ma vorrei provare il modo "semplice".
Ace

@Ace Abbastanza giusto, probabilmente è un po 'eccessivo per qualsiasi cosa tu abbia intenzione di fare: P Comunque, vale la pena tenere a mente se mai ne avrai bisogno.
Polar

Sono contento che qualcuno sia collegato a questo - quella pagina è sempre nella mia lista di "post davvero fantastici su come qualcuno ha implementato qualcosa".
Tim Holt,

+1. Questo è veramente forte. Grazie per questo, sarà sicuramente utile per me!
Andre

1

Un metodo molto semplice è quello di creare un gradiente radiale o sferico inverso con centro alla larghezza / 2 e altezza / 2. Per mascherare si desidera sottrarre il gradiente dal rumore anziché moltiplicarlo. Questo ti dà coste dall'aspetto più realistico con lo svantaggio che le isole non sono necessariamente collegate.

Puoi vedere la differenza tra sottrarre e moltiplicare il rumore con il gradiente qui: http://www.vfxpedia.com/index.php?title=Tips_and_Techniques/Natural_Phenomena/Smoke

Se non si è sicuri su come creare il gradiente radiale, è possibile utilizzare questo come punto di partenza:

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

Non dimenticare di ridimensionare il gradiente alla stessa altezza della tua mappa di altezza e in qualche modo devi comunque tener conto della linea di galleggiamento.

Il problema con questo metodo è che il tuo campo di altezza sarà centrato attorno al centro della mappa. Questo metodo, tuttavia, dovrebbe iniziare con l'aggiunta di funzioni e rendere il tuo paesaggio più diversificato in quanto puoi utilizzare l' aggiunta per aggiungere funzionalità alla tua mappa di altezza.


grazie per la risposta. non sono sicuro di averti capito bene, ma hai notato che la maschera non influenza affatto i dati della mappa di altezza originale, è solo influenzata in senso negativo, quindi definisce solo i pixel visualizzati (in%) o no . ma ci ho provato anche con semplici gradienti e non ero contento del risultato.
Ace

1

Secondo il suggerimento di ollipekka: quello che vuoi fare è sottrarre un'adeguata funzione di polarizzazione dalla tua mappa di altezza, in modo che i bordi siano garantiti sott'acqua.

Ci sono molte funzioni di bias adatte, ma una abbastanza semplice è:

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

dove x ed y sono i valori delle coordinate, scalati trovano tra 0 e 1. Questa funzione assume il valore 0 al centro della mappa (A x = y = 0,5) e tende all'infinito ai bordi. Pertanto, sottraendolo (ridimensionato in base a un fattore costante adatto) dalla propria mappa di altezza si assicura che anche i valori di altezza tendano a meno infinito vicino ai bordi della mappa. Scegli semplicemente l'altezza arbitraria che desideri e chiamala livello del mare.

Come osserva Ollipekka, questo approccio non garantirà che l'isola sarà contigua. Tuttavia, il ridimensionamento della funzione di polarizzazione in base a un fattore di scala abbastanza piccolo dovrebbe renderlo per lo più piatto nell'area centrale della mappa (quindi non influire molto sul terreno), con una distorsione significativa che appare solo vicino ai bordi. Quindi, farlo dovrebbe darti un'isola quadrata, per lo più contigua con, al massimo, possibili alcune piccole isole secondarie vicino ai bordi.

Naturalmente, se non ti dispiace la possibilità di terreni sconnessi, un fattore di ridimensionamento leggermente più grande dovrebbe darti più acqua e una forma dell'isola più naturale. La regolazione del livello del mare e / o della scala della mappa di altezza originale può essere utilizzata anche per variare le dimensioni e la forma delle isole risultanti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.