有趣的Shader

目錄

1.Shader簡(jiǎn)單介紹以及入門。
2.抖音特效經(jīng)典濾鏡實(shí)現(xiàn)(包含靈魂出竅、抖動(dòng))。
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ì)算機(jī)繪圖的經(jīng)驗(yàn),你就知道在這個(gè)過程中你需要畫一個(gè)圓,然后一個(gè)長(zhǎng)方形,一條線,一些三角形……直到畫出你想要的圖像。這個(gè)過程很像用手寫一封信或一本書 —— 都是一系列的指令,需要你一件一件完成。
Shaders 也是一系列的指令,但是這些指令會(huì)對(duì)屏幕上的每個(gè)像素同時(shí)下達(dá)。也就是說,你的代碼必須根據(jù)像素在屏幕上的不同位置執(zhí)行不同的操作。就像活字印刷,你的程序就像一個(gè) function(函數(shù)),輸入位置信息,輸出顏色信息,當(dāng)它編譯完之后會(huì)以相當(dāng)快的速度運(yùn)行。


Chinese movable type.
1.2 為什么 shaders 運(yùn)行特別快?

為了回答這個(gè)問題,不得不給大家介紹并行處理(parallel processing)的神奇之處。
想象你的 CPU 是一個(gè)大的工業(yè)管道,然后每一個(gè)任務(wù)都是通過這個(gè)管道的某些東西 —— 就像一個(gè)生產(chǎn)流水線那樣。有些任務(wù)要比別的大,也就是說要花費(fèi)更多時(shí)間和精力去處理。我們就稱它要求更強(qiáng)的處理能力。由于計(jì)算機(jī)自身的架構(gòu),這些任務(wù)需要串行;即一次一個(gè)地依序完成?,F(xiàn)代計(jì)算機(jī)通常有一組四個(gè)處理器,就像這個(gè)管道一樣運(yùn)行,一個(gè)接一個(gè)地處理這些任務(wù),從而使計(jì)算機(jī)流暢運(yùn)行。每個(gè)管道通常被稱為線程。


CPU

CPU視頻游戲和其他圖形應(yīng)用比起別的程序來說,需要高得多的處理能力。因?yàn)樗鼈兊膱D形內(nèi)容需要操作無數(shù)像素。想想看,屏幕上的每一個(gè)像素都需要計(jì)算,而在 3D 游戲中幾何和透視也都需要計(jì)算。
讓我們回到開始那個(gè)關(guān)于管道和任務(wù)的比喻。屏幕上的每個(gè)像素都代表一個(gè)最簡(jiǎn)單的任務(wù)。單獨(dú)來看完成任何一個(gè)像素的任務(wù)對(duì) CPU 來說都很容易,那么問題來了,屏幕上的每一個(gè)像素都需要解決這樣的小任務(wù)!也就是說,哪怕是對(duì)于一個(gè)老式的屏幕(分辨率 800x600)來說,都需要每幀處理480000個(gè)像素,即每秒進(jìn)行14400000次計(jì)算!是的,這對(duì)于微處理器就是大問題了!而對(duì)于一個(gè)現(xiàn)代的 2800x1800 視網(wǎng)膜屏,每秒運(yùn)行60幀,就需要每秒進(jìn)行311040000次計(jì)算。圖形工程師是如何解決這個(gè)問題的?


image

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

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

1.3 Hello world!

“Hello world!”通常都是學(xué)習(xí)一個(gè)新語(yǔ)言的第一個(gè)例子。這是一個(gè)非常簡(jiǎn)單,只有一行的程序。它既是一個(gè)熱情的歡迎,也傳達(dá)了編程所能帶來的可能性。

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

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

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

1.shader 語(yǔ)言 有一個(gè) main 函數(shù),會(huì)在最后返回顏色值。這點(diǎn)和 C 語(yǔ)言很像。

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

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

4.如果我們仔細(xì)觀察 vec4 類型,可以推測(cè)這四個(gè)變?cè)謩e響應(yīng)紅,綠,藍(lán)和透明度通道。同時(shí)我們也可以看到這些變量是規(guī)范化的,意思是它們的值是從0到1的。之后我們會(huì)學(xué)習(xí)如何規(guī)范化變量,使得在變量間map(映射)數(shù)值更加容易。

5.另一個(gè)可以從本例看出來的很重要的類 C 語(yǔ)言特征是,預(yù)處理程序的宏指令。宏指令是預(yù)編譯的一部分。有了宏才可以 #define (定義)全局變量和進(jìn)行一些基礎(chǔ)的條件運(yùn)算(通過使用 #ifdef 和 #endif)。所有的宏都以 # 開頭。預(yù)編譯會(huì)在編譯前一刻發(fā)生,把所有的命令復(fù)制到 #defines 里,檢查#ifdef 條件句是否已被定義, #ifndef 條件句是否沒有被定義。在我們剛剛的“hello world!”的例子中,我們?cè)诘?行檢查了 GL_ES 是否被定義,這個(gè)通常用在移動(dòng)端或?yàn)g覽器的編譯中。

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

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

1.4 運(yùn)行我們的 shader

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

2.1 運(yùn)行我們的 ShaderDesigner.app

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

ShaderDesigner.app 運(yùn)行效果

我們來看下Fragment Shader代碼

varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
void main()
{
    gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}
  1. textureCoordinate 視口分辨率(以像素計(jì))
  2. inputImageTexture 接收一個(gè)圖片的引用,當(dāng)做2D的紋理,這個(gè)數(shù)據(jù)類型就是smpler2D。
  3. u_time shader 運(yùn)行時(shí)間(以秒計(jì))
  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加載請(qǐng)等待??)。

抖音-靈魂出竅.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 試試抖音的抖動(dòng)效果吧

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

抖音-抖動(dòng).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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容