Three.js(九)perlin 2d噪音的生成

噪音的目的是爲了增長隨機性,使事物看起來更真實。可是簡單的使用隨機函數 並不 天然,由於隨機函數產生的值與值之間並不連續。數組

要構造一個連續的2d噪音,簡單的想法能夠是: 在一個平面上全部整數點位置賦一個隨機的值, 而整數網格中的點 則採用插值的方式獲得值。函數

這裏有兩個問題, 一是整數格子上的值如何給, 二是插值的方式。spa

perlin噪聲的解決方式是:orm

      整數格子上的計算一個叫作gradient的東西,在2d空間這個gradient是2維度向量, 它的分佈具備各向同性的特色,也就是每一個整數格子點都有相同的機率獲得某個方向的gradient。 實現的時候, gradient能夠從 8個方向的向量裏面獲取, 具體取哪個是須要一個隨機的過程的。。索引

        [1, 0], [1, 1],ip

        [0, 1], [-1, 1],get

        [-1, 0], [-1, -1],it

        [0, -1], [1, -1],io

    獲取一個點周圍4個整數定點的 gradient以後, 將gradient和  4個點到該點的 向量 的內積 做爲4個點的值,
    接着須要對這4個值進行插值。
    
     簡單的2維 線性插值的方法是: 首先 沿 x方向 將 n00 n10 x插值, 再將 n01 n11 x插值, 最後將結果 沿y方向插值。
   def mix(u, v, a):
        return u*(1-a)+v*a
     可是這樣插值結果不光滑, 能夠調整插值的係數,  用函數 3*a*a - 2*a 從新映射,獲得新的插值係數,用這個值插值就比較光滑, 且在端點兩側更加緊湊。
        float smooth(float u) {
        return 3.0*u*u-2.0*u;
      }
     接着是如何爲每一個定點選擇gradient呢? 這裏有採用一個週期循環的方式。 例如對256個數字0-255 生成一個排列permutation, 這樣平面上每一個點
經過permutation[(X+permutation[Y%256])%256] 能夠獲得一個0-255 之間的某個值 接着將這個值 %8 就獲得 其對應的gradient編號。
      這樣作,雖然會致使平面上的一個循環的問題,可是噪聲這種東西,原本就是局部的,在大的範圍上看,噪聲都是趨於0的, 所以問題不大。
     而如何生成一個 0-255的排列呢? 有一種叫作 permutation polynomial 排列多項式的東西, 例如 (2*x*x+x)%256 這樣一個函數,
   能夠生成一個0-255的排列 只須要把 0-255的值代入就能夠了。 
   實施上能夠生成任意 2^k 的排列。
     
     這樣整個流程就是:
    一個 gradient數組
    一個permutation 
    計算一個點周圍4個整數點的 gradient
    計算gradient和 4個整數點 到目標點的 向量的內積
    光滑插值積分的值。
    
     在Three.js 中咱們聲明一個平面 -1 1 -1 1. 使用平面的紋理座標做爲計算噪音的座標點。
         var plane = new THREE.Mesh(new THREE.PlaneGeometry(2, 2, 1, 1), 
     可是有個問題,紋理座標是從 0-1的, 所以平面上的點的臨近的4個整數點都是同樣的, 所以咱們須要一個係數用來乘以紋理座標,將平面的座標空間擴大, 所以shader裏面須要一個uniform float coff 做爲係數。
    同時shader裏面也須要一個uniform vec2 gradien[8]; 的2維度向量數組。
    可是如何訪問這個數組呢? shader裏面數組的訪問只能使用常數索引的方式, 固然咱們能夠把gradient放到1維紋理裏面, 這樣就能夠經過texture獲得對應的向量,這種方式彷佛更好。
    所以須要一個 8*1的數據紋理, 注意紋理須要設置 needsUpdate 來將數據存入到顯卡中。
    var texture = new THREE.DataTexture(data, 8, 1, THREE.RGBFormat);
    texture.needsUpdate = true;
    其中data是一個 8*3 的 UintArray
       var v = [
        [1, 0], [1, 1],
        [0, 1], [-1, 1],
        [-1, 0], [-1, -1],
        [0, -1], [1, -1],
    ];
    var data = new Uint8Array(3*8);
    for(var i in v) {
        data[i*3] = ~~((v[i][0]+1)/2*255);//R
        data[i*3+1] = ~~((v[i][1]+1)/2*255);//G
        data[i*3+2] = 0;//B
    }
      
    所以平面的material是:
       var plane = new THREE.Mesh(new THREE.PlaneGeometry(2, 2, 1, 1), 
        new THREE.ShaderMaterial({
            uniforms:{
                coff:{type:'f', value:512},
                gradient:{type:'t', value:0, texture:texture},
            },
            attributes:{
            },
            vertexShader: document.getElementById("vert").textContent,
            fragmentShader: document.getElementById("frag").textContent,
            }));
    
   而shader主要是fragment, vertex主要將紋理座標傳入到fragment中。
      uniform float coff;
    //uniform vec2 gradient[8];
    uniform sampler2D gradient;
    varying vec2 vUv;
    
    
    //計算隨機的排列序列號 接着獲取一個gradient
    float permu(float x){
        x = mod(x, 256.0);
        return mod((2.0*x*x+x), 256.0);
    }
    float smooth(float u) {
        return 3.0*u*u-2.0*u;
    }
    float myDot(vec2 p, float dx, float dy){
        return p.x*dx+p.y*dy;
    }
    vec2 getGradient(float v)
    {
        v = mod(v, 8.0);
        v = v/8.0;
        vec4 c = texture2D(gradient, vec2(v, 0));
        return vec2(c.r*2.0-1.0, c.g*2.0-1.0);
    }
    float noise(float x, float y) {
        float X = floor(x);
        float Y = floor(y);
        //偏移座標
        float difx = x-X;
        float dify = y-Y;
        //約束到0-255
        X = mod(X, 255.0);
        Y = mod(Y, 255.0);
        //0 - 255
        float g00 = permu(x+permu(y));
        float g01 = permu(x+permu(y+1.0));
        float g11 = permu(x+1.0+permu(y+1.0));
        float g10 = permu(x+1.0+permu(y));
        
        //0-8 gradient * 偏移座標
        float n00 = myDot(getGradient(g00), difx, dify);
        float n01 = myDot(getGradient(g01), difx, dify-1.0);
        float n11 = myDot(getGradient(g11), difx-1.0, dify-1.0);
        float n10 = myDot(getGradient(g10), difx-1.0, dify);
        //插值
        float u = smooth(difx);
        float v = smooth(dify);
        float nx0 = mix(n00, n10, u);
        float nx1 = mix(n01, n11, u);
        float nxy = mix(nx0, nx1, v);
        return nxy;
    }
  void main( void ) {
        float n = noise(vUv.x*coff, vUv.y*coff); 
        gl_FragColor = vec4(n, n, n, 1);
}
相關文章
相關標籤/搜索