Pull to refresh

Процедурная генерация текстур планет на основе алгоритма Diamond-Square, часть 1

Reading time14 min
Views42K
image

Доброго времени суток. Как со мной бывает, как только я разобрался в каком-то сложном для себя вопросе, я сразу хочу рассказать всем решение. Поэтому решил написать серию из двух статей по такой интересной теме, как процедурная генерация. А конкретнее, я буду рассказывать про генерацию текстур планет. В этот раз я подготовился основательнее и постараюсь сделать материал качественнее, чем в моем предыдущем посте «Простая система событий в Unity» (кстати, спасибо всем за ответные посты). Прежде чем продолжить, хочу обратить ваше внимание на несколько моментов:

1) Этот генератор не претендует на реалистичность, и писал я его для того, чтобы сгенерировать уникальные текстуры для сотни маленьких шариков, которые занимают 10% экрана и к тому же прикрыты облаками.
2) Чисто технический момент: я пишу на C# под Unity3d, так что думать о том, как выводить в изображение с приемлимой скоростью вам придется самим, для каждого языка и платформы свои способы.



Diamond-Square


Давным давно один хороший человек, Gavin S. P. Miller, описал алгоритм генерации 2D шума. А 4 года назад другой хороший человек, deNULL, написал подробную статью про этот и некоторые другие методы генерации. Прежде чем идти дальше, настоятельно рекомендую прочитать, алгоритм описан очень хорошо. Но, когда я стал писать код, возникло несколько технических проблем. Какие были проблемы и как их удалось решить расскажу по ходу статьи. Начнем с общей схемы действий:

image

Сначала, с использованием нашего алгоритма, генерируем «карту высот» — примерно то же самое, что мы видим на второй картинке. Так как значение компоненты цвета в Unity представляется дробным числом от 0 до 1, то моя реализация алгоритма заполняет поле значениями в этом диапазоне, но переделать алгоритм под другой диапазон не составит труда. Затем это значение в определенных соотношениях заносим в значения r,g,b цвета соответствующего пикселя. Приступим к первому пункту.

Генерируем карту высот


На этом месте вы, надеюсь, уже представляете себе общий принцип действия самого diamond-square, если нет, все-таки прочитайте статью, которую я указал выше. Я только опишу свою реализацию. Для начала сформируем двумерный массив, измерения которого должны быть равны 2^n + 1, для других размеров работать не будет). Я взял 2049х1025 (соотношение 2:1 лучше всего подходит для сферических планет в вакууме). Напишем методы Square и Diamond. Первый принимает координаты левого нижнего и правого верхнего углов квадрата и записывает значение в его центр. Второй — принимает значение точки, которую надо посчитать (т.е. середины сторон этого квадрата) и вычисляет ее на основе значений смежных углов квадрата, его центра и центра соседнего квадрата. Вот на этом месте будет интересная загвоздка, но сначала сами методы:

public static int ysize = 1025, xsize = ysize * 2 - 1;
public static float[,] heighmap = new float[xsize, ysize];
public static float roughness = 2f;      //Определяет разницу высот, чем больше, тем более неравномерная карта высот

public static void Square(int lx, int ly, int rx, int ry)
    {
        int l = (rx - lx) / 2;

        float a = heighmap[lx, ly];              //  B--------C
        float b = heighmap[lx, ry];              //  |        |
        float c = heighmap[rx, ry];              //  |   ce   |
        float d = heighmap[rx, ly];              //  |        |        
        int cex = lx + l;                        //  A--------D
        int cey = ly + l;

        heighmap[cex, cey] = (a + b + c + d) / 4 + Random.Range(-l * 2 * roughness / ysize, l * 2 * roughness / ysize);
    }

Важное замечание: Random.Range(float min, float max) возвращает псевдослучайное число в указанном диапазоне. Он быстр, но есть только в Unity. Как будет с System.Random не знаю. Я к тому, что возможно вам придется самим писать генератор псевдослучайных чисел.

Едем дальше:

bool lrflag = false;
public static void Diamond(int tgx, int tgy, int l)
    {
        float a, b, c, d;

        if (tgy - l >= 0)
            a = heighmap[tgx, tgy - l];                        //      C--------
        else                                                   //      |        |
            a = heighmap[tgx, ysize - l];                      // B---t g----D  |
                                                               //      |        |
                                                               //      A--------
        if (tgx - l >= 0)
            b = heighmap[tgx - l, tgy];
        else
            if (lrflag)
                b = heighmap[xsize - l, tgy];
            else
                b = heighmap[ysize - l, tgy];


        if (tgy + l < ysize)
            c = heighmap[tgx, tgy + l];
        else
            c = heighmap[tgx, l];

        if (lrflag)
            if (tgx + l < xsize)
                d = heighmap[tgx + l, tgy];
            else<source lang="cs">

                d = heighmap[l, tgy];
        else
            if (tgx + l < ysize)
                d = heighmap[tgx + l, tgy];
            else
                d = heighmap[l, tgy];

        heighmap[tgx, tgy] = (a + b + c + d) / 4 + Random.Range(-l * 2 * roughness / ysize, l * 2 * roughness / ysize);
    }

Вот здесь остановимся подробнее. Как видите, для каждой точки я провожу проверку: не выходит ли она за границы массива. Если выходит, то я присваиваю ей значение противоположной. Т.е. если, например, мы вычисляем ромб в левой части, то абсцисса его левой вершины меньше нуля, вместо нее используем значение точки ей симметричной, т.е. xsize — l (l — половина стороны квадрата). Таким образом при наложении на сферу мы получим текстуру без шва. Кроме того, для координаты, которая может выйти за правую границу, проводятся дополнительная проверка. Дело в том, что diamond-square действует только для квадратов. Я делаю прямоугольник со сторонами 2:1 и считаю его как два квадрата. Поэтому я ввел флаг, определяющий, в какой части мы действуем и в соответствии с ним считал правой границей либо 1025 либо 2049 (ysize или xsize). Не очень изящное решение, зато дешево, надежно и практично, мне все равно не требуются другие соотношения, так что я оставил как есть.

Перепроверьте все координаты! Я из-за одной ошибки в параметре diamond'a целый день ломал голову перед этой, безусловно интересной математически, но совсем не в тему картины:
Спойлер
image

Наши инструменты почти готовы, остался последний небольшой метод, объединяющий их для одного квадрата.

public static void DiamondSquare(int lx, int ly, int rx, int ry)
    {
        int l = (rx - lx) / 2;

        Square(lx, ly, rx, ry);

        Diamond(lx, ly + l, l);
        Diamond(rx, ry - l, l);
        Diamond(rx - l, ry, l);
        Diamond(lx + l, ly, l);
    }

Обратите внимание, мы НЕ вызываем этот метод рекурсивно, объясню почему. Посмотрите на эту картинку:

image

Я пошагово нарисовал, что будет если рекурсивно вызывать Diamond-Square для под-квадратов. Первый квадрат считается нормально, т.к. вершины diamond'a выходят за пределы массива и вместо них используется центр квадрата. Но вот квадрат внутри него считается уже неправильно, т.к. середины квадратов — соседей еще не посчитаны. В результате ничего хорошего не получится. Таким образом, как и написано в той статье, считать надо слоями и рекурсия в данном случае не нужна. Через некоторое время после осознания этого факта мне удалось придумать этот обсчет по слоям:

public static void Generate()
    {
        heighmap[0, 0] = Random.Range(0.3f, 0.6f);
        heighmap[0, ysize - 1] = Random.Range(0.3f, 0.6f);
        heighmap[xsize - 1, ysize - 1] = Random.Range(0.3f, 0.6f);
        heighmap[xsize - 1, 0] = Random.Range(0.3f, 0.6f);

        heighmap[ysize - 1, ysize - 1] = Random.Range(0.3f, 0.6f);
        heighmap[ysize - 1, 0] = Random.Range(0.3f, 0.6f);

        for (int l = (ysize - 1) / 2; l > 0; l /= 2)
            for (int x = 0; x < xsize - 1; x += l)
            {
                if (x >= ysize - l)
                    lrflag = true;
                else
                    lrflag = false;

                for (int y = 0; y < ysize - 1; y += l)
                    DiamondSquare(x, y, x + l, y + l);
            }
    }

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

Вуаля!

image

Ну а теперь самое интересное — цвет.

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

Я решил разделить всю планету на три пояса: снег, зеленая зона, пустынная зона. Помните в учебниках географии картинки с ними? Вот сейчас их и будем делать. Для этого я создал отдельный класс. Он небольшой, выкладываю сразу весь:

public static class TemperatureCurves_class
{
    static int xsize = Heighmap_class.xsize;
    public static int snowEdge = Heighmap_class.ysize / 10;
    public static int greenEdge = Heighmap_class.ysize / 3;

    public static int[] northGreen = new int[xsize];
    public static int[] southGreen = new int[xsize];

    public static int[] northSnow = new int[xsize];
    public static int[] southSnow = new int[xsize];

    static float snowRoughness = 0.03f;
    static float greenRoughness = 0.15f;

    static void MidPointDisplacement1D(ref int[] curve, int l, int r, float roughness)
    {
        if (r - l > 1)
        {
            curve[(l + r) / 2] = (curve[l] + curve[r]) / 2 + (int)Random.Range(-(r - l) * roughness, (r - l) * roughness);
            MidPointDisplacement1D(ref curve, l, (l + r) / 2, roughness);
            MidPointDisplacement1D(ref curve, (l + r) / 2, r, roughness);
        }
    }

    public static void Generate()
    {
        northSnow[0] = northSnow[xsize - 1] = Heighmap_class.ysize - snowEdge;
        southSnow[0] = southSnow[xsize - 1] = snowEdge;

        northGreen[0] = northGreen[xsize - 1] = Heighmap_class.ysize - greenEdge;
        southGreen[0] = southGreen[xsize - 1] = greenEdge
        
        MidPointDisplacement1D(ref northGreen, 0, xsize - 1, greenRoughness);
        MidPointDisplacement1D(ref southGreen, 0, xsize - 1, greenRoughness);
        MidPointDisplacement1D(ref northSnow, 0, xsize - 1, snowRoughness);
        MidPointDisplacement1D(ref southSnow, 0, xsize - 1, snowRoughness);
    }
}

Итак, как следует из названия, в этом классе лежат границы климатических поясов. Они в виде массивов — две южные, две северные. А генерируем мы их с помощью предка diamond-square: midpoint displacement. В нашем случае мы его используем на прямой. Думаю, принцип его действия после diamond-square объяснять не надо, тем более deNULL давно уже за меня это сделал. Фактически, там лежит набор координат y, сопоставленных координате х, обозначающих границу раздела поясов. Чтобы пояс был замкнутым (мы ведь все это потом на сферу натягивать будем), его края мы делаем равными. А для реализма у снега и для зеленой полосы делаем разные значения roughness так, чтобы полярный круг был кругом (прямой на текстуре), а пояса могли извиваться по самым непредсказуемым траекториям (но важно не переборщить, пустыня на границе снега будет смотреться странно). Впрочем, пустыни все равно будут смотреться странно, т.к. они определяются не только близостью к экватору, но и горами, ветрами и прочими факторами.

Работаем с цветом
Самая громоздкая и велосипедная часть кода, приготовьтесь.
Во-первых, создадим массив цветов (в юнити есть метод, быстро считывающий такой массив и записывающий его элементы в пиксели текстуры, это намного быстрее, чем всякие SetPixel() и прочие):

public Texture2D tex; //Ссылка на текстуру, в которую записываем результат
public static Color[] colors = new Color[Heighmap_class.xsize * Heighmap_class.ysize];

float waterLevel = 0.2f;

Кстати говоря, как писал deNULL, полезно возвести значения карты высот в квадрат, это немного сгладит ее и сделает более похожей на реальную.

void SqrHeighmap()
{
     for (int x = 0; x < Heighmap_class.xsize; x++)
         for (int y = 0; y < Heighmap_class.ysize; y++)
             Heighmap_class.heighmap[x, y] *= Heighmap_class.heighmap[x, y];
}

Кроме того, на готовую текстуру накладываю такой эффект:

void SmoothImg()
    {
        for (int i = 0; i < Heighmap_class.xsize * Heighmap_class.ysize - 2; i++)
        {
            Color clr1 = colors[i];
            Color clr2 = colors[i + 1];
            colors[i] = (2 * clr1 + clr2) / 3;
            colors[i + 1] = (clr1 + 2 * clr2) / 3;
        }
    }

Эффект небольшой, но и на скорость вроде не особо влияет.
А теперь пишем методы для задания цветов разным типам местности:

void SetSnow(int counter, float heigh)
    {
        if (heigh < waterLevel + Random.Range(-0.04f, 0.04f))
            colors[counter] = new Color(Random.Range(0.8f, 0.85f), Random.Range(0.8f, 0.85f), Random.Range(0.85f, 0.9f));
        else
        {
            colors[counter].r = 0.005f / heigh + Random.Range(0.8f, 0.85f);
            colors[counter].g = 0.005f / heigh + Random.Range(0.8f, 0.85f);
            colors[counter].b = 0.01f / heigh + Random.Range(0.8f, 0.85f);
        }
    }

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

void SetOcean(int counter, float heigh)
    {
        colors[counter].r = heigh / 5;
        colors[counter].g = heigh / 5;
        colors[counter].b = 0.2f + heigh / 2 + Random.Range(-0.02f, 0.02f);
    }

Ну тут все понятно, океан он и на других планетах океан.

void SetGreen(int counter, float heigh)
    {
        colors[counter].g = 0.1f / heigh + 0.05f + Random.Range(-0.04f, 0.04f);
        if (heigh < waterLevel + 0.1f)
            colors[counter].g -= (waterLevel + 0.1f - heigh);

        if (colors[counter].g > 0.5f)
            colors[counter].g = 0.5f / heigh + 0.05f + Random.Range(-0.04f, 0.04f);

        colors[counter].r = 0;
        colors[counter].b = 0;
    }

Меняем цвет обратно пропорционально высоте — чем выше, тем темнее. Кроме того, чтобы края материков не были засвеченными, у кромки воды уменьшаем его немного.

void SetDesert(int counter, float heigh)
    {
        colors[counter].r = Random.Range(0.6f, 0.75f) + heigh / 2 - 0.35f;
        colors[counter].g = Random.Range(0.6f, 0.75f) + heigh / 2 - 0.35f;
        colors[counter].b = Random.Range(0.2f, 0.3f) + heigh / 2 - 0.35f;
    }

Тут все понятно, и еще один тип местности:

void SetMountains(int counter, float heigh)
    {
        float rnd = Random.Range(-0.03f, 0.03f);
        if (heigh > 1.1f)
            heigh = 1.1f + Random.Range(-0.05f, 0.05f);

        colors[counter].r = heigh * heigh / 2 + rnd - 0.1f;
        colors[counter].g = heigh * heigh / 2 + rnd - 0.1f;
        colors[counter].b = heigh * heigh / 2 + rnd - 0.05f;
    }

Горы, при этом избегаем засвета (отдельный вопрос, почему вообще появляются значения больше 1.0f, но юнити вроде ничего против не имеет).

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

bool ChechInRange(int value, int min, int max, int rand)
    {
        return ((value > min + rand) && (value < max + rand));
    }

А теперь самое мясо:

void Awake()
    {
        Heighmap_class.Generate();
        TemperatureCurves_class.Generate();
        tex.Resize(Heighmap_class.xsize, Heighmap_class.ysize);
        tex.Apply();

        SqrHeighmap();

        int counter = 0;
        for (int y = 0; y < Heighmap_class.ysize; y++)
            for (int x = 0; x < Heighmap_class.xsize; x++)
            {
                float heigh = Heighmap_class.heighmap[x, y];
                if (heigh < waterLevel)
                    SetOcean(counter, heigh);
                else
                {
                    if (ChechInRange(y, TemperatureCurves_class.southSnow[x], TemperatureCurves_class.northSnow[x], Random.Range(-10, 10)))
                        if (ChechInRange(y, TemperatureCurves_class.southGreen[x], TemperatureCurves_class.northGreen[x], Random.Range(-10, 10)))
                        {
                            SetDesert(counter, heigh);
                            if (heigh < waterLevel + 0.1f + Random.Range(-0.05f, 0.05f))
                                SetGreen(counter, heigh);
                        }
                        else
                            SetGreen(counter, heigh);

                    if (heigh > 0.82f + Random.Range(-0.05f, 0.05f))
                        SetMountains(counter, heigh);
                }
                if ((y < TemperatureCurves_class.southSnow[x] + Random.Range(-10, 10)) || (y > TemperatureCurves_class.northSnow[x] + Random.Range(-10, 10)))
                    SetSnow(counter, heigh);

                counter++;
            }

        SmoothImg();
        tex.SetPixels(colors);
        tex.Apply();
    }

Попробуем словами:
Генерируем карту высот, генерируем границы поясов, инициализируем текстуру, возводим в квадрат.
В цикле:
Если высота меньше уровня океана, заполнить пиксель океаном.
В противном случае, проверять на принадлежность зеленой полосе, внутри проверяем принадлежность пустыни, если да, то делаем пустыню, если нет — то зелень.
Поверх этого всего ставим горы, если вдруг высота стала высокой.
И еще выше, если попали за полярный круг, то зовем белых ходоков снег.
Ну и после цикла сглаживаем картинку и загружаем в текстуру.

Вот такой генератор получился у меня. А какой у вас?
image

P.S. Исходники:
Демка
Проект unity
Текстом
using UnityEngine;
using System.Collections;

public class Heighmap_class
{
    public static int ysize = 1025, xsize = ysize * 2 - 1;
    public static float[,] heighmap = new float[xsize, ysize];
    public static float roughness = 2f;      //Определяет разницу высот, чем больше, тем более неравномерная карта высот

    public static void Square(int lx, int ly, int rx, int ry)
    {
        int l = (rx - lx) / 2;

        float a = heighmap[lx, ly];              //  B--------C
        float b = heighmap[lx, ry];              //  |        |
        float c = heighmap[rx, ry];              //  |   ce   |
        float d = heighmap[rx, ly];              //  |        |        
        int cex = lx + l;                        //  A--------D
        int cey = ly + l;

        heighmap[cex, cey] = (a + b + c + d) / 4 + Random.Range(-l * 2 * roughness / ysize, l * 2 * roughness / ysize);
    }

    static bool lrflag = false;
    public static void Diamond(int tgx, int tgy, int l)
    {
        float a, b, c, d;

        if (tgy - l >= 0)
            a = heighmap[tgx, tgy - l];                        //      C--------
        else                                                   //      |        |
            a = heighmap[tgx, ysize - l];                      // B---t g----D  |
                                                               //      |        |
                                                               //      A--------
        if (tgx - l >= 0)
            b = heighmap[tgx - l, tgy];
        else
            if (lrflag)
                b = heighmap[xsize - l, tgy];
            else
                b = heighmap[ysize - l, tgy];


        if (tgy + l < ysize)
            c = heighmap[tgx, tgy + l];
        else
            c = heighmap[tgx, l];

        if (lrflag)
            if (tgx + l < xsize)
                d = heighmap[tgx + l, tgy];
            else
                d = heighmap[l, tgy];
        else
            if (tgx + l < ysize)
                d = heighmap[tgx + l, tgy];
            else
                d = heighmap[l, tgy];

        heighmap[tgx, tgy] = (a + b + c + d) / 4 + Random.Range(-l * 2 * roughness / ysize, l * 2 * roughness / ysize);
    }

    public static void DiamondSquare(int lx, int ly, int rx, int ry)
    {
        int l = (rx - lx) / 2;

        Square(lx, ly, rx, ry);

        Diamond(lx, ly + l, l);
        Diamond(rx, ry - l, l);
        Diamond(rx - l, ry, l);
        Diamond(lx + l, ly, l);
    }


    public static void MidPointDisplacement(int lx, int ly, int rx, int ry)
    {
        int l = (rx - lx) / 2;
        if (l > 0)
        {
            float a = heighmap[lx, ly];              //  B--------C
            float b = heighmap[lx, ry];              //  |        |
            float c = heighmap[rx, ry];              //  |   ce   |
            float d = heighmap[rx, ly];              //  |        |
            //  A--------D
            int cex = lx + l;
            int cey = ly + l;

            heighmap[cex, cey] = (a + b + c + d) / 4 + Random.Range(-l * 2 * roughness / xsize, l * 2 * roughness / xsize);

            heighmap[lx, cey] = (a + b) / 2 + Random.Range(-l * 2 * roughness / xsize, l * 2 * roughness / xsize);
            heighmap[rx, cey] = (c + d) / 2 + Random.Range(-l * 2 * roughness / xsize, l * 2 * roughness / xsize);
            heighmap[cex, ly] = (a + d) / 2 + Random.Range(-l * 2 * roughness / xsize, l * 2 * roughness / xsize);
            heighmap[cex, ry] = (b + c) / 2 + Random.Range(-l * 2 * roughness / xsize, l * 2 * roughness / xsize);

            MidPointDisplacement(lx, ly, cex, cey);
            MidPointDisplacement(lx, ly + l, lx + l, ry);
            MidPointDisplacement(cex, cey, rx, ry);
            MidPointDisplacement(lx + l, ly, rx, cey);
        }
    }


    public static void Generate()
    {
        heighmap[0, 0] = Random.Range(0.3f, 0.6f);
        heighmap[0, ysize - 1] = Random.Range(0.3f, 0.6f);
        heighmap[xsize - 1, ysize - 1] = Random.Range(0.3f, 0.6f);
        heighmap[xsize - 1, 0] = Random.Range(0.3f, 0.6f);

        heighmap[ysize - 1, ysize - 1] = Random.Range(0.3f, 0.6f);
        heighmap[ysize - 1, 0] = Random.Range(0.3f, 0.6f);

        for (int l = (ysize - 1) / 2; l > 0; l /= 2)
            for (int x = 0; x < xsize - 1; x += l)
            {
                if (x >= ysize - l)
                    lrflag = true;
                else
                    lrflag = false;

                for (int y = 0; y < ysize - 1; y += l)
                    DiamondSquare(x, y, x + l, y + l);
            }
    }
}

using UnityEngine;
using System.Collections;

public static class TemperatureCurves_class
{
    static int xsize = Heighmap_class.xsize;
    public static int snowEdge = Heighmap_class.ysize / 10;
    public static int greenEdge = Heighmap_class.ysize / 3;

    public static int[] northGreen = new int[xsize];
    public static int[] southGreen = new int[xsize];

    public static int[] northSnow = new int[xsize];
    public static int[] southSnow = new int[xsize];

    static float snowRoughness = 0.03f;
    static float greenRoughness = 0.15f;

    static void MidPointDisplacement1D(ref int[] curve, int l, int r, float roughness)
    {
        if (r - l > 1)
        {
            curve[(l + r) / 2] = (curve[l] + curve[r]) / 2 + (int)Random.Range(-(r - l) * roughness, (r - l) * roughness);
            MidPointDisplacement1D(ref curve, l, (l + r) / 2, roughness);
            MidPointDisplacement1D(ref curve, (l + r) / 2, r, roughness);
        }
    }

    public static void Generate()
    {
        northSnow[0] = northSnow[xsize - 1] = Heighmap_class.ysize - snowEdge;
        southSnow[0] = southSnow[xsize - 1] = snowEdge;

        northGreen[0] = northGreen[xsize - 1] = Heighmap_class.ysize - greenEdge;
        southGreen[0] = southGreen[xsize - 1] = greenEdge + Random.Range(-100, 100);
        
        MidPointDisplacement1D(ref northGreen, 0, xsize - 1, greenRoughness);
        MidPointDisplacement1D(ref southGreen, 0, xsize - 1, greenRoughness);
        MidPointDisplacement1D(ref northSnow, 0, xsize - 1, snowRoughness);
        MidPointDisplacement1D(ref southSnow, 0, xsize - 1, snowRoughness);
    }

}

using UnityEngine;
using System.Collections;

public class Renderer_script : MonoBehaviour
{
    public Texture2D tex;
    public static Color[] colors = new Color[Heighmap_class.xsize * Heighmap_class.ysize];

    float waterLevel = 0.2f;

    void SqrHeighmap()
    {
        for (int x = 0; x < Heighmap_class.xsize; x++)
            for (int y = 0; y < Heighmap_class.ysize; y++)
                Heighmap_class.heighmap[x, y] *= Heighmap_class.heighmap[x, y];
    }
    void SetSnow(int counter, float heigh)
    {
        if (heigh < waterLevel + Random.Range(-0.04f, 0.04f))
            colors[counter] = new Color(Random.Range(0.8f, 0.85f), Random.Range(0.8f, 0.85f), Random.Range(0.85f, 0.9f));
        else
        {
            colors[counter].r = 0.005f / heigh + Random.Range(0.8f, 0.85f);
            colors[counter].g = 0.005f / heigh + Random.Range(0.8f, 0.85f);
            colors[counter].b = 0.01f / heigh + Random.Range(0.8f, 0.85f);
        }
    }
    void SetOcean(int counter, float heigh)
    {
        colors[counter].r = heigh / 5;
        colors[counter].g = heigh / 5;
        colors[counter].b = 0.2f + heigh / 2 + Random.Range(-0.02f, 0.02f);
    }
    void SetGreen(int counter, float heigh)
    {
        colors[counter].g = 0.1f / heigh + 0.05f + Random.Range(-0.04f, 0.04f);
        if (heigh < waterLevel + 0.1f)
            colors[counter].g -= (waterLevel + 0.1f - heigh);

        if (colors[counter].g > 0.5f)
            colors[counter].g = 0.5f / heigh + 0.05f + Random.Range(-0.04f, 0.04f);

        colors[counter].r = 0;
        colors[counter].b = 0;
    }
    void SetDesert(int counter, float heigh)
    {
        colors[counter].r = Random.Range(0.6f, 0.75f) + heigh / 2 - 0.35f;
        colors[counter].g = Random.Range(0.6f, 0.75f) + heigh / 2 - 0.35f;
        colors[counter].b = Random.Range(0.2f, 0.3f) + heigh / 2 - 0.35f;
    }
    void SetMountains(int counter, float heigh)
    {
        float rnd = Random.Range(-0.03f, 0.03f);
        if (heigh > 1.1f)
            heigh = 1.1f + Random.Range(-0.05f, 0.05f);

        colors[counter].r = heigh * heigh / 2 + rnd - 0.1f;
        colors[counter].g = heigh * heigh / 2 + rnd - 0.1f;
        colors[counter].b = heigh * heigh / 2 + rnd - 0.05f;
    }

    void SmoothImg()
    {
        for (int i = 0; i < Heighmap_class.xsize * Heighmap_class.ysize - 2; i++)
        {
            Color clr1 = colors[i];
            Color clr2 = colors[i + 1];
            colors[i] = (2 * clr1 + clr2) / 3;
            colors[i + 1] = (clr1 + 2 * clr2) / 3;
        }
    }

    bool ChechInRange(int value, int min, int max, int rand)
    {
        return ((value > min + rand) && (value < max + rand));
    }

    void Awake()
    {
        Heighmap_class.Generate();
        TemperatureCurves_class.Generate();
        tex.Resize(Heighmap_class.xsize, Heighmap_class.ysize);
        tex.Apply();

        SqrHeighmap();

        int counter = 0;
        for (int y = 0; y < Heighmap_class.ysize; y++)
            for (int x = 0; x < Heighmap_class.xsize; x++)
            {
                float heigh = Heighmap_class.heighmap[x, y];
                if (heigh < waterLevel)
                    SetOcean(counter, heigh);
                else
                {
                    if (ChechInRange(y, TemperatureCurves_class.southSnow[x], TemperatureCurves_class.northSnow[x], Random.Range(-10, 10)))
                        if (ChechInRange(y, TemperatureCurves_class.southGreen[x], TemperatureCurves_class.northGreen[x], Random.Range(-10, 10)))
                        {
                            SetDesert(counter, heigh);
                            if (heigh < waterLevel + 0.1f + Random.Range(-0.05f, 0.05f))
                                SetGreen(counter, heigh);
                        }
                        else
                            SetGreen(counter, heigh);

                    if (heigh > 0.82f + Random.Range(-0.05f, 0.05f))
                        SetMountains(counter, heigh);
                }
                if ((y < TemperatureCurves_class.southSnow[x] + Random.Range(-10, 10)) || (y > TemperatureCurves_class.northSnow[x] + Random.Range(-10, 10)))
                    SetSnow(counter, heigh);

                counter++;
            }

        SmoothImg();
        tex.SetPixels(colors);
        tex.Apply();
    }
}


Tags:
Hubs:
Total votes 34: ↑31 and ↓3+28
Comments21

Articles