2013-08-15 14 views
5

コヒーレントノイズのさまざまな実装のいくつかを調べています(私はライブラリがあると知っていますが、これは主に自分の恩恵と好奇心のためです)、どのように使用できるか、私はオリジナルのPerlinノイズのものを持っています。Perlinノイズの出力範囲

this frequently linked Math FAQによれば、出力範囲は-11の間になりますが、値がその範囲にどのようになるか分かりません。

私が理解するように、アルゴリズムは基本的にこれです:各格子点には、長さが1の関連するランダム勾配ベクトルがあります。次に、各点について、4つの周囲のグリッド点すべてに対して、ランダムな勾配とそのグリッド点からのベクトルの内積を計算します。次に、ファンシーなイージーカーブと線形補間を使用して、その値を1つの値にします。

しかし、これらのドットプロダクトは時には範囲[-1, 1]の外に出ることがあり、最終的にドットプロダクト間で線形補間を行うので、最終的な値は、場合によっては[-1, 1]の範囲外になりますか?

言う、例えば、ランダムなベクターの1つは、(単位正方形である)(sqrt(2)/2, sqrt(2)/2)(1の長さを有する)と(0.8, 0.8)であることを、あなたがおおよそ1.131の結果を得ます。その値が線形補間で使用される場合、生成される値が1より大きい可能性があります。実際、私の単純な実装では、それはかなり頻繁に起こります。

ここに何か不足していますか?

参考までに、私のコードはJavaです。 Vecは簡単な2次元ベクトル演算を行う単純なクラスです。fade()はイージーカーブ、lerp()は線形補間、gradient(x, y)はそのグリッドポイントのグラデーションをVecとして与えます。 gridSize変数はあなたにピクセル単位でグリッドのサイズを与えます(それは二重の型を持つ):

public double getPoint(int x, int y) { 
    Vec p = new Vec(x/gridSize, y/gridSize); 
    Vec d = new Vec(Math.floor(p.x), Math.floor(p.y)); 


    int x0 = (int)d.x, 
     y0 = (int)d.x; 


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

編集:ここでは、ランダムな勾配を生成するためのコードがあります:genjava.util.Randomある

double theta = gen.nextDouble() * 2 * Math.PI; 
gradients[i] = new Vec(Math.cos(theta), Math.sin(theta)); 

答えて

6

y0 = (int)d.x;がありますが、d.yを意味します。これはあなたの出力範囲に最も確実に影響し、そのような範囲をはるかに超える値が見られる理由です。

Perlinノイズの出力範囲は、実際 [-1、1] ないが、前記

。私は自分自身(私は古くなっているはずです)の数学についてはあまりよく分かりませんが、実際の範囲はであり、実際の範囲はnです。次元(あなたの場合は2)です。したがって、2Dパーリンノイズ関数の出力範囲は、[-0.707,0.707]になるはずです。これは、dと補間パラメータの両方がpの関数であるという事実と何らかの形で関係しています。そのディスカッションを読むと、あなたが探している正確な説明(特に、post #7)が見つかります。

私は以下のプログラム使用して実装をテスト(私はあなたの例から一緒にハッキング、とても奇妙gridCellsの使用と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); 

     } 

    } 

} 

を[-0.707、0.707]、私は一般的に-0.6と0.6の間の値を見ています。値の分布と低いサンプリングレートの結果である可能性があります。

+0

ありがとうございました!それはそれを修正した。 – Oskar

0

ドット積を計算すると、-1 + 1の範囲外の値が得られますが、補間ステップでは最終値は-1 +1の範囲に収まります。これは、補間されたドット積の距離ベクトルが、補間された軸の反対方向を指すためです。最後の補間の間、出力は-1 +1の範囲を超えません。

Perlinノイズの最終出力範囲は、勾配ベクトルの長さによって定義されます。 2Dノイズと、出力範囲-1 +1を持つという目標について述べると、勾配ベクトルの長さはsqrt(2)(〜1,4142)でなければなりません。これらのベクトル(1,1)(-1、-1)(-1、-1)と(1,0)(0,1)(-1、0)を混合するのはよくある間違いです。 0、-1)。この場合、最終的な出力範囲は-1 +1の範囲になりますが、-0.707 + 0.707の範囲の値がより頻繁になります。この問題を回避するには、ベクトルを(sqrt(2)、0)(0、sqrt(2))(-sqrt(1、0) (2)、0)(0、-sqrt(2))である。