アイランドマップマスクを作成する簡単な方法


21


私は、C#を使用して島のマップのマスクを生成するための素敵で簡単な方法を探しています。


基本的には、地形が水に囲まれていないパーリンノイズで生成されたランダムハイトマップを使用しています。

ここに画像の説明を入力してください

次のステップは、マスクを生成して、角と境界がただの水であることを確認することです。

ここに画像の説明を入力してください

その後、perlinノイズ画像からマスクを差し引くだけで島を取得できます。

ここに画像の説明を入力してください

コントラストをいじって

ここに画像の説明を入力してください

勾配曲線を使用すると、希望どおりに島の高さマップを取得できます。

ここに画像の説明を入力してください

(これらはもちろん単なる例です)

ご覧のとおり、島の「エッジ」は切り取られていますが、カラー値が白すぎない場合は大きな問題ではありません。グレースケールを4つのレイヤー(水、砂、草、岩)。

私の質問は、2番目の画像のように見栄えの良いマスクをどのように生成できますか?


更新

私はこの手法を見つけましたが、それは私にとって良い出発点のようですが、目的の出力を得るためにどの程度正確に実装できるかわかりません。 http://mrl.nyu.edu/~perlin/experiments/puff/


更新2

これが私の最終的な解決策です。

makeMask()このように正規化ループ内に関数を実装しました。

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

そしてこれが最終機能です:

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

これにより、画像#3のような出力が得られます。

コードを少し変更するだけで、イメージ#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;
        }
    }

あなたはあなたの完全なソースにリンクできますか?
ストイック

回答:


12

中心に向かってより高い値のバイアスで規則的なノイズを生成します。例で示したような正方形のような島の形状が必要な場合は、最も近いエッジまでの距離を要因として使用します。

ここに画像の説明を入力してください

その要因を使用すると、マスクノイズを生成するときに次のようなものを使用できます。

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 distanceToNearestEdgeは、その位置からマップの最も近いエッジまでの距離を返します。そしてisSolid、0と1の間の値がソリッドかどうか(カットオフが何であれ)を決定します。これは非常に単純な関数で、次のようになります。

isSolid(float value)戻り値<solidCutOffValue

solidCutOffValueソリッドかどうかを決定するために使用している値はどこにありますか。それは可能性.5さえ分割のための、または.75それ以上の固形または.25以下固体のために。

最後に、この少し(1 - (d/maxDVal)) * noiseAt(x,y)。最初に、これで0と1の間の係数を取得します。

(1 - (d/maxDVal))

ここに画像の説明を入力してください

どこ0が外側の端にあり1、内側の端にあります。これは、ノイズが内側で固体で、外側で非固体になる可能性が高いことを意味します。これは、から得られるノイズに適用される係数noiseAt(x,y)です。

名前が実際の値を誤解させる可能性があるため、値が何であるかをより視覚的に表現したものを次に示します。

ここに画像の説明を入力してください


その簡単な答えのthx、私はこのテクニックを実装しようとします。これで目的の出力が得られることを期待しています。
エース

ノイズの多いエッジを希望どおりに調整するには、おそらく微調整が必​​要です。しかし、これはあなたにとって堅実な基盤であるべきです。がんばろう!
マイケルハウス

これまでのところ、基本実装が機能するようになっていますが、IsSolid関数の例を教えていただけますか?私はエッジからの最小距離と最大距離に基づいて0と1の間の値を取得する方法を知りません。これまでの私のコードの更新を参照してください。
エース

私はそこにいくつかの混乱したロジックがありました。より意味をなすように修正しました。そして、の例提供isSolid
MichaelHouse

0から1の間の値を取得するには、最大値が何であるかを見つけ、現在の値をそれで割ります。次に、1からそれを減算しました。ゼロを外側のエッジに、1を内側のエッジにしたかったからです。
マイケルハウス

14

このためにある程度の計算能力をspareしまない場合は、このブログの著者が行ったことと同様の手法を使用できます。(注:彼のコードを直接コピーしたい場合は、ActionScriptにあります)。基本的に、彼は準ランダムポイントを生成し(つまり、比較的均一に見える)、これらを使用してボロノイポリゴンを作成します

ボロノイ多角形

次に、外側のポリゴンを水に設定し、残りのポリゴンを繰り返し処理し、隣接するポリゴンの特定の割合が水である場合、それらを水にします。その後、島を大まかに表すポリゴンマスクが残ります。

ポリゴンマップ

これから、エッジにノイズを適用して、これに似たものを作成できます(色は別の無関係なステップからのものです)。

ノイズの多いエッジを持つポリゴンマップ

その後、目的にかなう(非常に)リアルな島の形をしたマスクが残ります。パーリンノイズのマスクとして使用するか、海までの距離に基づいて高さの値を生成し、ノイズを追加することができます(ただし、不要なようです)。


あなたの返事のためのthx、しかしこれは私がウェブを検索することから得た最初の(ほとんど唯一の)解決策でした。このソリューションは非常に良いように見えますが、「単純な」方法を試してみたいと思います。
エース

@Aceフェアなことに、それはおそらくあなたがやろうとしている何のためのビットやり過ぎです:Pはまだ、それは念頭に置いて価値があるはずですあなたがそれを必要とします。
ポーラー

誰かがこれにリンクしてくれてうれしいです-そのページは常に「誰かが何かを実装した方法に関する本当に素晴らしい投稿」の私のリストに載っています。
ティム・ホルト

+1。これはとてもクールです。これをありがとう、それは間違いなく私のために役立つでしょう!
アンドレ

1

非常に簡単な方法の1つは、幅が2で高さが/ 2の逆ラジアルまたは球体のグラデーションを作成することです。マスキングでは、ノイズを乗算するのではなく、ノイズからグラデーションを減算します。これにより、より現実的な外観の海岸が得られますが、島が必ずしも接続されているわけではないという欠点があります。

グラデーションを使用したノイズの減算と乗算の違いは、http//www.vfxpedia.com/index.php?title = Tips_and_Techniques / Natural_Phenomena / Smokeで確認できます

放射状グラデーションの作成方法がわからない場合は、これを開始点として使用できます。

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

勾配を高さマップと同じ高さに拡大縮小することを忘れないでください。それでも、なんらかの形でウォーターラインを考慮する必要があります。

この方法の問題は、高さフィールドがマップの中心を中心とすることです。ただし、この方法では、追加機能を使用して高さマップに機能を追加できるため、機能の追加とランドスケープの多様化を開始する必要があります。


返信のthx。私はあなたを正しく理解しているかどうかはわかりませんが、マスクは元のハイトマップデータにまったく影響を与えないことに気付きましたか? 。しかし、私はそれを単純なグラデーションで試してみましたが、結果には満足していませんでした。
エース

1

2番目のollipekkaの提案:あなたがしたいことは、適切なバイアス関数をハイトマップから差し引くことです。これにより、エッジが水中にあることが保証されます。

適切なバイアス関数はたくさんありますが、かなり単純な関数は次のとおりです。

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

ここで、xyは、0〜1の間にスケーリングされた座標値です。この関数は、マップの中心(x = y = 0.5)で値0を取り、端で無限大になります。したがって、高さマップから(適切な定数係数でスケーリングされた)を差し引くと、高さの値もマップエッジの近くでマイナス無限大になる傾向があります。あなたが望む任意の高さを選んで、それを海面と呼びます。

ollipekkaが指摘しているように、このアプローチは島が隣接していることを保証するものではありません。ただし、バイアス関数をかなり小さい縮尺率でスケーリングすると、マップの中央領域でほぼ平坦になり(したがって、地形に大きな影響はありません)、エッジの近くにのみ大きなバイアスが表示されます。したがって、そうすることで、四角い、ほとんど連続した島が得られ、せいぜい、エッジの近くにいくつかの小さなサブ島ができるはずです。

もちろん、接続されていない地形の可能性を気にしない場合は、多少大きなスケーリング係数を使用すると、より多くの水とより自然な島の形が得られます。元の高さマップの海面やスケールを調整して、結果の島のサイズと形状を変えることもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.