Shader 001 - 函式造型能力

knxfe發表於2020-09-14

0x00 從函式出發

Shader 中的很多效果都是由函式計算得出的,如何更好地理解二者的關係呢。不妨先看看函式是什麼?函式的定義可以簡單地描述為:給定一個集合 A,對於其中的元素施加法則 f,則可以得到另一個集合 B。將這樣的 A 和 B 中的元素的對應關係,反映到二維直角座標系中,就可以得到一條關於 f 的曲線。比如,正弦函式 sin 的曲線。

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;
}

最後得到的效果如下:

alpha

仔細觀察從左到右的顏色變化,以及曲線的高度變化。不難發現,函式值越大的地方,顏色就越白,即,越接近白色的 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;
}

幾座抽象的綠色小山,雖然看上去有點粗糙:

moutain

還可以取消上面程式碼中的註釋,利用 u_time 值賦予畫面一點動效,這樣幾座綠色的小山又變成波動的海浪。

wave

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);
}

噪聲在圖形學中有廣泛的應用,比如,用來模擬一些不規則的動態表面:火焰、雲、岩石等。

noise

在 Shader 中需要時時刻刻與各種函式模型打交道,正是這些函式多樣的造型能力以及它們之間的有機結合,實現了多種多樣的 Shader 效果。

正確使用這些函式的能力,就是 Shader 的基本功。理解並不斷地練習如何使用這些函式,是非常重要的。

相關文章