0x00 從函式出發
Shader 中的很多效果都是由函式計算得出的,如何更好地理解二者的關係呢。不妨先看看函式是什麼?函式的定義可以簡單地描述為:給定一個集合 A,對於其中的元素施加法則 f,則可以得到另一個集合 B。將這樣的 A 和 B 中的元素的對應關係,反映到二維直角座標系中,就可以得到一條關於 f 的曲線。比如,正弦函式 sin 的曲線。
那麼,應當如何通過函式來得到想要的 Shader 效果?
我們都知道Shader 的中文翻譯為著色器,Shader 的作用就是為螢幕中的每個畫素著色。一段 Shader 程式的輸入是位置資訊,輸出則是顏色資訊。是不是很像函式中的對映關係:f(位置) = 顏色。
0x01 sin 的顏色
有了上面的表示式,稍加轉變,我們就可以用 shader 來描述 sin 的顏色。在 shader 中,顏色是由一個四維浮點數向量 vec4 來表示的,分別表示 (r, g, b, a),取值範圍為 0.0 到 1.0。為了展示一個完整的 sin 週期,可以對 sin 函式進行一些縮放和平移的操作,使其週期 T = 1.0:
float f = sin(x * PI_2) / 2.0 + 0.5;
同時,也需要將螢幕的畫素座標規範化,使其落在 0.0 到 1.0 之間。下面 st 的計算是一個很常用的操作,其中,gl_FragCoord 是畫素的座標,u_resolution 是畫布的尺寸,兩者相除可以將畫素座標規範化。
vec2 st = gl_FragCoord.xy / u_resolution;
最終程式碼:
#ifdef GL_ES
precision mediump float;
#endif
#define PI_2 6.2831853
uniform vec2 u_resolution;
// 繪製 y 和 x 對應關係的曲線
float plot(float y, float x) {
return smoothstep(x - 0.01, x, y) - smoothstep(x, x + 0.01, y);
}
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
float f = sin(st.x * PI_2) / 2.0 + 0.5;
vec4 color = vec4(f);
float p = plot(st.y, f);
color = (1.0 - p) * color + p * vec4(0.0, 1.0, 0.0, 1.0);
gl_FragColor = color;
}
最後得到的效果如下:
仔細觀察從左到右的顏色變化,以及曲線的高度變化。不難發現,函式值越大的地方,顏色就越白,即,越接近白色的 rgba (1.0, 1.0, 1.0, 1.0);而函式值越小的地方,顏色就越黑,即,越接近黑色的 rgba (0.0, 0.0, 0.0, 0.0)。
這非常好理解,顏色 gl_FragColor 的每個分量就是根據函式的值來構造的。
0x02 sin 的形狀
說完顏色,不妨再觀察一下 sin 的曲線變化,是不是很像一座座高低起伏的山?只需對上面的程式碼稍加改造,就能得到幾座連綿的綠色小山。
#ifdef GL_ES
precision mediump float;
#endif
#define PI_2 6.2831853
uniform vec2 u_resolution;
uniform float u_time;
void main() {
vec2 st = gl_FragCoord.xy / u_resolution;
// st.x += u_time / 2.0;
float f = sin(st.x * PI_2 * 2.0) / 8.0 + 0.2;
float p = 1.0 - smoothstep(f, f + 0.01, st.y);
vec4 color = p * vec4(0.0, 1.0, 0.0, 1.0);
// color = p * vec4(0.1, 0.3, 0.4, 1.0);
gl_FragColor = color;
}
幾座抽象的綠色小山,雖然看上去有點粗糙:
還可以取消上面程式碼中的註釋,利用 u_time 值賦予畫面一點動效,這樣幾座綠色的小山又變成波動的海浪。
0x04 理解練習掌握
本文僅談論了最基本的 sin 函式,但也不難看出,sin 是一個強有力的造型工具。再結合另外兩個工具:fract 和 dot,我們還能利用 sin 來繪製一幅簡單的噪聲圖。
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
float rnd = random(st);
gl_FragColor = vec4(vec3(rnd),1.0);
}
噪聲在圖形學中有廣泛的應用,比如,用來模擬一些不規則的動態表面:火焰、雲、岩石等。
在 Shader 中需要時時刻刻與各種函式模型打交道,正是這些函式多樣的造型能力以及它們之間的有機結合,實現了多種多樣的 Shader 效果。
正確使用這些函式的能力,就是 Shader 的基本功。理解並不斷地練習如何使用這些函式,是非常重要的。