圖形學中的噪聲入門


一、白噪聲

白噪聲常與偽隨機數一起使用。這樣,固定的輸入就會產出固定的隨機數輸出,最終渲染出來的紋理也會是固定的,但又具備隨機的視覺效果。

常用的白噪聲隨機產生函數如下:

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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM