使用Perlin噪声生成2d瓦片地图

时间:2013-07-03 06:11:22

标签: java libgdx noise perlin-noise procedural

我浏览了整个互联网并研究了柏林的噪音,但是,我仍感到困惑。

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

如果我的实施是正确的,我不知道从那里去哪里制作随机地形。我如何将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;
    }
}

2 个答案:

答案 0 :(得分:4)

perlin噪音的正确性
关于你的perlin噪音是否“正确”;最简单的方法是看你的perlin噪音(或基于几个八度音程的技术分形噪音)是否正常工作是使用你的perlin噪音的值来生成灰度图像,这个图像应该看起来喜欢某种景观(连绵起伏的丘陵或山脉,取决于您为持久性选择的参数(以及较少程度的八度音阶)。一些perlin噪音的例子是:

低俗:
Persisance of 0.5

高度不满:
Persisance of 0.7

高度差异(缩小):
enter image description here

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

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之间,但perlin噪声通常在-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。如果您不是这样,请随时修改

映射到磁贴
这完全取决于您,您需要定义perlin噪声值的某些范围以创建某些切片。但要注意的是,perlin噪声偏向于0.假设2D你可以通过半字面上的景观类比获得不错的结果,低值=水,低值=沙,中值=草,高值=雪。

还要注意,在一些实施方式中(例如,我的世界生物群系和洞穴),将几个随机值组合以产生总体结果。见https://softwareengineering.stackexchange.com/questions/202992/randomization-of-biomes/203040#203040

改进的想法
如果你发现perlin噪声的产生太慢,那么考虑单纯形噪声,它具有非常相似的特性,但效率更高(特别是在更高的尺寸时)。然而,单面噪声在数学上要复杂得多。

答案 1 :(得分:3)

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

我也在研究这个问题,起初我发现你的代码在外观上接合工作时有用,但是当我想改变图像的大小时,平滑的噪音不能适当地缩放,我找不到一个修复代码的方法。

经过更多的研究后,我发现你的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);