Использование шума Перлина для создания двумерной тайловой карты

Я просмотрел весь Интернет и исследовал шум Перлина, однако я все еще в замешательстве.

Я использую java и libgdx. У меня есть класс Perlin для работы и генерации шума, но я не уверен, что значения, которые он дает, верны. Как проверить, действительно ли выводится шум Перлина?

Если моя реализация верна, я не знаю, куда идти, чтобы создать случайный ландшафт. Как мне сопоставить шум Перлина с тайлами? В настоящее время у меня есть 4 основных плитки; вода, песок, камни и трава.

package com.bracco.thrive.world;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
public class WorldGeneration {

Perlin noise = new Perlin();
private SpriteBatch spriteBatch;
//private boolean debug = false;
private TextureRegion[] regions = new TextureRegion[4];
private Texture texture;

 float x = 110;
 float y = 120;
 float originX = 0;
 float originY = 16;
 float width = 16;
 float height = 16;
 float scaleX = 1;
 float scaleY = 1;
 float rotation = 1;


@SuppressWarnings("static-access")
public void createWorld(){
    spriteBatch = new SpriteBatch();
     texture = new Texture(Gdx.files.internal("assets/data/textures/basictextures.png"));

     regions[0] = new TextureRegion(texture,0,0,16,16); //grass 
     regions[1] = new TextureRegion(texture,16,0,16,16); //water
     regions[2] = new TextureRegion(texture,0,17,16,16); //sand
     regions[3] = new TextureRegion(texture,17,17,16,16); //rock
    float[][] seed =  noise.GenerateWhiteNoise(50, 50);
    for (int i = 0;i < seed.length; i++){
        for ( int j = 0; j < seed[i].length; j++){
            System.out.println(seed[i][j] + " ");
        }
    }
     float[][] seedE = noise.GenerateSmoothNoise( seed, 6);
     for (int i = 0;i < seedE.length; i++){
            for ( int j = 0; j < seedE[i].length; j++){
                System.out.println(seedE[i][j] + " ");
            }

     }
     float[][] perlinNoise = noise.GeneratePerlinNoise(seedE, 8);
     for (int i = 0;i < perlinNoise.length; i++){
            for ( int j = 0; j < perlinNoise[i].length; j++){
                System.out.println(perlinNoise[i][j] + " ");
            }
        }
}

public void render(){
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    spriteBatch.begin();
    //spriteBatch.draw(texture, 0,  0,  16, 16);
    for (int i = 0; i < regions.length; i++){
        spriteBatch.draw(regions[i],75 * (i + 1),100);
    }
    spriteBatch.end();
}



}


package com.bracco.thrive.world;

    import java.util.Random;

    public class Perlin {

    public static float[][] GenerateWhiteNoise(int width,int height){

        Random random = new Random((long) (Math.round(Math.random() * 100 * Math.random() * 10))); //Seed to 0 for testing
        float[][] noise = new float[width][height];

        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++){
                noise[i][j] = (float)(Math.random() % 1);
            }
        }

        return noise;
    }

    float[][] GenerateSmoothNoise(float[][] baseNoise, int octave)
    {
       int width = baseNoise.length;
       int height = baseNoise.length;

       float[][] smoothNoise = new float[width][height];

       int samplePeriod = 1 << octave; // calculates 2 ^ k
       float sampleFrequency = 1.0f / samplePeriod;

       for (int i = 0; i < width; i++)
       {
          //calculate the horizontal sampling indices
          int sample_i0 = (i / samplePeriod) * samplePeriod;
          int sample_i1 = (sample_i0 + samplePeriod) % width; //wrap around
          float horizontal_blend = (i - sample_i0) * sampleFrequency;

          for (int j = 0; j < height; j++)
          {
             //calculate the vertical sampling indices
             int sample_j0 = (j / samplePeriod) * samplePeriod;
             int sample_j1 = (sample_j0 + samplePeriod) % height; //wrap around
             float vertical_blend = (j - sample_j0) * sampleFrequency;

             //blend the top two corners
             float top = Interpolate(baseNoise[sample_i0][sample_j0],
                baseNoise[sample_i1][sample_j0], horizontal_blend);

             //blend the bottom two corners
             float bottom = Interpolate(baseNoise[sample_i0][sample_j1],
                baseNoise[sample_i1][sample_j1], horizontal_blend);

             //final blend
             smoothNoise[i][j] = Interpolate(top, bottom, vertical_blend);
          }
       }

       return smoothNoise;
    }

    float Interpolate(float x0, float x1, float alpha)
    {
       return x0 * (1 - alpha) + alpha * x1;
    }

    float[][] GeneratePerlinNoise(float[][] baseNoise, int octaveCount)
    {
       int width = baseNoise.length;
       int height = baseNoise[0].length;

       float[][][] smoothNoise = new float[octaveCount][][]; //an array of 2D arrays containing

       float persistance = 0.5f;

       //generate smooth noise
       for (int i = 0; i < octaveCount; i++)
       {
           smoothNoise[i] = GenerateSmoothNoise(baseNoise, i);
       }

        float[][] perlinNoise = new float[width][height];
        float amplitude = 1.0f;
        float totalAmplitude = 0.0f;

        //blend noise together
        for (int octave = octaveCount - 1; octave >= 0; octave--)
        {
           amplitude *= persistance;
           totalAmplitude += amplitude;

           for (int i = 0; i < width; i++)
           {
              for (int j = 0; j < height; j++)
              {
                 perlinNoise[i][j] += smoothNoise[octave][i][j] * amplitude;
              }
           }
        }

       //normalisation
       for (int i = 0; i < width; i++)
       {
          for (int j = 0; j < height; j++)
          {
             perlinNoise[i][j] /= totalAmplitude;
          }
       }

       return perlinNoise;
    }
}

person user2489897    schedule 03.07.2013    source источник
comment
Вы столкнулись с ошибкой? В чем ошибка? Или это просто не работает, как вы хотели? Вместо этого попробуйте сделать скриншот того, что вы хотите и чего вы достигли.   -  person Dariusz    schedule 03.07.2013
comment
Я предполагаю, что ваш вопрос заключается в том, что вы хотите, чтобы какой-то пиксельный шейдер накладывал шум на ваши базовые текстуры - я советую вам переформулировать свой вопрос, чтобы быть более конкретным. Я мало что знаю о пиксельных шейдерах, но без них я бы предположил, что вам нужно создать новую текстуру с вашими зашумленными тайлами.   -  person kutschkem    schedule 03.07.2013
comment
Я не вижу места в вашем коде, где вы на самом деле пытаетесь использовать свой шум.   -  person Jyro117    schedule 04.07.2013
comment
Извините, я просто запутался, моя цель - сгенерировать перлиновый шум, а затем использовать его для рендеринга случайных 2D-карт тайлов. Я потерян в основном.   -  person user2489897    schedule 05.07.2013
comment
@user2489897 user2489897 В чем именно ваша проблема; что шум перлина может быть неправильным, или что вы не знаете, как преобразовать шум перлина в тайлы? Если вы не уверены, что шум перлина правильный, используйте его для создания изображения в оттенках серого (я могу опубликовать код для достижения этого, если это ваша проблема) и посмотрите на него. Это должно выглядеть так, будто холмы/горы смотрят с воздуха.   -  person Richard Tingle    schedule 05.07.2013
comment
p.s. если вы решите ответить мне, не забудьте указать @RichardTingle в своем комментарии, чтобы StackOverflow сообщил мне, что вы ответили   -  person Richard Tingle    schedule 05.07.2013
comment
На самом деле это было проще найти, чем я себе представлял, поэтому я разместил свой код   -  person Richard Tingle    schedule 05.07.2013


Ответы (2)


Правильность шума перлина
Относительно того, является ли ваш шум перлина «правильным»; самый простой способ проверить, работает ли ваш шум перлина (или технически фрактальный шум, основанный на нескольких октавах шума перлина), — это использовать значения вашего шума перлина для создания изображения в оттенках серого, это изображение должно выглядеть как какой-то пейзаж (холмы или горы в зависимости от параметров, которые вы выбрали для постоянства (и, в меньшей степени, количества октав). Некоторые примеры шума перлина:

Низкая устойчивость:
Постоянство 0,5

or

Высокая устойчивость:
Постоянство 0,7

or

Высокая устойчивость (уменьшено):
введите здесь описание изображения

Эти изображения в оттенках серого создаются с помощью следующего кода

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class ImageWriter {
    //just convinence methods for debug

    public static void greyWriteImage(double[][] data){
        //this takes and array of doubles between 0 and 1 and generates a grey scale image from them

        BufferedImage image = new BufferedImage(data.length,data[0].length, BufferedImage.TYPE_INT_RGB);

        for (int y = 0; y < data[0].length; y++)
        {
          for (int x = 0; x < data.length; x++)
          {
            if (data[x][y]>1){
                data[x][y]=1;
            }
            if (data[x][y]<0){
                data[x][y]=0;
            }
              Color col=new Color((float)data[x][y],(float)data[x][y],(float)data[x][y]); 
            image.setRGB(x, y, col.getRGB());
          }
        }

        try {
            // retrieve image
            File outputfile = new File("saved.png");
            outputfile.createNewFile();

            ImageIO.write(image, "png", outputfile);
        } catch (IOException e) {
            //o no!
        }
    }


    public static void main(String args[]){
        double[][] data=new double[2][4];
        data[0][0]=0.5;
        data[0][5]=1;
        data[1][0]=0.7;
        data[1][6]=1;

        greyWriteImage(data);
    }
}

Этот код предполагает, что каждая запись будет между 0 и 1, но шум перлина обычно производит от -1 до 1, масштабируется в соответствии с вашими требованиями. Предполагая, что ваш шум Perlin даст значение для любого x, y, вы можете запустить его, используя следующий код

    //generates 100 by 100 data points within the specified range

    double iStart=0;
    double iEnd=500;
    double jStart=0;
    double jEnd=500;

    double[][] result=new double[100][100];

    for(int i=0;i<100;i++){
        for(int j=0;j<100;j++){
            int x=(int)(iStart+i*((iEnd-iStart)/100));
            int y=(int)(jStart+j*((jEnd-jStart)/100));
            result[i][j]=0.5*(1+perlinNoise.getNoise(x,y));
        }
    }

    ImageWriter.greyWriteImage(result);

Моя реализация ожидает целых чисел x и y. Не стесняйтесь изменять, если это не так для вас

Сопоставление с плитками
Это полностью зависит от вас, вам нужно определить определенные диапазоны значения шума перлина для создания определенных плиток. Имейте в виду, однако, что шум перлина смещен в сторону 0. Предполагая 2D, вы могли бы получить хорошие результаты, восприняв аналогию с ландшафтом полубуквально, низкие значения = вода, низкие значения = песок, средние значения = трава, высокие значения = снег.

Также имейте в виду, что в некоторых реализациях (например, в биомах и пещерах Minecraft) несколько случайных значений объединяются для создания общего результата. См. https://softwareengineering.stackexchange.com/questions/202992/randomization-of-biomes/203040#203040

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

person Richard Tingle    schedule 05.07.2013

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

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

После дополнительных исследований я нашел вашу реализацию SmoothNoise очень хитрой, поэтому я повторно реализовал ее из надежного источника (http://lodev.org/cgtutor/randomnoise.html).

Вот мой класс шума, он может генерировать и работать с любым шумом:

package com.heresysoft.arsenal.utils;

public class Noise
{

    public static double[] blend(double[] noise1, double[] noise2, double persistence)
    {
        if (noise1 != null && noise2 != null && noise1.length > 0 && noise1.length == noise2.length)
        {
            double[] result = new double[noise1.length];
            for (int i = 0; i < noise1.length; i++)
                result[i] = noise1[i] + (noise2[i] * persistence);
            return result;
        }

        return null;
    }

    public static double[] normalize(double[] noise)
    {
        if (noise != null && noise.length > 0)
        {
            double[] result = new double[noise.length];

            double minValue = noise[0];
            double maxValue = noise[0];
            for (int i = 0; i < noise.length; i++)
            {
                if (noise[i] < minValue)
                    minValue = noise[i];
                else if (noise[i] > maxValue)
                    maxValue = noise[i];
            }

            for (int i = 0; i < noise.length; i++)
                result[i] = (noise[i] - minValue) / (maxValue - minValue);

            return result;
        }

        return null;
    }

    public static double[] perlinNoise(int width, int height, double exponent)
    {
        int[] p = new int[width * height];
        double[] result = new double[width * height];
        /*final int[] permutation = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23,
                                   190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
                                   20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230,
                                   220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169,
                                   200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147,
                                   118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44,
                                   154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104,
                                   218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192,
                                   214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24,
                                   72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180};*/

        for (int i = 0; i < p.length / 2; i++)
            p[i] = p[i + p.length / 2] = (int) (Math.random() * p.length / 2);//permutation[i];

        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                double x = i * exponent / width;                                // FIND RELATIVE X,Y,Z
                double y = j * exponent / height;                                // OF POINT IN CUBE.
                int X = (int) Math.floor(x) & 255;                  // FIND UNIT CUBE THAT
                int Y = (int) Math.floor(y) & 255;                  // CONTAINS POINT.
                int Z = 0;
                x -= Math.floor(x);                                // FIND RELATIVE X,Y,Z
                y -= Math.floor(y);                                // OF POINT IN CUBE.
                double u = fade(x);                                // COMPUTE FADE CURVES
                double v = fade(y);                                // FOR EACH OF X,Y,Z.
                double w = fade(Z);
                int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z,      // HASH COORDINATES OF
                        B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z;      // THE 8 CUBE CORNERS,

                result[j + i * width] = lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, Z),  // AND ADD
                                                             grad(p[BA], x - 1, y, Z)), // BLENDED
                                                     lerp(u, grad(p[AB], x, y - 1, Z),  // RESULTS
                                                          grad(p[BB], x - 1, y - 1, Z))),// FROM  8
                                             lerp(v, lerp(u, grad(p[AA + 1], x, y, Z - 1),  // CORNERS
                                                          grad(p[BA + 1], x - 1, y, Z - 1)), // OF CUBE
                                                  lerp(u, grad(p[AB + 1], x, y - 1, Z - 1), grad(p[BB + 1], x - 1, y - 1, Z - 1))));
            }
        }
        return result;
    }

    public static double[] smoothNoise(int width, int height, double zoom)
    {
        if (zoom > 0)
        {
            double[] noise = whiteNoise(width, height);
            double[] result = new double[width * height];
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    double x = i / zoom;
                    double y = j / zoom;

                    // get fractional part of x and y
                    double fractX = x - (int) x;
                    double fractY = y - (int) y;

                    // wrap around
                    int x1 = ((int) x + width) % width;
                    int y1 = ((int) y + height) % height;

                    // neighbor values
                    int x2 = (x1 + width - 1) % width;
                    int y2 = (y1 + height - 1) % height;

                    // smooth the noise with bilinear interpolation
                    result[j + i * width] = fractX * fractY * noise[y1 + x1 * width]
                                            + fractX * (1 - fractY) * noise[y2 + x1 * width]
                                            + (1 - fractX) * fractY * noise[y1 + x2 * width]
                                            + (1 - fractX) * (1 - fractY) * noise[y2 + x2 * width];
                }
            }

            return result;
        }

        return null;
    }

    public static double[] turbulence(int width, int height, double zoom)
    {
        // http://lodev.org/cgtutor/randomnoise.html
        double[] result = new double[width * height];
        double initialZoom = zoom;

        while (zoom >= 1)
        {
            result = blend(result, smoothNoise(width, height, zoom), zoom);
            zoom /= 2.0;
        }

        for (int i = 0; i < result.length; i++)
            result[i] = (128.0 * result[i] / initialZoom);

        return result;
    }

    public static double[] whiteNoise(int width, int height)
    {
        double[] result = new double[width * height];
        for (int i = 0; i < width * height; i++)
            result[i] = Math.random();
        return result;
    }

    private static double fade(double t)
    {
        return t * t * t * (t * (t * 6 - 15) + 10);
    }

    private static double lerp(double t, double a, double b)
    {
        return a + t * (b - a);
    }

    private static double grad(int hash, double x, double y, double z)
    {
        int h = hash & 15;                      // CONVERT LO 4 BITS OF HASH CODE
        double u = h < 8 ? x : y,                 // INTO 12 GRADIENT DIRECTIONS.
                v = h < 4 ? y : h == 12 || h == 14 ? x : z;
        return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
    }

}

Вот пример использования функции smoothNoise:

        double[] data = Noise.normalize(Noise.smoothNoise(width, height, 32));

        for (int i = 0; i < data.length; i++)
            data[i] = 255*data[i];

        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
        img.getRaster().setPixels(0, 0, width, height, data);

Вот пример использования функции турбулентности:

        double[] data = Noise.normalize(Noise.turbulence(width, height, 32));

        for (int i = 0; i < data.length; i++)
            data[i] = 255*data[i];

        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
        img.getRaster().setPixels(0, 0, width, height, data);

Вот пример использования функции perlinNoise:

        double[] data = Noise.normalize(Noise.perlinNoise(width, height, 7));

        for (int i = 0; i < data.length; i++)
            data[i] = 255 * data[i];

        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
        img.getRaster().setPixels(0, 0, width, height, data);
person Malbeth    schedule 18.01.2014