有趣的Shader

目錄

1.Shader簡單介紹以及入門躁愿。
2.抖音特效經(jīng)典濾鏡實現(xiàn)(包含靈魂出竅、抖動)署尤。
3.用Shader創(chuàng)造一些新鮮有趣的效果吧蝶锋。

1.1 什么是Fragment Shader(片段著色器)?

我們把 shaders 和古騰堡印刷術(shù)相提并論言秸。為什么這樣類比呢软能?更重要的是,什么是 shader举畸?


From Letter-by-Letter, Right: William Blades (1891). To Page-by-page, Left: Rolt-Wheeler (1920).

如果你曾經(jīng)有用計算機繪圖的經(jīng)驗查排,你就知道在這個過程中你需要畫一個圓,然后一個長方形抄沮,一條線跋核,一些三角形……直到畫出你想要的圖像岖瑰。這個過程很像用手寫一封信或一本書 —— 都是一系列的指令,需要你一件一件完成砂代。
Shaders 也是一系列的指令蹋订,但是這些指令會對屏幕上的每個像素同時下達。也就是說刻伊,你的代碼必須根據(jù)像素在屏幕上的不同位置執(zhí)行不同的操作露戒。就像活字印刷,你的程序就像一個 function(函數(shù))捶箱,輸入位置信息智什,輸出顏色信息,當它編譯完之后會以相當快的速度運行丁屎。


Chinese movable type.
1.2 為什么 shaders 運行特別快荠锭?

為了回答這個問題,不得不給大家介紹并行處理(parallel processing)的神奇之處晨川。
想象你的 CPU 是一個大的工業(yè)管道证九,然后每一個任務都是通過這個管道的某些東西 —— 就像一個生產(chǎn)流水線那樣。有些任務要比別的大础爬,也就是說要花費更多時間和精力去處理甫贯。我們就稱它要求更強的處理能力。由于計算機自身的架構(gòu)看蚜,這些任務需要串行叫搁;即一次一個地依序完成。現(xiàn)代計算機通常有一組四個處理器供炎,就像這個管道一樣運行渴逻,一個接一個地處理這些任務,從而使計算機流暢運行音诫。每個管道通常被稱為線程惨奕。


CPU

CPU視頻游戲和其他圖形應用比起別的程序來說,需要高得多的處理能力竭钝。因為它們的圖形內(nèi)容需要操作無數(shù)像素梨撞。想想看,屏幕上的每一個像素都需要計算香罐,而在 3D 游戲中幾何和透視也都需要計算卧波。
讓我們回到開始那個關(guān)于管道和任務的比喻。屏幕上的每個像素都代表一個最簡單的任務庇茫。單獨來看完成任何一個像素的任務對 CPU 來說都很容易港粱,那么問題來了,屏幕上的每一個像素都需要解決這樣的小任務旦签!也就是說查坪,哪怕是對于一個老式的屏幕(分辨率 800x600)來說寸宏,都需要每幀處理480000個像素,即每秒進行14400000次計算偿曙!是的氮凝,這對于微處理器就是大問題了!而對于一個現(xiàn)代的 2800x1800 視網(wǎng)膜屏遥昧,每秒運行60幀覆醇,就需要每秒進行311040000次計算。圖形工程師是如何解決這個問題的炭臭?


image

這個時候,并行處理就是最好的解決方案袍辞。比起用三五個強大的微處理器(或者說“管道”)來處理這些信息鞋仍,用一大堆小的微處理器來并行計算,就要好得多搅吁。這就是圖形處理器(GPU : Graphic Processor Unit)的來由威创。
[圖片上傳失敗...(image-9a0dd3-1556266552619)]
把GPU設(shè)想成一堆小型微處理器排成一個平面的畫面,假設(shè)每個像素的數(shù)據(jù)是乒乓球谎懦。14400000個乒乓球可以在一秒內(nèi)阻塞幾乎任何管道肚豺。但是一面800x600的管道墻,每秒接收30波480000個像素的信息就可以流暢完成界拦。這在更高的分辨率下也是成立的 —— 并行的處理器越多吸申,可以處理的數(shù)據(jù)流就越大。

另一個 GPU 的魔法是特殊數(shù)學函數(shù)可通過硬件加速享甸。非常復雜的數(shù)學操作可以直接被微芯片解決截碴,而無須通過軟件。這就表示可以有更快的三角和矩陣運算 —— 和電流一樣快蛉威。

1.3 Hello world!

“Hello world!”通常都是學習一個新語言的第一個例子日丹。這是一個非常簡單,只有一行的程序蚯嫌。它既是一個熱情的歡迎哲虾,也傳達了編程所能帶來的可能性。

然而在 GPU 的世界里择示,第一步就渲染一行文字太難了束凑,所以我們改為選擇一個鮮艷的歡迎色,來吧躁起來对妄!

#ifdef GL_ES
precision mediump float;
#endif
void main() {
    gl_FragColor = vec4(1.0,0.0,1.0,1.0);
}

盡管這幾行簡單的代碼看起來不像有很多內(nèi)容湘今,我們還是可以據(jù)此推測出一些知識點:

1.shader 語言 有一個 main 函數(shù),會在最后返回顏色值剪菱。這點和 C 語言很像摩瞎。

2.最終的像素顏色取決于預設(shè)的全局變量 gl_FragColor拴签。

3.這個類 C 語言有內(nèi)建的變量(像gl_FragColor),函數(shù)和數(shù)據(jù)類型旗们。在本例中我們剛剛介紹了vec4(四分量浮點向量)蚓哩。之后我們會見到更多的類型,像 vec3 (三分量浮點向量)和 vec2 (二分量浮點向量)上渴,還有非常著名的:float(單精度浮點型)岸梨, int(整型) 和 bool(布爾型)。

4.如果我們仔細觀察 vec4 類型稠氮,可以推測這四個變元分別響應紅曹阔,綠,藍和透明度通道隔披。同時我們也可以看到這些變量是規(guī)范化的赃份,意思是它們的值是從0到1的。之后我們會學習如何規(guī)范化變量奢米,使得在變量間map(映射)數(shù)值更加容易抓韩。

5.另一個可以從本例看出來的很重要的類 C 語言特征是,預處理程序的宏指令鬓长。宏指令是預編譯的一部分谒拴。有了宏才可以 #define (定義)全局變量和進行一些基礎(chǔ)的條件運算(通過使用 #ifdef 和 #endif)。所有的宏都以 # 開頭涉波。預編譯會在編譯前一刻發(fā)生英上,把所有的命令復制到 #defines 里,檢查#ifdef 條件句是否已被定義怠蹂, #ifndef 條件句是否沒有被定義善延。在我們剛剛的“hello world!”的例子中,我們在第2行檢查了 GL_ES 是否被定義城侧,這個通常用在移動端或瀏覽器的編譯中易遣。

6.float類型在 shaders 中非常重要,所以精度非常重要嫌佑。更低的精度會有更快的渲染速度豆茫,但是會以質(zhì)量為代價。你可以選擇每一個浮點值的精度屋摇。在第一行(precision mediump float;)我們就是設(shè)定了所有的浮點值都是中等精度揩魂。但我們也可以選擇把這個值設(shè)為“低”(precision lowp float;)或者“高”(precision highp float;)。

7.最后可能也是最重要的細節(jié)是炮温,GLSL 語言規(guī)范并不保證變量會被自動轉(zhuǎn)換類別火脉。這句話是什么意思呢?顯卡的硬件制造商各有不同的顯卡加速方式,但是卻被要求有最精簡的語言規(guī)范倦挂。因而畸颅,自動強制類型轉(zhuǎn)換并沒有包括在其中。在我們的“hello world!”例子中方援,vec4 精確到單精度浮點没炒,所以應被賦予 float 格式。但是如果你想要代碼前后一致犯戏,不要之后花費大量時間 debug 的話送火,最好養(yǎng)成在 float 型數(shù)值里加一個 . 的好習慣。如下這種代碼就可能不能正常運行:

1.4 運行我們的 shader

現(xiàn)在你可能躍躍欲試先匪,想在你熟悉的平臺上小試牛刀了种吸。
1.你可以下載glslViewer。這個 MacOS+樹莓派程序直接在終端運行.
2.如果你想用 WebGL 顯示 shader呀非,并不關(guān)心其他平臺骨稿,你可以用glslCanvas
3.這里提安利個桌面小工具ShaderDesigner,這個 MacOS的程序可以直接調(diào)用攝像頭并預覽姜钳,加入你的shader代碼來創(chuàng)造有趣的效果。

2.1 運行我們的 ShaderDesigner.app

下載ShaderDesigner并在MacOS環(huán)境下運行如下圖

ShaderDesigner.app 運行效果

我們來看下Fragment Shader代碼

varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
void main()
{
    gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}
  1. textureCoordinate 視口分辨率(以像素計)
  2. inputImageTexture 接收一個圖片的引用形耗,當做2D的紋理哥桥,這個數(shù)據(jù)類型就是smpler2D。
  3. u_time shader 運行時間(以秒計)
  4. texture2D(Texture,v_texCoord)方法
  5. 最終的像素顏色取決于vec4類型 gl_FragColor
2.2 小試牛刀解決鏡像翻轉(zhuǎn)
varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
void main()
void main()
{
    vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);
    gl_FragColor = texture2D(inputImageTexture, st);
}
2.3 試試抖音的靈魂出竅

先來看看效果吧(gif加載請等待??)激涤。

抖音-靈魂出竅.gif

varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
vec2 scaleFromCenter(vec2 coord, float scale) {
  if (scale > 1.0 || scale < 0.0) { return coord; }
  vec2 scaleCenter = vec2(0.5);
  return (coord - scaleCenter) * scale + scaleCenter;
}
void main()
{
    vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);
    float scale = 1.0 - mod(u_time * 1.4, 0.8) + 0.4;
    if (scale < 0.0) {
    gl_FragColor = texture2D(inputImageTexture, st);
    return;
    }
    vec2 newCoord = scaleFromCenter(st, scale);
    float colorScale = scale * 0.5;
    vec4 resultColor = texture2D(inputImageTexture, st) * (1.0 - colorScale + 0.2);
    vec4 newCoordColor = texture2D(inputImageTexture, newCoord) * (colorScale - 0.2);
    vec4 result = (resultColor + newCoordColor);
    gl_FragColor = result;
}
2.4 試試抖音的抖動效果吧

先來看看效果吧(gif加載請等待??)

抖音-抖動.gif

varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
vec2 scaleFromCenter(vec2 coord, float scale) {
  if (scale > 1.0 || scale < 0.0) { return coord; }
  vec2 scaleCenter = vec2(0.55, 0.45);
  return (coord - scaleCenter) * scale + scaleCenter;
}

vec2 scaleFromCenter2(vec2 coord, float scale) {
  if (scale > 1.0 || scale < 0.0) { return coord; }
  vec2 scaleCenter = vec2(0.5);
  return (coord - scaleCenter) * scale + scaleCenter;
}

vec2 scaleFromCenter3(vec2 coord, float scale) {
  if (scale > 1.0 || scale < 0.0) { return coord; }
  vec2 scaleCenter = vec2(0.45);
  return (coord - scaleCenter) * scale + scaleCenter;
}
void main()
{
     vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);
    float scale = 1.0 - mod(u_time * 1.2, 0.8) + 0.5;
    if (scale < 0.0) {
    gl_FragColor = texture2D(inputImageTexture, st);
    return;
    }
    vec2 newCoord = scaleFromCenter(st, scale);
    vec4 result = texture2D(inputImageTexture, newCoord);
    vec2 newCoord2 = scaleFromCenter2(st, scale);
    vec4 result2 = texture2D(inputImageTexture, newCoord2);
    vec2 newCoord3 = scaleFromCenter3(st, scale);
    vec4 result3 = texture2D(inputImageTexture, newCoord3);
    vec4 xx = result * vec4(0.0,0.0,1.0,1.0) +
    result2 * vec4(0.0,1.0,0.0,1.0) +
    result3 * vec4(1.0,0.0,0.0,1.0) ;
   gl_FragColor = vec4(xx.rgb, 1.0);
}

3.1用Shader創(chuàng)造一些新鮮有趣的效果吧—四合一拟糕。
有趣的四合一
varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
vec2 scaleFromCenter2D(vec2 coord, vec2 scale) {
  vec2 scaleCenter = scale;
  vec2 st = coord - scaleCenter;
  st = st * scale + scaleCenter;
  if (st.x < 0.0) {
    st.x = mod(st.x, 1.0);
  }
  else if (st.x > 1.0) {
    st.x = fract(st.x);
  }
  if (st.y < 0.0) {
    st.y = mod(st.y, 1.0);
  }
  else if (st.y > 1.0) {
    st.y = fract(st.y);
  }
  return st;
}

void main()
{
    vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);
    st = scaleFromCenter2D(st, vec2(2.0));
    gl_FragColor = texture2D(inputImageTexture, st);

}

3.2用Shader創(chuàng)造一些新鮮有趣的效果吧—三合一。
有趣的三合一
varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
void main()
{
    vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);

    if (st.y <=  0.333){
       gl_FragColor = texture2D(inputImageTexture, vec2(st.x,st.y + 0.333));
    }else if(st.y > 0.333 && st.y<= 0.666){
       gl_FragColor = texture2D(inputImageTexture, st);
    }else{
       gl_FragColor = texture2D(inputImageTexture, vec2(st.x,st.y - 0.333));
    }
}
3.3用Shader創(chuàng)造一些新鮮有趣的效果吧—中間鏡像倦踢。
有趣的中間鏡像
varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
void main()
{
    vec2 st = vec2(1.0 - textureCoordinate.x, textureCoordinate.y);
    if (st.x <=  0.5){
        gl_FragColor = texture2D(inputImageTexture, vec2(  0.5 - st.x,st.y ));
       }else{
        gl_FragColor = texture2D(inputImageTexture, vec2( st.x - 0.5,st.y ));
       }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末送滞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辱挥,更是在濱河造成了極大的恐慌犁嗅,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晤碘,死亡現(xiàn)場離奇詭異褂微,居然都是意外死亡,警方通過查閱死者的電腦和手機园爷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門宠蚂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人童社,你說我怎么就攤上這事求厕。” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵呀癣,是天一觀的道長美浦。 經(jīng)常有香客問我,道長十艾,這世上最難降的妖魔是什么抵代? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮忘嫉,結(jié)果婚禮上荤牍,老公的妹妹穿的比我還像新娘。我一直安慰自己庆冕,他們只是感情好康吵,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著访递,像睡著了一般晦嵌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拷姿,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天惭载,我揣著相機與錄音,去河邊找鬼响巢。 笑死描滔,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的踪古。 我是一名探鬼主播含长,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼伏穆!你這毒婦竟也來了拘泞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤枕扫,失蹤者是張志新(化名)和其女友劉穎陪腌,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铡原,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡偷厦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了燕刻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片只泼。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖卵洗,靈堂內(nèi)的尸體忽然破棺而出请唱,到底是詐尸還是另有隱情弥咪,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布十绑,位于F島的核電站聚至,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏本橙。R本人自食惡果不足惜扳躬,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望甚亭。 院中可真熱鬧贷币,春花似錦、人聲如沸亏狰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽暇唾。三九已至促脉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間策州,已是汗流浹背瘸味。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留够挂,地道東北人硫戈。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像下硕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子汁胆,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359