目錄
1.Shader簡單介紹以及入門躁愿。
2.抖音特效經(jīng)典濾鏡實現(xiàn)(包含靈魂出竅、抖動)署尤。
3.用Shader創(chuàng)造一些新鮮有趣的效果吧蝶锋。
1.1 什么是Fragment Shader(片段著色器)?
我們把 shaders 和古騰堡印刷術(shù)相提并論言秸。為什么這樣類比呢软能?更重要的是,什么是 shader举畸?
如果你曾經(jīng)有用計算機繪圖的經(jīng)驗查排,你就知道在這個過程中你需要畫一個圓,然后一個長方形抄沮,一條線跋核,一些三角形……直到畫出你想要的圖像岖瑰。這個過程很像用手寫一封信或一本書 —— 都是一系列的指令,需要你一件一件完成砂代。
Shaders 也是一系列的指令蹋订,但是這些指令會對屏幕上的每個像素同時下達。也就是說刻伊,你的代碼必須根據(jù)像素在屏幕上的不同位置執(zhí)行不同的操作露戒。就像活字印刷,你的程序就像一個 function(函數(shù))捶箱,輸入位置信息智什,輸出顏色信息,當它編譯完之后會以相當快的速度運行丁屎。
1.2 為什么 shaders 運行特別快荠锭?
為了回答這個問題,不得不給大家介紹并行處理(parallel processing)的神奇之處晨川。
想象你的 CPU 是一個大的工業(yè)管道证九,然后每一個任務都是通過這個管道的某些東西 —— 就像一個生產(chǎn)流水線那樣。有些任務要比別的大础爬,也就是說要花費更多時間和精力去處理甫贯。我們就稱它要求更強的處理能力。由于計算機自身的架構(gòu)看蚜,這些任務需要串行叫搁;即一次一個地依序完成。現(xiàn)代計算機通常有一組四個處理器供炎,就像這個管道一樣運行渴逻,一個接一個地處理這些任務,從而使計算機流暢運行音诫。每個管道通常被稱為線程惨奕。
CPU視頻游戲和其他圖形應用比起別的程序來說,需要高得多的處理能力竭钝。因為它們的圖形內(nèi)容需要操作無數(shù)像素梨撞。想想看,屏幕上的每一個像素都需要計算香罐,而在 3D 游戲中幾何和透視也都需要計算卧波。
讓我們回到開始那個關(guān)于管道和任務的比喻。屏幕上的每個像素都代表一個最簡單的任務庇茫。單獨來看完成任何一個像素的任務對 CPU 來說都很容易港粱,那么問題來了,屏幕上的每一個像素都需要解決這樣的小任務旦签!也就是說查坪,哪怕是對于一個老式的屏幕(分辨率 800x600)來說寸宏,都需要每幀處理480000個像素,即每秒進行14400000次計算偿曙!是的氮凝,這對于微處理器就是大問題了!而對于一個現(xiàn)代的 2800x1800 視網(wǎng)膜屏遥昧,每秒運行60幀覆醇,就需要每秒進行311040000次計算。圖形工程師是如何解決這個問題的炭臭?
這個時候,并行處理就是最好的解決方案袍辞。比起用三五個強大的微處理器(或者說“管道”)來處理這些信息鞋仍,用一大堆小的微處理器來并行計算,就要好得多搅吁。這就是圖形處理器(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)境下運行如下圖
我們來看下Fragment Shader代碼
varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform float u_time;
void main()
{
gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}
- textureCoordinate 視口分辨率(以像素計)
- inputImageTexture 接收一個圖片的引用形耗,當做2D的紋理哥桥,這個數(shù)據(jù)類型就是smpler2D。
- u_time shader 運行時間(以秒計)
- texture2D(Texture,v_texCoord)方法
- 最終的像素顏色取決于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加載請等待??)激涤。
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加載請等待??)
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 ));
}
}