Shader 中的隨機與噪聲

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

1、隨機(random)

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

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

咱們把上面的公式封裝成rand()函數:dom

Shader 中的隨機是肯定性隨機(僞隨機),也就是當咱們的輸入值肯定時,輸出值也是肯定的,而 JavaScript 和 PHP 則是非肯定隨機,每次隨機出來的內容是不同的。固然咱們還能夠對隨機增長一些變化:wordpress

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

更多的隨機研究能夠看這篇文章,你會發現隨機數也是能夠「操做」的: ui

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

2D 隨機

如今咱們對隨機有了深刻的理解,是時候將它應用到二維,x 軸和 y 軸。爲此咱們須要將一個二維向量轉化爲一維浮點數。這裏有幾種不一樣的方法來實現,但 dot() 函數在這個例子中尤爲有用。它根據兩個向量的方向返回一個 0.0 到 1.0 之間的值。—— refer3d

若是你對下面的vec2(12.23,78.32)))*232348.23)留有疑問,姑且將其理解爲 magic number,它的效果就跟電視沒有信號時的雪花效果同樣:code

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

封裝函數:

// 僞隨機
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))));
}
複製代碼


2、噪聲(noise)

噪聲跟隨機有什麼不一樣?

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

有了噪音咱們就能夠還原出天然界的真實景象:

如何獲得一個離散的隨機值,能夠經過上面的隨機函數:

接着把這些離散的隨機值經過mix()線性插值的方式鏈接起來:

經過smoothstep()函數讓變化更圓滑:

在一些 noise 的應用中你會發現程序員喜歡用他們本身的三次多項式函數(好比下面的例子),而不是用smoothstep(),結果是同樣的。

經過這種方式獲得了一段 「噪音」

當咱們把它做爲值,顯示在畫布中,會是什麼樣子呢?能夠看到一維的噪音並無太大的價值:

能夠用直接封裝好的noise()函數(文章底部會羅列這些函數的聲明):

2D 噪聲

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

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

函數封裝:

// 一維(這裏都是基於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);
}
複製代碼

相關連接:

相關文章
相關標籤/搜索