Ada Haritası Maskesi Oluşturmak İçin Basit Bir Yöntem


21


C # ile bir ada haritası için maske oluşturmak için güzel ve kolay bir yol arıyorum.


Temel olarak, arazinin su ile çevrili OLMADIĞINDA, perlin gürültüsü ile oluşturulan rastgele bir yükseklik haritası kullanıyorum.

görüntü tanımını buraya girin

Bir sonraki adım, köşelerin ve sınırların sadece su olmasını sağlamak için bir maske oluşturmak olacaktır.

görüntü tanımını buraya girin

O zaman bir ada almak için maskeyi perlin gürültüsü görüntüsünden çıkarabilirim.

görüntü tanımını buraya girin

ve kontrast ile uğraşırken ..

görüntü tanımını buraya girin

ve gradyan eğrisi, istediğim gibi bir ada yükseklik haritası alabilirim ..

görüntü tanımını buraya girin

(bunlar sadece elbette örneklerdir)

Görebildiğiniz gibi, adanın "kenarları" kesildi, renk değeri çok beyaz değilse büyük bir sorun değil çünkü gri tonlamayı sadece 4 katmana (su, kum, çim ve Kaya).

Sorum şu, ikinci resimde olduğu gibi nasıl iyi görünümlü bir maske oluşturabilirim?


GÜNCELLEŞTİRME

Bu tekniği buldum, benim için iyi bir başlangıç ​​noktası gibi görünüyor, ancak istenen çıktıyı elde etmek için ne kadar doğru uygulayabileceğimden emin değilim. http://mrl.nyu.edu/~perlin/experiments/puff/


GÜNCELLEME 2

bu benim son çözümüm.

Bu makeMask()işlevi normalleştirme döngümün içine şu şekilde uyguladım :

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

ve bu son işlevdir:

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

bu, resim # 3'teki gibi bir çıktı verecektir.

kodda biraz değişiklik yapılması durumunda, başlangıçtaki resimden # 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;
        }
    }

tam kaynağınıza bağlanma şansınız var mı?
stoic

Yanıtlar:


12

Merkeze doğru daha yüksek değerler için önyargılı düzenli gürültü oluşturun. Örneğinizde gösterdiğiniz gibi kare şeklinde ada şekilleri istiyorsanız, en yakın kenara olan mesafeyi faktörünüz olarak kullanırdım.

görüntü tanımını buraya girin

Bu faktörle, maske gürültüsünü oluştururken aşağıdaki gibi bir şey kullanabilirsiniz:

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

Nerede distanceToNearestEdgeo pozisyondan haritanın en yakın kenarına mesafe döner. Ve isSolid0 ile 1 arasındaki bir değerin katı olup olmadığına karar verir (kesiğiniz ne olursa olsun). Çok basit bir fonksiyon, şöyle görünebilir:

isSolid (float değeri) dönüş değeri <solidCutOffValue

Nerede solidCutOffValueEğer katı arasında olmadığına karar için kullandığınız ne olursa olsun değerdir. .5Bölünmek, hatta .75daha katı ya da .25daha az katı olması için olabilir .

Sonunda, bu biraz (1 - (d/maxDVal)) * noiseAt(x,y). İlk önce, bununla 0 ile 1 arasında bir faktör elde ediyoruz:

(1 - (d/maxDVal))

görüntü tanımını buraya girin

Burada 0dış kenarında ve 1iç ucunda yer alır. Bu, gürültümüzün iç tarafta sağlam, dış tarafta sağlam olmadığı anlamına gelir. Bu, aldığımız gürültüye uygulanan faktördür noiseAt(x,y).

İsimlerin gerçek değerlere yanıltıcı olabileceğinden, değerlerin ne olduğuna dair daha görsel bir sunum:

görüntü tanımını buraya girin


Bu hızlı cevap için teşekkürler, bu tekniği uygulamaya çalışacağım. bununla istenen çıktıyı almayı umuyorum.
Ace

Gürültülü kenarları istediğiniz gibi elde etmek için biraz ince ayar yapmanız gerekebilir. Fakat bu sizin için sağlam bir temel olmalı. İyi şanslar!
MichaelHouse

Şimdiye kadar temel uygulamanın çalışmasını sağladım, bana sadece IsSolid işlevi örneği verebilir misiniz? Kenardan minimum ve maksimum mesafeye bağlı olarak 0 ile 1 arasında nasıl bir değer alabileceğimi bilmiyorum. şu ana kadar kodum için güncellememe bakın.
Ace

İçeride biraz karışık mantık vardı. Daha mantıklı olmak için düzelttim. Ve bir örnek verdiisSolid
MichaelHouse

0 ile 1 arasında bir değer elde etmek için, maksimum değerin ne olduğunu bulmanız ve mevcut değerinizi buna göre bölmeniz yeterlidir. Sonra bunu birinden çıkardım çünkü sıfırın dış kenarda olmasını ve 1'in iç kenarda olmasını istedim.
MichaelHouse

14

Bunun için bir miktar hesaplama gücü ayırmaya istekliyseniz, o zaman bu blogun yazarının yaptığı ile benzer bir teknik kullanabilirsiniz . ( Not: Kodunu doğrudan kopyalamak istiyorsanız, ActionScript'tedir). Temel olarak, yarı-rastgele noktalar oluşturur (yani nispeten düzgün görünüyor) ve sonra Voronoi poligonları oluşturmak için bunları kullanır .

Voronoi Poligonları

Daha sonra dışarıdaki çokgenleri suya ayarlar ve bitişik çokgenlerin belirli bir yüzdesi su ise , onları çok su yaparak, çokgenlerin geri kalan kısmında yinelenir . Daha sonra kabaca bir adayı temsil eden bir çokgen maskesi ile bırakılırsınız.

Poligon Haritası

Bundan kenarlara gürültü uygulayabilirsiniz, buna benzer bir şey ortaya çıkar (renkler başka, ilgisiz bir adımdan gelir):

Gürültülü Kenarlı Çokgen Harita

Daha sonra amaçlarınıza uygun, gerçekçi görünümlü ada şeklindeki bir maske ile kalırsınız. Perlin gürültünüz için maske olarak kullanmayı seçebilir veya daha sonra denize olan mesafeye bağlı olarak yükseklik değerleri oluşturabilir ve gürültü ekleyebilir (bu gereksiz görünmesine rağmen).


Cevabınız için teşekkürler, ama bu web aramada aldığım ilk (hemen hemen sadece) çözümü. Bu çözüm çok hoş göründü, ama "basit" yolu denemek istiyorum.
Ace

@Ace Fair yeterince, ne yapacaksanız yapın, muhtemelen biraz overkill: P Yine de, hiç ihtiyacınız olursa akılda tutmaya değer .
Polar

Buna bağlı birileri memnun - bu sayfa her zaman "birinin bir şeyi nasıl uyguladığı hakkında gerçekten harika yazılar" listemde.
Tim Holt,

+1. Bu çok havalı. Bunun için teşekkür ederim, kesinlikle benim için yardımcı olacaktır!
Andre

1

Çok basit bir yöntem, / 2 ve yükseklik / 2 genişliğinde merkez ile ters radyal veya küresel degrade oluşturmaktır. Maskeleme için , degradeyi çarpmak yerine gürültüden çıkarmak istiyorsunuz . Bu, size adaların mutlaka birbirine bağlanmadığı dezavantajı ile daha gerçekçi görünüm kıyıları verir.

Gürültüyü çıkarma ve çarpmayla arasındaki farkı gradyan ile burada görebilirsiniz: http://www.vfxpedia.com/index.php?title=Tips_and_Techniques/Natural_Phenomena/Smoke

Radyal degradenin nasıl oluşturulacağından emin değilseniz, bunu başlangıç ​​noktası olarak kullanabilirsiniz:

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

Gradyanızı yükseklik haritanızla aynı yüksekliğe ölçeklendirmeyi unutmayın; yine de su hattınızı bir şekilde etkilemeniz gerekir.

Bu yöntemle ilgili sorun, yükseklik alanınızın haritanın merkezi çevresinde ortalanmış olmasıdır. Bu yöntem, ancak, özellikler ekleyerek ve kullanabileceğiniz gibi manzara daha çeşitli hale başlamak gerekir eklenmesini boy haritasına özellikler eklemek.


Cevap için teşekkürler. Sizi doğru anladığımdan emin değilim, ancak maskenin orijinal yükseklik haritası verisini hiç etkilemediğini fark ettiniz mi, sadece olumsuzdan etkilenir, bu yüzden görüntülenen pikselleri (% olarak) tanımlar veya belirlemez . ama aynı zamanda basit degradelerle denedim ve sonuçtan memnun değildim.
Ace,

1

İkinci olarak ollipekka'nın önerisi: Yapmak istediğiniz şey, yükseklik haritanızdan uygun bir önyargı fonksiyonunu çıkarmak, böylece kenarların su altında kalması garanti ediliyor.

Çok sayıda uygun önyargı işlevi var, ancak oldukça basit biri:

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

burada x ve y , 0 ile 1 arasında olacak şekilde ölçeklendirilen koordinat değerleridir. Bu işlev, haritanın ortasındaki 0 ​​değerini alır ( x = y = 0.5'te) ve kenarlarda sonsuzluğa meyillidir. Böylece, yükseklik haritanızdan (uygun bir sabit faktör ile ölçeklendirilir) çıkarmanız, yükseklik değerlerinin harita kenarlarının yanında eksi sonsuzluğa meyilli olmasını sağlar. İstediğin herhangi bir yüksekliği seç ve deniz seviyesine çevir.

Ollipekka'nın belirttiği gibi, bu yaklaşım adanın bitişik olacağını garanti etmeyecektir. Bununla birlikte, önyargı işlevini oldukça küçük bir ölçek faktörü ile ölçeklendirmek, haritanın orta alanında çoğunlukla düz hale getirmelidir (bu nedenle arazinizi çok fazla etkilemez), yalnızca kenarların yakınında belirgin bir önyargı görünecektir. Bu nedenle, bunu yapmak size, kenarlarında en az birkaç küçük deniz altı adası olan ve en çok da mümkünse bitişik ada olan sağlam bir ada vermelidir.

Tabii ki, bağlantısız arazi olasılığına aldırış etmiyorsanız, biraz daha büyük bir ölçeklendirme faktörü size daha fazla su ve daha doğal görünümlü bir ada şekli vermelidir. Deniz seviyesini ve / veya orijinal yükseklik haritanızın ölçeğini ayarlamak, elde edilen adanın / adaların boyutunu ve şeklini değiştirmek için de kullanılabilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.