Shader基礎(chǔ)技巧整理
自從接觸了shader之后我便深深得愛上了它,因?yàn)樗?dú)特的編程思考方式?jīng)_擊著我這十幾年的慣性認(rèn)知。
在向各位大佬學(xué)習(xí)的過程中,每學(xué)到一個(gè)新的技巧,我都不禁感嘆:“實(shí)在是妙!”
本文將整理一些個(gè)人常用的shader技巧/方法,只包含片元著色器相關(guān)內(nèi)容。
由于本人尚屬初學(xué),所以內(nèi)容會比較基礎(chǔ)。
簡單幾何圖形
區(qū)間(帶通)

兩個(gè)階梯函數(shù)疊加構(gòu)成的帶通函數(shù),用數(shù)字信號處理的角度去思考貌似是個(gè)不錯(cuò)的選擇
float Band(float v, float start, float end) {float up = step(start, v);float down = 1.0 - step(end, v);return up * down;}
矩形

x, y兩個(gè)方向的帶通函數(shù)疊加
float Rect(vec2 uv, float l, float b, float r, float t) {float x = Band(uv.x, l, r);float y = Band(uv.y, b, t);return x * y;}
圓形

圓形比較容易出現(xiàn)鋸齒,所以用?smoothstep?做平滑處理。
由于?distance()?依賴?sqrt()?開根號,在一些老硬件上會比較耗時(shí),實(shí)際使用時(shí)可以考慮轉(zhuǎn)換為和r平方進(jìn)行比較的公式。
float Circle(vec2 uv, vec2 o, float r, float blur) {return smoothstep(r, r-blur, distance(uv, o));}
混合疊加
上述幾何圖形函數(shù)的輸出值是0或1的float(經(jīng)過?smoothstep()?可能會出現(xiàn)中間值,不過此處可以不考慮)。
0或1的float值可以利用加、減、乘來模擬位運(yùn)算。比如上述區(qū)間、矩形幾何圖形都是通過乘法疊加。
加減法例子:ET臉

畫一個(gè)大圓當(dāng)臉,減去兩個(gè)小圓當(dāng)眼睛
float ETFace(vec2 uv, vec2 o) {float c = Circle(uv, vec2(.0, .0), 0.5, 0.01);c -= Circle(uv, vec2(-.2, -.2), 0.2, 0.01);c -= Circle(uv, vec2(.2, -.2), 0.2, 0.01);return c;}
坐標(biāo)空間處理
Cocos Creator以?左上角?為坐標(biāo)原點(diǎn),范圍(0, 1)。
shadertoy, GlslEditor中均以?左下角?為坐標(biāo)原點(diǎn),范圍(0, 1),接下來所有代碼將使用這個(gè)坐標(biāo)系。
在繪制某些對稱圖形時(shí)可能需要將原點(diǎn)調(diào)整到屏幕中心,即將(0, 1)區(qū)間映射到(-0.5, 0.5)。
根據(jù)不同場景也可以將(0, 1)區(qū)間映射到(-1, 1),哪個(gè)處理起來方便用哪個(gè)。
// (0, 1)區(qū)間映射到(-1, 1)uv = uv * 2.0 - 1.0;
也可以用下面的方法從任意區(qū)間映射到任意區(qū)間
float Remap01(float a, float b, float t) {return (t-a) / (b-a);}float Remap(float a, float b, float c, float d, float t) {return Remap01(a, b, t) * (d-c) + c;}
長寬適配
不知道這個(gè)功能的簡稱是什么,暫且這么稱呼吧。
其作用是在分辨率長寬不等的情況下將坐標(biāo)空間映射為等邊,映射后原先較長的一邊其自變量區(qū)間會被放大。
void main() {vec2 uv = gl_FragCoord.xy/u_resolution.xy;uv = uv * 2.0 - 1.0; // 位移到以中間為原點(diǎn)uv.x *= u_resolution.x/u_resolution.y; // 將x的自變量區(qū)間拉長float mask = Rect(uv, -0.5, -0.5, 0.5, 0.5);vec3 color = vec3(mask);gl_FragColor = vec4(color,1.0);}
計(jì)算角度

用atan()?計(jì)算角度,圖中將(-PI, PI)區(qū)間映射到(0, 1)區(qū)間,并且將值對應(yīng)的灰度輸出。atan()?計(jì)算比較耗時(shí),實(shí)際項(xiàng)目中慎用。
void main() {vec2 uv = gl_FragCoord.xy/u_resolution.xy;uv = uv * 2.0 - 1.0;uv.x *= u_resolution.x/u_resolution.y;float angle = atan(uv.y, uv.x);angle = Remap(-PI, PI, 0., 1.0, angle);vec3 color = vec3(angle);gl_FragColor = vec4(color,1.0);}
旋轉(zhuǎn)

uv乘以旋轉(zhuǎn)矩陣
mat2 Rotate2d(float angle){return mat2(cos(angle), -sin(angle),sin(angle), cos(angle));}void main() {vec2 uv = gl_FragCoord.xy/u_resolution.xy;uv = uv * 2.0 - 1.0;uv.x *= u_resolution.x/u_resolution.y;uv = Rotate2d(PI / 6.) * uv;float mask = Rect(uv, -0.5, -0.5, 0.5, 0.5);vec3 color = vec3(mask);gl_FragColor = vec4(color,1.0);}
網(wǎng)格化

將屏幕分割成5x5個(gè)網(wǎng)格,每個(gè)格子里畫一個(gè)圓。
原理是將uv拉伸5倍后取小數(shù)部分,這樣處理后uv會變成每個(gè)網(wǎng)格內(nèi)的局部坐標(biāo),這個(gè)技巧被廣泛使用。
void main() {// ...uv = fract(uv * 5.);float mask = Circle(uv, vec2(0.5), 0.5, 0.01);// ...}
噪音(隨機(jī)化hash)

獲取噪音的方法很多很靈活,輸入一般是和uv相關(guān)的變量,輸出(0, 1)范圍的1維或2維值。
只要讓人肉眼難分辨出模式,就是一個(gè)好用的噪音函數(shù)。
噪音的用途非常廣泛,可以利用噪音降低圖片的人工痕跡,后面會單獨(dú)整理一篇文章。
float Noise1(vec2 p) {return fract(sin(dot(p, vec2(12.9898,78.233))) * 43758.5453123);}float Noise2(vec2 p) {p = frac(p * vec2(123.34, 345.45));p += dot(p, p + 34.345);return frac(p.x * p.y);}
常用鏈接
shadertoy
可以找到很多大神級shader作品
http://shadertoy.com/The Art Of Code頻道
邊做邊講解,帶你領(lǐng)略01之間的藝術(shù)
https://www.youtube.com/c/TheArtofCodeIsCool/videos
《The Book Of Shaders》
很棒的入門書籍,可惜還沒更新完
https://thebookofshaders.com/
我的博客
將《The Book Of Shaders》里的GlslEditor嵌入博客用于平時(shí)練習(xí)
https://caogtaa.github.io/shader/2020/07/16/shader-playground/

技術(shù)交流,歡迎加我微信:ezglumes ,拉你入技術(shù)交流群。
推薦閱讀:
音視頻開發(fā)工作經(jīng)驗(yàn)分享 || 視頻版
開通專輯 | 細(xì)數(shù)那些年寫過的技術(shù)文章專輯
NDK 學(xué)習(xí)進(jìn)階免費(fèi)視頻來了
推薦幾個(gè)堪稱教科書級別的 Android 音視頻入門項(xiàng)目
覺得不錯(cuò),點(diǎn)個(gè)在看唄~

