《The Book Of Shader》筆記,有增刪。
一、隨機(random)
說到隨機函式,JavaScript 中有 Math.random()
,PHP 中有rand()
,在圖形繪製時,隨機也無處不在。《The Book Of Shader》 通過一個簡單的函式衍化,讓我們瞭解隨機:
通過fract()
和sin()
的結合,我們得到了一個有一定規律但被打亂的曲線,當我們把1.0
變成無限大時,再看看效果:
我們把上面的公式封裝成rand()
函式:
Shader 中的隨機是確定性隨機(偽隨機),也就是當我們的輸入值確定時,輸出值也是確定的,而 JavaScript 和 PHP 則是非確定隨機,每次隨機出來的內容是不一樣的。當然我們還可以對隨機增加一些變化:
rand()*rand()
會讓值更趨近於 0:
更多的隨機研究可以看這篇文章,你會發現隨機數也是可以「操作」的:
你會發現隨機圖表中,會有兩個地方的隨機分佈不均勻(-1.5707 ~ 1.5707),這是 sin() 最大值和最小值的地方,所以我們在取值的時候儘量避免這兩個地方:
2D 隨機
現在我們對隨機有了深入的理解,是時候將它應用到二維,x 軸和 y 軸。為此我們需要將一個二維向量轉化為一維浮點數。這裡有幾種不同的方法來實現,但 dot() 函式在這個例子中尤其有用。它根據兩個向量的方向返回一個 0.0 到 1.0 之間的值。—— refer
如果你對下面的vec2(12.23,78.32)))*232348.23)
留有疑問,姑且將其理解為 magic number,它的效果就跟電視沒有訊號時的雪花效果一樣:
下面對這些隨機數做一些操作:
封裝函式:
// 偽隨機
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)
噪聲跟隨機有什麼不同?
噪音的基礎來自於隨機數,隨機數的特點是每個點的值都是離散的,相互完全沒有關係,而噪音則是讓離散的隨機數連續起來。最簡單的連續化處理就是插值,在離散資料中間用函式插值的方法把空隙填滿空間就自然連續了。說到插值,學過數值分析的立刻就能想到七八種插值方法,只要能保持連續性不管是三角函式,正態分佈,還是樣條曲線都可以使用。—— 不只是噪音
有了噪音我們就可以還原出自然界的真實景象:
如何得到一個離散的隨機值,可以通過上面的隨機函式:
接著把這些離散的隨機值通過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);
}
複製程式碼
相關連結: