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

21


Я ищу хороший и простой способ создать маску для карты острова с помощью C #.


В основном я использую случайную карту высот, сгенерированную с помощью перлин-шума, где ландшафт НЕ окружен водой.

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

Следующим шагом будет создание маски, чтобы углы и границы были просто водой.

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

Тогда я могу просто вычесть маску из изображения перлин-шума, чтобы получить остров.

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

и играть с контрастом ..

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

и кривая градиента, я могу получить карту высот острова так, как я этого хочу ..

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

(это всего лишь примеры, конечно)

так что, как вы можете видеть, «края» острова просто обрезаются, что не является большой проблемой, если значение цвета не слишком белое, потому что я просто разделю оттенки серого на 4 слоя (вода, песок, трава и камень).

Мой вопрос, как я могу создать красивую маску, как на втором изображении?


ОБНОВИТЬ

Я нашел эту технику, она мне кажется хорошей отправной точкой, но я не уверен, насколько точно я могу ее реализовать, чтобы получить желаемый результат. 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

Где distanceToNearestEdgeвозвращает расстояние до ближайшего края карты от этой позиции. И isSolidрешает, является ли значение между 0 и 1 сплошным (независимо от того, что вы обрезаете). Это очень простая функция, она может выглядеть так:

isSolid (значение с плавающей точкой) возвращаемое значение <solidCutOffValue

Где solidCutOffValueкакое значение вы используете, чтобы выбирать между твердым или нет. Это может быть .5даже для разделения, или .75для более твердого или .25для менее твердого.

Наконец, это немного (1 - (d/maxDVal)) * noiseAt(x,y). Во-первых, мы получаем коэффициент от 0 до 1 с этим:

(1 - (d/maxDVal))

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

Где 0на внешнем краю и 1на внутреннем краю. Это означает, что наш шум с большей вероятностью будет сплошным внутри и не сплошным снаружи. Это фактор, который мы применяем к шуму, который мы получаем noiseAt(x,y).

Вот более наглядное представление значений, поскольку имена могут вводить в заблуждение фактические значения:

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

MichaelHouse
источник
спасибо за этот быстрый ответ, я постараюсь реализовать эту технику. надеюсь получить желаемый результат с этим.
Ace
Скорее всего, потребуется некоторая настройка, чтобы получить шумные края, как вы хотите их. Но это должно быть прочной основой для вас. Удачи!
MichaelHouse
до сих пор я получил базовую реализацию для работы, вы можете просто дать мне пример функции IsSolid? Я не знаю, как я могу получить значение от 0 до 1 на основе минимального и максимального расстояния от края. посмотрите мое обновление для моего кода до сих пор.
Ace
У меня там была какая-то смешанная логика. Я исправил это, чтобы иметь больше смысла. И предоставил примерisSolid
MichaelHouse
Чтобы получить значение от 0 до 1, вы просто узнаете, какое максимальное значение может быть, и поделите текущее значение на это. Затем я вычел это из единицы, потому что хотел, чтобы ноль находился на внешнем крае, а 1 - на внутреннем.
MichaelHouse
14

Если вы готовы сэкономить некоторые вычислительные мощности для этого, то вы можете использовать метод, аналогичный тому, что делал автор этого блога . ( Примечание: если вы хотите напрямую скопировать его код, он написан на ActionScript). По сути, он генерирует квазислучайные точки (т.е. выглядит относительно равномерно), а затем использует их для создания многоугольников Вороного .

Вороной Полигоны

Затем он устанавливает внешние полигоны на воду и перебирает остальные полигоны, превращая их в воду, если определенный процент смежных полигонов - вода . Затем вы остаетесь с многоугольной маской, грубо представляющей остров.

Карта полигонов

Из этого вы можете применить шум к краям, что приведет к чему-то похожему на это (цвета из другого, не связанного с этим шага):

Карта многоугольника с зашумленными краями

Затем вы получаете (вполне) реалистично выглядящую маску в форме острова, которая будет служить вашим целям. Вы можете использовать его в качестве маски для шума Перлина, или затем вы можете генерировать значения высоты на основе расстояния до моря и добавлять шум (хотя это кажется ненужным).

полярный
источник
Спасибо за ваш ответ, но это первое (почти единственное) решение, которое я получил от поиска в Интернете. Это решение кажется очень хорошим, но я хотел бы попробовать «простой» способ.
Ace
@ Достаточно справедливо, это, вероятно, немного излишне для того, что вы собираетесь делать: 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;
    }

Не забудьте масштабировать ваш градиент до той же высоты, что и ваша карта высот, и вам все равно нужно как-то учесть ватерлинию.

Проблема с этим методом в том, что ваше поле высоты будет центрировано вокруг центра карты. Этот метод, однако, должен помочь вам начать добавлять объекты и сделать ландшафт более разнообразным, так как вы можете использовать дополнение для добавления объектов на карту высот.

ollipekka
источник
спасибо за ответ. я не уверен, правильно ли я вас понял, но вы заметили, что маска вообще не влияет на исходные данные карты высот, она просто влияет на негатив, поэтому она просто определяет отображаемые пиксели (в%) или нет , но также я попробовал это с простыми градианами, и я не был доволен результатом.
Ace
1

Второе предложение ollipekka: вы хотите вычесть подходящую функцию смещения из вашей карты высот, чтобы края гарантированно находились под водой.

Есть много подходящих функций смещения, но одна довольно простая:

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

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

Как отмечает ollipekka, такой подход не гарантирует, что остров будет смежным. Однако масштабирование функции смещения с помощью довольно небольшого масштабного коэффициента должно сделать ее в основном плоской в ​​средней области карты (таким образом, не сильно затрагивая вашу местность), при этом значительный уклон появляется только вблизи краев. Таким образом, в результате вы получите квадратный, в основном, смежный остров с, по крайней мере, несколькими крошечными субостровами у краев.

Конечно, если вы не возражаете против возможности разъединения ландшафта, несколько больший коэффициент масштабирования должен дать вам больше воды и более естественную форму острова. Регулировка уровня моря и / или масштаба вашей исходной карты высот также может использоваться для изменения размера и формы результирующих островов.

Илмари Каронен
источник