Shader 中的隨機與噪聲

_Hahn_發表於2019-02-26

《The Book Of Shader》筆記,有增刪。

一、隨機(random)

說到隨機函式,JavaScript 中有 Math.random(),PHP 中有rand(),在圖形繪製時,隨機也無處不在。《The Book Of Shader》 通過一個簡單的函式衍化,讓我們瞭解隨機:

Shader 中的隨機與噪聲

通過fract()sin()的結合,我們得到了一個有一定規律但被打亂的曲線,當我們把1.0變成無限大時,再看看效果:

Shader 中的隨機與噪聲

我們把上面的公式封裝成rand()函式:

Shader 中的隨機與噪聲

Shader 中的隨機是確定性隨機(偽隨機),也就是當我們的輸入值確定時,輸出值也是確定的,而 JavaScript 和 PHP 則是非確定隨機,每次隨機出來的內容是不一樣的。當然我們還可以對隨機增加一些變化:

rand()*rand() 會讓值更趨近於 0:

Shader 中的隨機與噪聲

更多的隨機研究可以看這篇文章,你會發現隨機數也是可以「操作」的:

Shader 中的隨機與噪聲
Shader 中的隨機與噪聲

你會發現隨機圖表中,會有兩個地方的隨機分佈不均勻(-1.5707 ~ 1.5707),這是 sin() 最大值和最小值的地方,所以我們在取值的時候儘量避免這兩個地方:

Shader 中的隨機與噪聲

2D 隨機

現在我們對隨機有了深入的理解,是時候將它應用到二維,x 軸和 y 軸。為此我們需要將一個二維向量轉化為一維浮點數。這裡有幾種不同的方法來實現,但 dot() 函式在這個例子中尤其有用。它根據兩個向量的方向返回一個 0.0 到 1.0 之間的值。—— refer

如果你對下面的vec2(12.23,78.32)))*232348.23)留有疑問,姑且將其理解為 magic number,它的效果就跟電視沒有訊號時的雪花效果一樣:

Shader 中的隨機與噪聲

下面對這些隨機數做一些操作:

Shader 中的隨機與噪聲

封裝函式:

// 偽隨機
float random (float n) {
    return fract(sin(n)*1000000.);
}

float random (vec2 st) {
    return fract(sin(dot(st.xy,vec2(12.9898,78.233)))*43758.5453123);
}

// 雜湊函式(雜湊值)
float hash(float n) {
    return fract(sin(n) * 1e4);
}

float hash(vec2 p) { 
    return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x))));
}
複製程式碼

二、噪聲(noise)

噪聲跟隨機有什麼不同?

噪音的基礎來自於隨機數,隨機數的特點是每個點的值都是離散的,相互完全沒有關係,而噪音則是讓離散的隨機數連續起來。最簡單的連續化處理就是插值,在離散資料中間用函式插值的方法把空隙填滿空間就自然連續了。說到插值,學過數值分析的立刻就能想到七八種插值方法,只要能保持連續性不管是三角函式,正態分佈,還是樣條曲線都可以使用。—— 不只是噪音

有了噪音我們就可以還原出自然界的真實景象:

Shader 中的隨機與噪聲

如何得到一個離散的隨機值,可以通過上面的隨機函式:

Shader 中的隨機與噪聲

接著把這些離散的隨機值通過mix()線性插值的方式連線起來:

Shader 中的隨機與噪聲

通過smoothstep()函式讓變化更圓滑:

Shader 中的隨機與噪聲

在一些 noise 的應用中你會發現程式設計師喜歡用他們自己的三次多項式函式(比如下面的例子),而不是用smoothstep(),結果是一樣的。

Shader 中的隨機與噪聲

通過這種方式得到了一段 「噪音」

當我們把它作為值,顯示在畫布中,會是什麼樣子呢?可以看到一維的噪音並沒有太大的價值:

Shader 中的隨機與噪聲

可以用直接封裝好的noise()函式(文章底部會羅列這些函式的宣告):

Shader 中的隨機與噪聲

2D 噪聲

2D 噪聲在圖形角度才更具備價值,其自變數不再是水平或垂直的一個值而是二維的值:

Shader 中的隨機與噪聲

當我們使用已經封裝好後的 2D noise() 函式並傳入座標後,看看效果:

Shader 中的隨機與噪聲

函式封裝:


// 一維(這裡都是基於hash,也可以改成基於random
float noise(float x) {
    float i = floor(x);
    float f = fract(x);
    float u = f * f * (3.0 - 2.0 * f);
    return mix(hash(i), hash(i + 1.0), u);
}

// 二維
float noise(vec2 x) {
    vec2 i = floor(x);
    vec2 f = fract(x);

	// Four corners in 2D of a tile
	float a = hash(i);
    float b = hash(i + vec2(1.0, 0.0));
    float c = hash(i + vec2(0.0, 1.0));
    float d = hash(i + vec2(1.0, 1.0));

    // Simple 2D lerp using smoothstep envelope between the values.
	// return vec3(mix(mix(a, b, smoothstep(0.0, 1.0, f.x)),
	//			mix(c, d, smoothstep(0.0, 1.0, f.x)),
	//			smoothstep(0.0, 1.0, f.y)));

	// Same code, with the clamps in smoothstep and common subexpressions
	// optimized away.
    vec2 u = f * f * (3.0 - 2.0 * f);
	return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}

// 三維
float noise(vec3 x) {
    const vec3 step = vec3(110, 241, 171);

    vec3 i = floor(x);
    vec3 f = fract(x);
 
    // For performance, compute the base input to a 1D hash from the integer part of the argument and the 
    // incremental change to the 1D based on the 3D -> 1D wrapping
    float n = dot(i, step);

    vec3 u = f * f * (3.0 - 2.0 * f);
    return mix(mix(mix( hash(n + dot(step, vec3(0, 0, 0))), hash(n + dot(step, vec3(1, 0, 0))), u.x),
                   mix( hash(n + dot(step, vec3(0, 1, 0))), hash(n + dot(step, vec3(1, 1, 0))), u.x), u.y),
               mix(mix( hash(n + dot(step, vec3(0, 0, 1))), hash(n + dot(step, vec3(1, 0, 1))), u.x),
                   mix( hash(n + dot(step, vec3(0, 1, 1))), hash(n + dot(step, vec3(1, 1, 1))), u.x), u.y), u.z);
}
複製程式碼

相關連結:

相關文章