Output range of Perlin noise

后端 未结 2 1916
后悔当初
后悔当初 2020-12-19 02:03

I\'m investigating a few of the various implementations for coherent noise (I know there are libraries, but this is mostly for my own edification and curiosity) and how you

相关标签:
2条回答
  • 2020-12-19 02:35

    When you calculate dot product, you may get values outside -1 +1 range, however during interpolation step, final value falls in -1 +1 range. This is because distance vectors of dots products that are interpolated point into opposite directions of interpolated axis. During the last interpolation output will not exceed -1 +1 range.

    Final output range for Perlin noise is defined by length of gradient vectors. If we talk about 2D noise and our goal to have output range -1 +1, the length of the gradient vectors should be sqrt(2) (~1,4142). It is common mistake to mix these vectors (1, 1) (-1, 1) (1, -1) (-1, -1) and (1, 0) (0, 1) (-1, 0) (0, -1). In this case final output range still will be -1 +1 range, however the values in range -0.707 +0.707 will be more frequent. To avoid this problem (1, 0) (0, 1) (-1, 0) (0, -1) vectors should be replaced with (sqrt(2), 0) (0, sqrt(2)) (-sqrt(2), 0) (0, -sqrt(2)).

    0 讨论(0)
  • 2020-12-19 02:43

    You have y0 = (int)d.x;, but you mean d.y. This will most certainly affect your output range, and is the reason you are seeing such largely out-of-range values.


    That said, the output range of Perlin noise is not actually [-1, 1]. While I'm not quite sure of the math myself (I must be getting old), this rather lengthy discussion works out that the actual range is [-sqrt(n)/2, sqrt(n)/2], where n is the dimensionality (2 in your case). So the output range of your 2D Perlin noise function should be [-0.707, 0.707]. This is somehow related to the fact that both d and the interpolation parameters are a function of p. If you read through that discussion, you may find the precise explanation you are looking for (particularly, post #7).

    I am testing your implementation using the following program (which I hacked together from your example, so pardon the weird use of gridCells and gridSize):

    import java.util.Random;
    
    
    public class Perlin {
    
        static final int gridSize = 200;
        static final int gridCells = 20;
        static final Vec[][] gradients = new Vec[gridCells + 1][gridCells + 1];
    
        static void initializeGradient () {
            Random rand = new Random();
            for (int r = 0; r < gridCells + 1; ++ r) {
                for (int c = 0; c < gridCells + 1; ++ c) {
                    double theta = rand.nextFloat() * Math.PI;
                    gradients[c][r] = new Vec(Math.cos(theta), Math.sin(theta));                
                }
            }
        }
    
        static class Vec {
            double x;
            double y;
            Vec (double x, double y) { this.x = x; this.y = y; }
            double dot (Vec v) { return x * v.x + y * v.y; }
            Vec sub (double x, double y) { return new Vec(this.x - x, this.y - y); }
        }
    
        static double fade (double v) {
            // easing doesn't matter for range sample test.
            // v = 3 * v * v - 2 * v * v * v;
            return v;
        }
    
        static double lerp (double p, double a, double b) {
            return (b - a) * p + a;
        }
    
        static Vec gradient (int c, int r) {
            return gradients[c][r];
        }
    
        // your function, with y0 fixed. note my gridSize is not a double like yours.     
        public static double getPoint(int x, int y) {
    
            Vec p = new Vec(x / (double)gridSize, y / (double)gridSize);
            Vec d = new Vec(Math.floor(p.x), Math.floor(p.y));
    
            int x0 = (int)d.x,
                y0 = (int)d.y;
    
            double d00 = gradient(x0    , y0    ).dot(p.sub(x0    , y0    )),
                   d01 = gradient(x0    , y0 + 1).dot(p.sub(x0    , y0 + 1)),
                   d10 = gradient(x0 + 1, y0    ).dot(p.sub(x0 + 1, y0    )),
                   d11 = gradient(x0 + 1, y0 + 1).dot(p.sub(x0 + 1, y0 + 1));
    
            double fadeX = fade(p.x - d.x),
                   fadeY = fade(p.y - d.y);
    
            double i1 = lerp(fadeX, d00, d10),
                   i2 = lerp(fadeX, d01, d11);
    
            return lerp(fadeY, i1, i2);
    
        }
    
        public static void main (String[] args) {
    
            // loop forever, regenerating gradients and resampling for range. 
            while (true) {
    
                initializeGradient();
    
                double minz = 0, maxz = 0;
    
                for (int x = 0; x < gridSize * gridCells; ++ x) {
                    for (int y = 0; y < gridSize * gridCells; ++ y) {
                        double z = getPoint(x, y);
                        if (z < minz)
                            minz = z;
                        else if (z > maxz)
                            maxz = z;
                    }
                }
    
                System.out.println(minz + " " + maxz);
    
            }
    
        }
    
    }
    

    I am seeing values within the theoretical range of [-0.707, 0.707], although I am generally seeing values between -0.6 and 0.6; which could just be a consequence of the value distribution and a low sampling rate.

    0 讨论(0)
提交回复
热议问题