java 使用柏林噪声生成二维瓦片地图

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17440865/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-01 01:59:21  来源:igfitidea点击:

Using Perlin noise to generate a 2d tile map

javalibgdxnoiseperlin-noiseprocedural

提问by user2489897

I looked all over the internet and researched Perlin noise, however, I am still confused.

我查看了整个互联网并研究了 Perlin 噪声,但是,我仍然感到困惑。

I am using java and libgdx. I have got a Perlin class to work and generate noise but I'm not sure if the values its giving are correct. How do I check it actually is outputting Perlin noise?

我正在使用 java 和libgdx。我有一个 Perlin 类可以工作并产生噪音,但我不确定它给出的值是否正确。我如何检查它实际上是在输出柏林噪声?

If my implementation is correct I don't know where to go from there to make random terrain. How would I map Perlin noise to tiles? Currently I have 4 basic tiles; water, sand, rock, and grass.

如果我的实现是正确的,我不知道从哪里开始制作随机地形。我如何将柏林噪声映射到瓷砖?目前我有 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;
    }
}

回答by Richard Tingle

Correctness of perlin noise
Regarding if your perlin noise is 'correct'; the easiest way to see if your perlin noise (or technically fractal noise based upon several octaves of perlin noise)is working is to use the values of your perlin noise to generate a greyscale image, this image should look like some kind of landscape (rolling hills, or mountains depending on the parameters you chose for the persistance (and to a less extent the number of octaves). Some examples of perlin noise is:

柏林噪声的正确性
关于你的柏林噪声是否“正确”;查看您的柏林噪声(或基于几个倍频程柏林噪声的技术上的分形噪声)是否有效的最简单方法是使用您的柏林噪声值生成灰度图像,该图像应该看起来像某种风景(滚动山丘或山脉取决于您为持久性选择的参数(以及在较小程度上八度音程的数量)。柏林噪声的一些示例是:

Low Persisance:
Persisance of 0.5

低持久性:
0.5 的持久性

or

或者

High Persisance:
Persisance of 0.7

高持久性:
0.7 的持久性

or

或者

High Persisance (zoomed out):
enter image description here

高持久性(缩小):
在此处输入图片说明

These greyscale images are produced by the following code

这些灰度图像由以下代码生成

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

This code assumes each entry will be between 0 and 1, but perlin noise usually produces between -1 and 1, scale according to your implimentation. Assuming your perlin noise will give a value for any x,y then you can run this using the following code

此代码假定每个条目都在 0 到 1 之间,但柏林噪声通常会产生在 -1 到 1 之间,根据您的实现进行缩放。假设您的柏林噪声将为任何 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);

My implimentation expects integer x and y. Feel free to modify if this is not the case for you

我的实现需要整数 x 和 y。如果您不属于这种情况,请随意修改

Mapping to tiles
This is entirely up to you, you need to define certain ranges of the perlin noise value to create certain tiles. Be aware however that perlin noise is biased towards 0. Assuming 2D you could get nice results by taking the landscape analogy semi literally, low values=water, lowish values=sand, medium values=grass, high values =snow.

映射到图块
这完全取决于您,您需要定义柏林噪声值的特定范围以创建特定图块。但是请注意,柏林噪声偏向于 0。假设 2D,您可以通过将景观类比半字面意思来获得不错的结果,低值 = 水,低值 = 沙,中等值 = 草,高值 = 雪。

Also be aware that in some implementations (eg minecraft biomes and caverns) several random values are combined to create an overall result. See https://softwareengineering.stackexchange.com/questions/202992/randomization-of-biomes/203040#203040

另请注意,在某些实现中(例如 Minecraft 生物群落和洞穴),多个随机值组合在一起以创建整体结果。见https://softwareengineering.stackexchange.com/questions/202992/randomization-of-biomes/203040#203040

Ideas for improvement
If you find that perlin noise generation is too slow then consider simplex noise, it has very similar properties but is more efficient (especially at higher dimentions). Simplex noise is however considerably more complex mathematically.

改进的想法
如果您发现柏林噪声生成速度太慢,请考虑单纯形噪声,它具有非常相似的特性,但效率更高(尤其是在更高维度上)。然而,单纯形噪声在数学上要复杂得多。

回答by Malbeth

I realize this is a somewhat old question, but I'd like to post my solution none the less since I found it hard to find working examples.

我意识到这是一个有点老的问题,但我仍然想发布我的解决方案,因为我发现很难找到工作示例。

I was also researching this problem, at first I found your code useful as it seamed to work, in appearance, but when I wanted to change the size of the image the smooth noise would not scale appropriately and I could not find a way to fix your code.

我也在研究这个问题,起初我发现你的代码很有用,因为它在外观上接缝工作,但是当我想改变图像的大小时,平滑噪声不会适当地缩放,我找不到解决方法你的代码。

After more research I found your implementation of SmoothNoise very dodgy and so I re-implemented it from a reliable source (http://lodev.org/cgtutor/randomnoise.html).

经过更多研究,我发现您对 SmoothNoise 的实现非常狡猾,因此我从可靠的来源 ( http://lodev.org/cgtutor/randomnoise.html)重新实现了它。

Here is my noise class, it can generate and work with any kind of noise :

这是我的噪音类,它可以产生和处理任何类型的噪音:

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

}

Here is an example of how to use the smoothNoise function :

以下是如何使用 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);

Here is an example of how to use the turbulence function :

以下是如何使用湍流函数的示例:

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

Here is an example of how to use the perlinNoise function :

以下是如何使用 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);