Простий метод створення маски островних карт


21


Я шукаю приємний та простий спосіб створити маску для карти острова за допомогою C #.


В основному я використовую довільну карту висоти, генеровану перлинським шумом, де місцевість НЕ оточена водою.

введіть тут опис зображення

Наступним кроком буде створення маски, щоб кути та межі були лише водою.

введіть тут опис зображення

Тоді я можу просто відняти маску від зображення шуму перлин, щоб отримати острів.

введіть тут опис зображення

і граючи навколо з контрастом ..

введіть тут опис зображення

і крива градієнта, я можу отримати мапу висоти острова так, як мені хочеться.

введіть тут опис зображення

(це лише приклади, звичайно)

так що, як бачите, "краї" острова просто обрізані, що не є великою проблемою, якщо значення кольору не надто біле, тому що я просто розділю сірий масштаб на 4 шари (вода, пісок, трава і скеля).

Моє запитання: як я можу створити добре виглядає маску, як на другому зображенні?


ОНОВЛЕННЯ

Я знайшов цю техніку, це здається гарною відправною точкою для мене, але я не впевнений, наскільки точно я можу її реалізувати, щоб отримати бажаний результат. http://mrl.nyu.edu/~perlin/experiment/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;
        }
    }

будь-який шанс, що ви можете зв’язати з вашим повним джерелом?
stoic

Відповіді:


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

Де 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 для цієї швидкої відповіді, я спробую реалізувати цю техніку. сподіваюся отримати бажаний вихід з цим.
Туз

Можливо, знадобиться певна настройка, щоб отримати шумні краї так, як ви хочете. Але це має бути міцною основою для вас. Удачі!
MichaelHouse

Поки я отримав базову реалізацію для роботи, ви можете просто надати мені приклад функції IsSolid? Я не знаю, як я можу отримати значення від 0 до 1, виходячи з мінімальної та максимальної відстані від краю. переглянути моє оновлення для мого коду до цих пір.
Туз

У мене була якась змішана логіка. Я це виправив, щоб мати більше сенсу. І isSolid
наводив

Щоб отримати значення між 0 і 1, ви просто дізнаєтесь, яким може бути максимальне значення, і поділите своє поточне значення на це. Потім я відняв це від одного, бо хотів, щоб нуль знаходився на зовнішньому краї, а 1 - на внутрішньому.
MichaelHouse

14

Якщо ви готові зекономити для цього деяку обчислювальну потужність, ви можете використати аналогічну методику, яку зробив автор цього блогу . ( Примітка. Якщо ви хочете безпосередньо скопіювати його код, він знаходиться в ActionScript). В основному він генерує квазі випадкові точки (тобто виглядає відносно рівномірно), а потім використовує їх для створення полігонів Вороного .

Вороного полігони

Потім він встановлює зовнішні багатокутники для води і проходить через інші частини багатокутників, роблячи їх водою, якщо певний відсоток сусідніх багатокутників є водою . Тоді вам залишається багатокутна маска, яка приблизно представляє острів.

Карта багатокутника

З цього ви можете подати шум до країв, в результаті чого щось подібне до цього (кольори - з іншого, не пов'язаного кроку):

Карта полігону з галасливими краями

Тоді вам залишається (цілком) реалістично виглядає маска у формі острова, яка б відповідала вашим цілям. Ви можете вибрати його як маску для вашого шуму Перліна, або потім зможете генерувати значення висоти на основі відстані до моря та додавати шум (хоча це здається непотрібним).


THX для вашої відповіді, але це woth перше (в значній мірі єдине) рішення, яке я отримав від пошуку в Інтернеті. таке рішення здається дуже приємним, але я хотів би спробувати "простий" спосіб.
Туз

@ Досить справедливо, це, мабуть, трохи непосильне для того, що ви збираєтесь робити: P Все-таки варто пам’ятати, якщо вам це коли-небудь знадобиться.
Полярний

Радий, що хтось з цим пов’язаний - ця сторінка завжди в моєму списку "справді фантастичних публікацій про те, як хтось щось реалізував".
Тім Холт

+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

Я є другою пропозицією ollipekka: те, що ви хочете зробити, - це відняти відповідну функцію зміщення з вашої карти висоти, щоб краї були гарантовано підводними.

Існує безліч підходящих функцій зміщення, але одна досить проста:

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

де x і y - значення координат, масштабовані на рівні від 0 до 1. Ця функція приймає значення 0 в центрі карти (при x = y = 0,5) і має тенденцію до нескінченності по краях. Таким чином, віднімання його (масштабування відповідного постійного коефіцієнта) з вашої карти висоти гарантує, що значення висоти також будуть прагнути до мінус нескінченності біля країв карти. Просто виберіть будь-яку довільну висоту, яку ви хочете, і назвіть її рівнем моря.

Як зазначає ollipekka, такий підхід не гарантує, що острів буде суміжним. Однак масштабування функції зміщення за допомогою досить малого масштабного коефіцієнта повинно зробити його здебільшого рівним в середній частині карти (таким чином, не впливаючи на вашу місцевість), при цьому значне зміщення з’явиться лише біля країв. Таким чином, це повинно дати вам скутерський, здебільшого суміжний острів з, максимум, можливими декількома крихітними під-островами біля країв.

Звичайно, якщо ви не заперечуєте над можливістю відключення місцевості, дещо більший коефіцієнт масштабування повинен дати вам більше води та більш природну форму острова. Регулювання рівня моря та / або масштабу вашої оригінальної карти висоти також може використовуватися для зміни розміру та форми отриманого острова.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.