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

噪音的目的是为了增加随机性,使事物看起来更真实。但是简单的使用随机函数 并不 自然,因为随机函数产生的值与值之间并不连续。

要构造一个连续的2d噪音,简单的想法可以是: 在一个平面上所有整数点位置赋一个随机的值, 而整数网格中的点 则采用插值的方式得到值。

这里有两个问题, 一是整数格子上的值如何给, 二是插值的方式。

perlin噪声的解决方式是:

      整数格子上的计算一个叫做gradient的东西,在2d空间这个gradient是2维度向量, 它的分布具有各向同性的特点,也就是每个整数格子点都有相同的概率得到某个方向的gradient。 实现的时候, gradient可以从 8个方向的向量里面获取, 具体取哪一个是需要一个随机的过程的。。

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

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

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

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

    获取一个点周围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);
}
    
    
    
      
   

转载于:https://my.oschina.net/u/186074/blog/79977

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值