артефакты во время генерации карты высот с использованием фрактала в стиле плазмы

Сегодня я провел несколько часов, исследуя, как обычно происходит генерация случайного ландшафта, и после прочтения о плазменном фрактале (смещение средней точки и алгоритм ромбовидного квадрата) я решил попробовать реализовать его. Мой результат был на самом деле не ужасным, но у меня есть эти ужасные артефакты в виде квадрата/линии/сетки, от которых я никак не могу избавиться!

При отображении в виде изображения в градациях серого моя карта высот выглядит примерно так: карта высот http://sphotos-d.ak.fbcdn.net/hphotos-ak-ash3/535816_10151739010123327_225111175_n.jpg

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

Я начинаю с установки четырех углов карты на случайные значения от 0 до 1, а затем запускаю алгоритм рекурсивного смещения:

    public void GenerateTerrainLayer()
    {  
        //set the four corners of the map to have random values
        TerrainData[0, 0] = (float)RandomGenerator.NextDouble();
        TerrainData[GenSize, 0] = (float)RandomGenerator.NextDouble();
        TerrainData[0, GenSize] = (float)RandomGenerator.NextDouble();
        TerrainData[GenSize, GenSize] = (float)RandomGenerator.NextDouble();

        //begin midpoint displacement algorithm...
        MidPointDisplace(new Vector2_I(0, 0), new Vector2_I(GenSize, 0), new Vector2_I(0, GenSize), new Vector2_I(GenSize, GenSize));
    }

TerrainData — это просто массив 2D-поплавков*. Vector2_I — это просто мой собственный целочисленный векторный класс. Последние четыре функции: MidPointDisplace, которая является рекурсивной функцией, CalculateTerrainPointData, которая усредняет 2 значения данных и добавляет немного шума, CalculateTerrainPointData2, которая усредняет 4 значения данных и добавляет немного шума и имеет немного более высокое значение масштаба (используется только для центральных точек) и, наконец, моя функция шума, которая представляет собой просто случайный шум, а не настоящий шум, такой как перлин и т. д. Они выглядят так:

   private void MidPointDisplace(Vector2_I topleft, Vector2_I topright, Vector2_I bottomleft, Vector2_I bottomright)
    {
        //check size of square working on.. if its shorter than a certain amount stop the algo, we've done enough
        if (topright.X - topleft.X < DisplacementMaxLOD)
        {
            return;
        }


        //calculate the positions of all the middle points for the square that has been passed to the function
        Vector2_I MidLeft, MidRight, MidTop, MidBottom, Center;

        MidLeft.X = topleft.X;
        MidLeft.Y = topleft.Y + ((bottomleft.Y - topleft.Y) / 2);

        MidRight.X = topright.X;
        MidRight.Y = topright.Y + ((bottomright.Y - topright.Y) / 2);

        MidTop.X = topleft.X + ((topright.X - topleft.X) / 2);
        MidTop.Y = topleft.Y;

        MidBottom.X = bottomleft.X + ((bottomright.X - bottomleft.X) / 2);
        MidBottom.Y = bottomleft.Y;

        Center.X = MidTop.X;
        Center.Y = MidLeft.Y;

        //collect the existing data from the corners of the area passed to algo
        float TopLeftDat, TopRightDat, BottomLeftDat, BottomRightDat;

        TopLeftDat = GetTerrainData(topleft.X, topleft.Y);          
        TopRightDat = GetTerrainData(topright.X, topright.Y);          
        BottomLeftDat = GetTerrainData(bottomleft.X, bottomleft.Y);          
        BottomRightDat = GetTerrainData(bottomright.X, bottomright.Y);

        //and the center


        //adverage data and insert for midpoints..
        SetTerrainData(MidLeft.X, MidLeft.Y, CalculateTerrainPointData(TopLeftDat, BottomLeftDat, MidLeft.X, MidLeft.Y));
        SetTerrainData(MidRight.X, MidRight.Y, CalculateTerrainPointData(TopRightDat, BottomRightDat, MidRight.X, MidRight.Y));
        SetTerrainData(MidTop.X, MidTop.Y, CalculateTerrainPointData(TopLeftDat, TopRightDat, MidTop.X, MidTop.Y));
        SetTerrainData(MidBottom.X, MidBottom.Y, CalculateTerrainPointData(BottomLeftDat, BottomRightDat, MidBottom.X, MidBottom.Y));
        SetTerrainData(Center.X, Center.Y, CalculateTerrainPointData2(TopLeftDat, TopRightDat, BottomLeftDat, BottomRightDat, Center.X, Center.Y));


        debug_displacement_iterations++;

        //and recursively fire off new calls to the function to do the smaller squares
        Rectangle NewTopLeft = new Rectangle(topleft.X, topleft.Y, Center.X - topleft.X, Center.Y - topleft.Y);
        Rectangle NewTopRight = new Rectangle(Center.X, topright.Y, topright.X - Center.X, Center.Y - topright.Y);
        Rectangle NewBottomLeft = new Rectangle(bottomleft.X, Center.Y, Center.X - bottomleft.X, bottomleft.Y - Center.Y);
        Rectangle NewBottomRight = new Rectangle(Center.X , Center.Y, bottomright.X - Center.X, bottomright.Y - Center.Y);

        MidPointDisplace(new Vector2_I(NewTopLeft.Left, NewTopLeft.Top), new Vector2_I(NewTopLeft.Right, NewTopLeft.Top), new Vector2_I(NewTopLeft.Left, NewTopLeft.Bottom), new Vector2_I(NewTopLeft.Right, NewTopLeft.Bottom));
        MidPointDisplace(new Vector2_I(NewTopRight.Left, NewTopRight.Top), new Vector2_I(NewTopRight.Right, NewTopRight.Top), new Vector2_I(NewTopRight.Left, NewTopRight.Bottom), new Vector2_I(NewTopRight.Right, NewTopRight.Bottom));
        MidPointDisplace(new Vector2_I(NewBottomLeft.Left, NewBottomLeft.Top), new Vector2_I(NewBottomLeft.Right, NewBottomLeft.Top), new Vector2_I(NewBottomLeft.Left, NewBottomLeft.Bottom), new Vector2_I(NewBottomLeft.Right, NewBottomLeft.Bottom));
        MidPointDisplace(new Vector2_I(NewBottomRight.Left, NewBottomRight.Top), new Vector2_I(NewBottomRight.Right, NewBottomRight.Top), new Vector2_I(NewBottomRight.Left, NewBottomRight.Bottom), new Vector2_I(NewBottomRight.Right, NewBottomRight.Bottom));

    }

    //helper function to return a data value adveraged from two inputs, noise value added for randomness and result clamped to ensure a good value
    private float CalculateTerrainPointData(float DataA, float DataB, int NoiseX, int NoiseY)
    {
         return MathHelper.Clamp(((DataA + DataB) / 2.0f) + NoiseFunction(NoiseX, NoiseY), 0.0f, 1.0f) * 1.0f;
    }

    //helper function to return a data value adveraged from four inputs, noise value added for randomness and result clamped to ensure a good value
    private float CalculateTerrainPointData2(float DataA, float DataB, float DataC, float DataD, int NoiseX, int NoiseY)
    {
        return MathHelper.Clamp(((DataA + DataB + DataC + DataD) / 4.0f) + NoiseFunction(NoiseX, NoiseY), 0.0f, 1.0f) * 1.5f;
    }

    private float NoiseFunction(int x, int y)
    {
        return (float)(RandomGenerator.NextDouble() - 0.5) * 0.5f;
    }

Хорошо, спасибо, что нашли время посмотреть - надеюсь, кто-то знает, откуда появляется этот сетчатый узор :)

*edit - случайно написал целые числа, исправил на числа с плавающей запятой


person David Burford    schedule 11.02.2013    source источник
comment
Может быть, это эффект из-за потери точности при округлении от float до int?   -  person thalador    schedule 12.04.2013


Ответы (3)


Я выявил 3 проблемы в вашем коде. (2 из которых связаны)

Вы не уменьшаете случайность на каждом шаге. На каждом шаге должно быть уменьшение случайности. В противном случае вы получите белый шум. Вы выбираете коэффициент (0,5-0,7 отлично подходит для моих целей) и умножаете сокращение на альфа в каждой рекурсии и масштабируете сгенерированное случайное число на этот коэффициент.

Вы поменяли местами ромбовидный и квадратный шаг. Сначала ромбы, потом квадраты. Обратный путь невозможен (см. далее).

Ваш квадратный шаг использует только точки в одном направлении. Это, вероятно, вызывает прямоугольные структуры, о которых вы говорите. Квадраты должны усреднять значения для всех четырех сторон. Это означает, что квадратный шаг зависит от точки, созданной ромбовидным шагом. И не только шаг ромба прямоугольника, на который вы сейчас смотрите, но и прямоугольников рядом с ним. Для значений вне карты вы можете использовать перенос, использовать фиксированное значение или усреднить только 3 значения.

person Cookiemon    schedule 23.04.2016

Я вижу проблему в вашей реализации CalculateTerrainPointData: вы не уменьшаете результат NoiseFunction с каждой итерацией.

См. это описание алгоритма смещения средней точки:

  • Start with a single horizontal line segment.
    • Repeat for a sufficiently large number of times:
      • Repeat over each line segment in the scene:
        • Find the midpoint of the line segment.
        • Сместите среднюю точку по Y на случайную величину.
        • Уменьшите диапазон случайных чисел.

Быстрый способ сделать это в вашем коде без особых изменений — добавить некоторый параметр scale к MidPointDisplace (по умолчанию установлено значение 1.0f) и CalculateTerrainPointData; используйте его в CalculateTerrainPointData, чтобы умножить результат NoiseFunction; и уменьшать его с каждым рекурсивным вызовом до MidPointDisplace(..., 0.5f * scale).

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

person Alexander Konstantinov    schedule 18.02.2013
comment
это значительно улучшило генерацию, карта стала намного более гладкой и выглядит почти правильно, но странная сетка все еще присутствует! Пример изображения - почти готов :) - person David Burford; 24.02.2013

Согласно обзору смещения средней точки в Википедии, получается только среднее значение для самой центральной точки. к нему добавлен шум - попробуйте добавить шум только через CalculateTerrainPointData2 и удалить шум через CalculateTerrainPointData.

person Pikalek    schedule 10.07.2013