一、白噪聲
白噪聲常與偽隨機數一起使用。這樣,固定的輸入就會產出固定的隨機數輸出,最終渲染出來的紋理也會是固定的,但又具備隨機的視覺效果。
常用的白噪聲隨機產生函數如下:
float random = dot(vec in , vec const);
使用輸入向量和一個任意向量點乘,即可得到一個隨機的結果;
在OpenGL中使用:
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform float u_time; float rand(vec2 vec){ float random = dot(vec , vec2(-12.000,20.820)); random = fract(random); return random; } void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; vec3 color = vec3(rand(uv)); gl_FragColor = vec4(color,1.0); }
得到的噪聲圖:
可以看到兩個向量點乘投影形成的條帶。一點也不均勻。
解決方法是對點積后的結果取正弦,然后乘上一個大數,將其小數點移后幾位,得到更為隨機分布的小數。
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform float u_time; float rand(vec2 vec){ float random = sin(dot(vec, vec2(12.9898,78.233))) * 43758.5453; random = fract(random); return random; } void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; vec3 color = vec3(rand(uv)); gl_FragColor = vec4(color,1.0); }
得到的噪聲如下:
注意這里取的
vec2(12.9898,78.233)
和43758.5453;
都是前輩們總結下來的經驗數,可以得到很好的白噪聲圖。
二、細胞
分割為細胞的方法就是紋理坐標乘上對應的倍數,簡單高效。
void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; uv = uv * 10.0; vec3 color = vec3(fract(uv),1.0); gl_FragColor = vec4(color,1.0); }
得到的效果圖:
為每個細胞賦上不同的顏色:
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; float rand2dTo1d(vec2 value ,vec2 randvec2 ) { vec2 dotDir = randvec2; vec2 smallValue = sin(value); float random = dot(smallValue, dotDir); random = fract(sin(random) * 143758.5453); return random; } vec2 rand2dto2d(vec2 value){ return vec2( rand2dTo1d(value, vec2(12.989, 78.233)), rand2dTo1d(value, vec2(39.346, 11.135)) ); } void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; vec2 st = uv * 10.0; vec3 color = vec3(rand2dto2d(floor(st)),1.0); gl_FragColor = vec4(color,1.0); }
得到的圖片:
這里使用了前面提到的白噪聲公式來為細胞產出不同的顏色。
三、連續噪聲(Value Noise)
白噪聲很好,但是它不具備連續性。
柏林噪聲可以很好地解決這個問題。
使用一維噪聲:
使用一維的白噪聲生成灰度圖
float rand1dTo1d(float value){ float mutator = 0.546; float random = fract(sin(value + mutator) * 143758.5453); return random; } void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; vec3 color = vec3(rand1dTo1d( floor(uv.x * 30.))); gl_FragColor = vec4(color,1.0); }
得到:
將其壓縮成線:
float rand1dTo1d(float value){ float mutator = 0.546; float random = fract(sin(value + mutator) * 143758.5453); return random; } void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; //移到中心 uv -= vec2(0.5); vec2 st = uv * 6.; float noise = rand1dTo1d( floor(st.x));
//取噪聲與紋理y坐標距離
//在 dist-> 0時畫線, float dist = abs(noise - st.y);
//抗鋸齒 vec3 color = vec3(smoothstep(0, fwidth(st.y), dist)); gl_FragColor = vec4(color,1.0); }
得到:
為了讓值連續,取其小數部分進行插值,修改noise如下:
//插值 float noise = mix(rand1dTo1d( floor(st.x)), rand1dTo1d(ceil(st.x)), fract(st.x));
得到的圖如下:
這時使用的插值因子是
st.x
得到的結果並不平滑,我們應該使用緩動公式插值,常見的公式是。
更改,如下:
//插值 float x = fract(st.x); float t= 6.*pow(x,5.) - 15.*pow(x,4.)+10.*pow(x,3.); float noise = mix(rand1dTo1d( floor(st.x)), rand1dTo1d(ceil(st.x)), fract(t));
得到的圖如下:
此時,一個連續的一維的噪聲就生成了。
下面應用到二維:
一張紋理圖片中,直觀輸入就是uv,一個vec2變量。
在一維情況下,我們需要考慮左右插值,僅需插值一次。
二維時,取輸入點記作(x0,y0),則需要使用(x0+1,y0),(x0,y0+1),(x0+1,y0+1)共四個點計算噪聲值。所以,噪聲函數應該變為:
float noise2d(vec2 value){ float x10 = rand2dTo1d(vec2(floor(value.x), ceil(value.y)), vec2(12.989, 78.233)); float x11 = rand2dTo1d(vec2(ceil(value.x), ceil(value.y)), vec2(12.989, 78.233)); float x00 = rand2dTo1d(vec2(floor(value.x), floor(value.y)), vec2(12.989, 78.233)); float x01 = rand2dTo1d(vec2(ceil(value.x), floor(value.y)), vec2(12.989, 78.233)); float x = fract(value.x); float y = fract(value.y);
float tx= 6.*pow(x,5.) - 15.*pow(x,4.)+10.*pow(x,3.); float ty= 6.*pow(y,5.) - 15.*pow(y,4.)+10.*pow(y,3.); //使用 tx插值 x -> x+1 float x1 = mix(x10, x11, tx); float x0 = mix(x00, x01, tx);
//使用ty插值 y -> y+1 float noise = mix(x0, x1, ty); return noise; }
主函數調用變為:
void main() { vec2 uv = gl_FragCoord.xy/u_resolution.yy; //移到中心 uv -= vec2(0.5); vec2 st = uv * 10.; //插值 float noise = noise2d(st); vec3 color = vec3(noise); gl_FragColor = vec4(color,1.0); }
這時得到的噪聲圖如下:
一個連續的噪聲圖。
三維的情況同樣,只不過插值點變成了八個。
四、柏林噪聲
價值噪聲使用內插值的方式實現了連續效果,但是情況不理想,更多時候采用梯度插值的方式,也就是柏林噪聲,這樣得到的噪聲更加平滑連續。
一維柏林噪聲實現
float gradientNoise(float value){
float x =fract(value);
float tx= 6.*pow(x,5.) - 15.*pow(x,4.)+10.*pow(x,3.);
float noise0 = (rand1dTo1d(floor(value)) * 2. - 1.) * x;
float noise1 = (rand1dTo1d(ceil(value)) * 2. - 1.)*(x -1.);
return mix(noise0, noise1, tx);
}
柏林噪聲與價值噪聲區別就在噪聲產生的方式,rand1dTo1d(floor(value)) * 2. - 1.操作會產生(-1,+1)的隨機變量,在一維情況下可視為隨機長度的上下梯度。
在(x,x+1)的整數之間,由於偽隨機數的關系,產生隨機值是固定的,因此,乘上小數部分后,即可得到連續的梯度。
一維柏林噪聲結果:
二維柏林噪聲
取(x,y)(x+1,y)(x,y+1)(x+1,y+1)四個正方形頂點,產生四個向量,並與指向正方形內部的方向向量作點積,得到各個頂點在正方形上的投影值,插值即可得到。
a = dot(random( i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ) b = dot( random( i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ) c = dot( random( i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ) d = dot( random( i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ) //a b 、c d 在x軸連續 ab= mix(a,b,u.x) cd = mix(b,c,u.x) //ab cd 在y軸連續 noise = mix(ab,cd,u.y)
得到:
加亮顏色值
col += 0.5;
讓明暗分界更加明顯:
col = fract(col *10.);
col*10得到的值范圍是(0,10),取小數后得到十個(0,1)的數值范圍,而且根據噪聲的插值方式,這十個數值范圍應該是連續且嵌套的,得到類似等高線的效果:
五、噪聲疊加
在一個噪聲基礎上疊加多種頻率的噪聲,可以得到變化豐富的噪聲圖:
0次疊加
疊加1/2頻率 +1/2 的noise
(1+1/2+1/4)*noise
在疊加的基礎上加上UV偏移
uv =rotateMatrix * uv