Процедурная генерация ландшафта острова

Изменить: переписал свой вопрос после нескольких попыток и сделал его более конкретным.

Привет, я создаю мобильную RTS-игру с процедурно сгенерированными картами. Я разработал, как создать ландшафт с базовым шумом Перлина, и попытался интегрировать https://gamedev.stackexchange.com/questions/54276/a-simple-method-to-create-island-map-mask для процедурного создания острова. Это результат на данный момент:

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

Изображение ниже от http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/ показывает, какой ландшафт мне нужен. Учебное пособие там отличное, но было бы слишком интенсивным, поэтому пост.

Я хочу остров произвольной формы с массой суши, созданной шумом Перлина, окруженной водой.

Я хочу именно такую ​​форму

edit: базовый ландшафтный дизайн Perlin сейчас работает =)

Вот мой код. Сценарий, прикрепленный к нулю с кнопкой для активации Begin ():

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;

public class Gen_Perlin : MonoBehaviour {

public float Tiling = 0.5f;
private bool active = false;
public int mapHeight = 10;

public void Begin()
{
    if (active == false) {
        TerrainData terrainData = new TerrainData ();
        const int size = 513;
        terrainData.heightmapResolution = size;
        terrainData.size = new Vector3 (2000, mapHeight, 2000);

        terrainData.heightmapResolution = 513;
        terrainData.baseMapResolution = 1024;
        terrainData.SetDetailResolution (1024, 1024);

        Terrain.CreateTerrainGameObject (terrainData);
        GameObject obj = GameObject.Find ("Terrain");
        obj.transform.parent = this.transform;

        if (obj.GetComponent<Terrain> ()) {
            GenerateHeights (obj.GetComponent<Terrain> (), Tiling);
        }
    } else {
        GameObject obj = GameObject.Find ("Terrain");
        if (obj.GetComponent<Terrain> ()) {
            GenerateHeights (obj.GetComponent<Terrain> (), Tiling);
        }
    }
}

public void GenerateHeights(Terrain terrain, float tileSize)
{
    Debug.Log ("Start_Height_Gen");
    float[,] heights = new float[terrain.terrainData.heightmapWidth, terrain.terrainData.heightmapHeight];

    for (int i = 0; i < terrain.terrainData.heightmapWidth; i++)
    {
        for (int k = 0; k < terrain.terrainData.heightmapHeight; k++)
        {
            heights[i, k] = 0.25f + Mathf.PerlinNoise(((float)i / (float)terrain.terrainData.heightmapWidth) * tileSize, ((float)k / (float)terrain.terrainData.heightmapHeight) * tileSize);
            heights[i, k] *= makeMask( terrain.terrainData.heightmapWidth, terrain.terrainData.heightmapHeight, i, k, heights[i, k] );
        }
    }
    terrain.terrainData.SetHeights(0, 0, heights);
}

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

}


person 4t0m1c    schedule 07.05.2015    source источник
comment
см. простой генератор карты острова на C ++   -  person Spektre    schedule 15.04.2016
comment
Хорошо, что это работает! Следующим шагом я бы предложил заменить Mathf.PerlinNoise импортированной библиотекой с хорошей симплексной реализацией 2D. Проблема шума Перлина заключается в том, что он очень привязан к сетке. Вы можете импортировать Unity.Mathematics.noise.snoise(float2) или использовать this. Последнее, вероятно, будет быстрее, поскольку вы не используете задания Burst, которые оптимизируют шум Unity.Mat Mathematics.   -  person KdotJPG    schedule 20.08.2020
comment
Ого, я не видел, сколько лет было этому посту! Я думаю, что это появилось в моих последних результатах поиска, потому что есть недавний ответ. Надеюсь, мой комментарий будет кому-то полезен!   -  person KdotJPG    schedule 20.08.2020


Ответы (2)


Ага. В статье используется очень сложный метод.

Наилучший способ сделать это - взять функцию, которая представляет форму вашего базового острова со значениями высоты от 0 до 1. Для типа острова на картинке вам в основном нужно что-то, что плавно поднимается с краев, и плавно возвращается к нулю там, где вы хотите озер.

Теперь вы либо добавляете эту поверхность к своей базовой фрактальной поверхности (если вы хотите сохранить заостренность на малых высотах), либо умножаете ее (если хотите, чтобы более низкие высоты были плавными). Затем вы определяете высоту, ниже которой находится вода.

Вот мой очень быстрый способ сделать это, визуализированный с помощью Terragen:

Ринг-Айленд - Быстрая и грязная попытка

Я использовал функцию, которая поднимается кольцом от края карты к середине к середине, а затем снова опускается, чтобы соответствовать форме, аналогичной фигуре из статьи. На практике вы можете использовать это только для получения формы острова, а затем вырезать участок местности, соответствующий контуру, и закопать все остальное.

Я использовал свой собственный генератор фрактальных ландшафтов, как описано здесь: https://fractal-landscapes.co.uk для основного фрактала.

Вот код C #, изменяющий ландшафт:

public void MakeRingIsland()
{
    this.Normalize(32768);
    var ld2 = (double) linearDimension / 2;
    var ld4 = 4 / (double) linearDimension;
    for (var y = 0u; y < linearDimension; y++)
    {
        var yMul = y * linearDimension;
        for (var x = 0u; x < linearDimension; x++)
        {
            var yCoord = (y - ld2) * ld4;
            var xCoord = (x - ld2) * ld4;
            var dist = Math.Sqrt(xCoord * xCoord + yCoord * yCoord);
            var htMul = dist > 2 ? 0 : 
                (dist < 1 ? 
                    dist + dist - dist * dist : 
                    1 - (dist - 1) * (dist - 1));
            var height = samples[x + yMul];
            samples[x + yMul] = (int) (height + htMul * 32768);
        }
    }
}
person Adam Brown    schedule 20.08.2020
comment
Работает хорошо! Обратите внимание, что я бы, вероятно, избегал Math.sqrt в функции расстояния и просто использовал квадрат расстояния. Разделите его на ширину или высоту, чтобы масштабировать до того же приблизительного диапазона. Sqrt может вызвать острую точку посередине, а также приведет к дополнительным вычислительным затратам, которые могут вам не понадобиться. - person KdotJPG; 20.08.2020
comment
Sqrt нужен для плавного кусочного подъема и падения. По сути, это складывание двух квадратиков спиной к спине, чтобы получить форму пончика. Конечно, вы можете избежать этого, используя другую базовую функцию, и вы можете создать таблицу поиска, чтобы избежать этого для более чем 1/8 точек. Кроме того, для ясности код максимально прост - я бы не стал писать так или использовать именно этот метод. С этим есть проблемы. - person Adam Brown; 20.08.2020
comment
Для реального метода, который делает то же самое, вы можете использовать изолинию высотного контура для определения формы острова (в основном использовать этот метод для береговой линии), но затем определить функцию умножения, которая для моря равна нулю или отрицательна. , и масштабируется в соответствии с расстоянием от берега до берега. Это позволит избежать бочкообразной формы, которую вы получаете от пончика. - person Adam Brown; 20.08.2020
comment
Извините, я должен уточнить, что я не ОП. Я нашел это только потому, что он появился в результатах поиска по темам, связанным с ландшафтом, отсортированным по новейшим. И теперь я понимаю смысл sqrt! Острие есть, но оно будет ниже уровня воды. Это может иметь значение в приложении, которое показывает подводный ландшафт, но не здесь, если диапазон шума не может вывести точку на поверхность. Визуализация Wolfram - person KdotJPG; 21.08.2020

изображение, которое вы показываете, взято из статьи описывая, как его генерировать

person Jordan Szubert    schedule 07.05.2015
comment
Я знаю, но это довольно сложный метод. Я больше думал о том, чтобы, возможно, использовать маску для основной формы. - person 4t0m1c; 07.05.2015
comment
К тому же это слишком интенсивно для мобильного устройства. - person 4t0m1c; 07.05.2015