【问题标题】:Using Perlin noise to generate a 2d tile map使用 Perlin 噪声生成 2d 瓦片图
【发布时间】:2013-06-30 17:20:19
【问题描述】:

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

我正在使用 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;
    }
}

【问题讨论】:

  • 您遇到错误了吗?错误是什么?还是它根本无法按您的预期工作?试着截取你想要的和你实现的。
  • 我猜你的问题是你想要某种像素着色器用噪声覆盖你的基础纹理 - 我建议你重新表述你的问题更具体。我对像素着色器了解不多,但如果没有它们,我猜你需要用嘈杂的瓷砖创建一个新的纹理。
  • 我在你的代码中看不到你实际尝试使用噪音的地方。
  • 对不起,我很困惑,我的目标是生成 perlin 噪声,然后用它来渲染随机的 2d 瓦片地图。我基本上迷路了。
  • @user2489897 你的具体问题是什么?柏林噪声可能是错误的,或者您不知道如何将柏林噪声转换为瓷砖?如果您不确定 perlin 噪声是否“正确”,请使用它生成灰度图像(如果这是您的问题,我可以发布代码来实现这一点)并查看它。它应该看起来像从空中看的山丘/山脉

标签: java libgdx noise perlin-noise procedural


【解决方案1】:

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

低持久性:

或

高持久性:

或

高持久性(缩小):

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

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 之间产生,根据您的实施进行缩放。假设您的柏林噪声将为任何 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 噪声偏向于 0。假设 2D,您可以通过半字面意义上的景观类比得到很好的结果,低值 = 水,低值 = 沙子,中等值 = 草,高值 = 雪。

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

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

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2020-08-30
      • 1970-01-01
      • 2021-12-15
      • 2014-02-08
      • 2011-08-01
      • 1970-01-01
      • 2011-06-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多