噪音的目的是爲了增長隨機性,使事物看起來更真實。可是簡單的使用隨機函數 並不 天然,由於隨機函數產生的值與值之間並不連續。數組
要構造一個連續的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);
}