在以前的文章中說起了 Shader 中的顏色計算,介紹了一些基本的顏色混合計算,然而在實際的 Shader 濾鏡中,簡單到加減乘除並不能很好地還原出咱們想要的效果,mix()
也只是其中一個選擇。html
回顧一下,平時拿到設計師提供的設計稿,都能看到他們在 Photoshop 中應用了大量的圖層混合模式,圖層混合模式給設計師提供了豐富的圖層混合效果,大大減小他們對顏色的操做,更天然地混合不一樣圖層。git
一樣的,當咱們但願經過 Shader 給圖片增長不同的濾鏡效果時,圖層混合模式將很是適用。github
首先咱們先定義下什麼是混合模式:markdown
混合模式是圖像處理技術中的一個技術名詞,不只用於普遍使用的 Photoshop 中,也應用於 After Effect、illustrator、 Dreamweaver、Fireworks 等軟件。主要功效是能夠用不一樣的方法將對象顏色與底層對象的顏色混合。當您將一種混合模式應用於某一對象時,在此對象的圖層或組下方的任何對象上均可看到混合模式的效果。 —— 百度百科ide
Adobe 也專門介紹了混合模式的相關知識:helpx.adobe.com/cn/photosho…函數
咱們簡單羅列下不一樣的混合模式:工具
相關的計算公式,也能夠直接經過這個在線地址查看混合效果:jamieowen.github.io/glsl-blend/ (圖片用的很差,不太好看出效果)oop
讓人欣喜的是,咱們不用重複的去實現上面的邏輯了,這個 Github 庫已經幫咱們實現了大部分的混合模式:github.com/jamieowen/g… Shader 均可以直接看到:post
什麼狀況下須要圖層混合模式?下面舉個抖音的例子,能夠看到這個視頻下面有一個叫作「霓虹」的特效:spa
實際應用到效果是這樣的:
那咱們能夠怎麼來實現呢?首先實現出一個霓虹的效果來,簡單來講就是一個邊緣羽化的圓形,以下所示:
// 封裝了一個函數 vec3 drawLeaks(vec2 _uv, vec2 position, vec2 speed, vec2 size, vec3 resolution, vec3 color, float t, vec2 range) { vec2 leakst = _uv; vec2 newsize = normalize(size); newsize /= abs(newsize.x) + abs(newsize.y); leakst -= .5; // 座標系居中 leakst.x *= resolution.x/resolution.y; // 等比例縮放 leakst.x -= position.x; // 位置調整x leakst.y -= position.y; // 位置調整y leakst.x -= speed.x * t * 10.; // 運動速率x leakst.y -= speed.y * t * 10.; // 運動速率y if (newsize.x < newsize.y) // 大小比例調整 leakst.y *= newsize.x / newsize.y; if (newsize.x > newsize.y) leakst.x *= newsize.y / newsize.x; float angle = atan(leakst.y, leakst.x); // 笛卡爾轉極座標 float radius = length(leakst); vec3 finalColor = vec3(smoothstep(range.x, range.y, radius))*color*(1.-t); // 預設size&上色 return finalColor; } void main() { vec3 leakColor = drawLeaks(myst, vec2(.0, .0), vec2(.0, .0), vec2(.0, .0), iResolution, vec3(166./255., 66./255., 65./255.)*1.5, 0., vec2(.3, 0.)); gl_FragColor = vec4(leakColor, 1.); } 複製代碼
效果以下:
這個時候,這個效果能夠理解爲 PS 中的一個「圖層」,咱們把它叫作混合層(Blend Layer),而後咱們須要增長一個基礎層(Base Layer)用於混合,咱們先試試加法:
// 這裏只展現主要代碼 void main() { vec3 leakColor = drawLeaks(myst, vec2(.0, .0), vec2(.0, .0), vec2(.0, .0), iResolution, vec3(166./255., 66./255., 65./255.)*1.5, 0., vec2(.3, 0.)); vec3 texelColor = texture2D(texure, myst).rgb; gl_FragColor = vec4(leakColor + texelColor, 1.); } 複製代碼
看樣子還行啊,可是若是 Base Layer 是白色背景,則會出現一些問題:
因爲使用了加法,符合加色系的規則,顏色的混合最終會往最亮的顏色靠攏,當任何顏色跟一個趨近於白色底相加,都會愈來愈亮,失去了原來的濾鏡顏色。那加法不能同時適用於暗色底和白色底。乘法呢?若是要用乘法,首先必須把 Blend Layer 改爲白色底(不然任何顏色和黑色底相乘都是黑色):
void main() { vec3 leakColor = drawLeaks(myst, vec2(.0, .0), vec2(.0, .0), vec2(.0, .0), iResolution, vec3((255.-255.)/255., (255.-95.)/255., (255.-32.)/255.), 0., vec2(.3, 0.)); gl_FragColor = vec4(leakColor, 1.); } 複製代碼
而後使用乘法(雖然不太好看,但好歹是上了色):
再看看暗色底是什麼表現:
簡單來講,經過乘法計算顏色其實是減色系的操做。顏色的混合只會愈來愈暗,濾鏡並不能帶來更鮮活的表現。因此不論是加法或是乘法,都沒有辦法將濾鏡和底圖很好的融合起來。因此這個時候,圖層混合模式才這麼重要。
當設計師拿到 Blend Layer 和 Base Layer,他們會堅決果斷地給二者添加一個「濾色」的混合模式,爲何他們會這麼作,每每是源於對這個混合模式的瞭解和平常實踐,做爲工程師,達不到他們的第六感,咱們不妨看看「濾色」的定義:
查看每一個通道的顏色信息,並將混合色的互補色與基色進行正片疊底。結果色老是較亮的顏色。用黑色過濾時顏色保持不變。用白色過濾將產生白色。此效果相似於多個攝影幻燈片在彼此之上投影。
濾色的實現是這樣的:
float blendScreen(float base, float blend) { return 1.0-((1.0-base)*(1.0-blend)); } 複製代碼
簡單來講,濾色就是把兩個圖層中較暗的顏色去掉,取較亮的顏色。咱們不妨試試:
float blendScreen(float base, float blend) { return 1.0-((1.0-base)*(1.0-blend)); } vec3 blendScreen(vec3 base, vec3 blend) { return vec3(blendScreen(base.r,blend.r),blendScreen(base.g,blend.g),blendScreen(base.b,blend.b)); } void main() { vec3 leakColor = drawLeaks(myst, vec2(.0, .0), vec2(.0, .0), vec2(.0, .0), iResolution, vec3((255.-255.)/255., (255.-95.)/255., (255.-32.)/255.), 0., vec2(.3, 0.)); vec3 texelColor = texture2D(texture, myst).rgb; gl_FragColor = vec4(blendScreen(texelColor, leakColor), 1.); } 複製代碼
這個效果是符合預期的。實際上咱們在抖音上獲得的「霓虹」效果也是這樣:淺色底效果較不明顯,而深色底較明顯。因此能夠簡單到猜想:抖音的這個特效一樣是經過濾色的方式來添加霓虹效果。
上面經過一個簡單的案例來講明瞭混合模式的使用,實際上經過這些圖層混合模式,咱們能夠獲得一批能直接上線的濾鏡效果了。然而並不是全部狀況都適合圖層混合模式,好比在轉場上,mix()
會更適合於兩個圖層之間的過渡融合。
圖層混合模式更適合於那種 局部效果+背景純色 須要應用在具體圖像上的狀況。好比上面是一個案例,另一個案例就是漏光(Light Leak):
或者鏡頭光暈(Lens flare):