Shader 中的顏色混合模式(Blend Mode)

在以前的文章中說起了 Shader 中的顏色計算,介紹了一些基本的顏色混合計算,然而在實際的 Shader 濾鏡中,簡單到加減乘除並不能很好地還原出咱們想要的效果,mix()也只是其中一個選擇。html

回顧一下,平時拿到設計師提供的設計稿,都能看到他們在 Photoshop 中應用了大量的圖層混合模式,圖層混合模式給設計師提供了豐富的圖層混合效果,大大減小他們對顏色的操做,更天然地混合不一樣圖層。git

一樣的,當咱們但願經過 Shader 給圖片增長不同的濾鏡效果時,圖層混合模式將很是適用。github

1、定義

首先咱們先定義下什麼是混合模式:markdown

混合模式是圖像處理技術中的一個技術名詞,不只用於普遍使用的 Photoshop 中,也應用於 After Effect、illustrator、 Dreamweaver、Fireworks 等軟件。主要功效是能夠用不一樣的方法將對象顏色與底層對象的顏色混合。當您將一種混合模式應用於某一對象時,在此對象的圖層或組下方的任何對象上均可看到混合模式的效果。 —— 百度百科ide

Adobe 也專門介紹了混合模式的相關知識:helpx.adobe.com/cn/photosho…函數

咱們簡單羅列下不一樣的混合模式:工具

  • 正常(Normal):編輯或繪製每一個像素,使其成爲結果色。這是默認模式。(在處理位圖圖像或索引顏色圖像時,「正常」模式也稱爲閾值。)
  • 溶解(Dissolve):編輯或繪製每一個像素,使其成爲結果色。可是,根據任何像素位置的不透明度,結果色由基色或混合色的像素隨機替換。

  • 變暗(Darken):查看每一個通道中的顏色信息,並選擇基色或混合色中較暗的顏色做爲結果色。將替換比混合色亮的像素,而比混合色暗的像素保持不變。
  • 正片疊底(Multiply):查看每一個通道中的顏色信息,並將基色與混合色進行正片疊底。結果色老是較暗的顏色。任何顏色與黑色正片疊底產生黑色。任何顏色與白色正片疊底保持不變。當您用黑色或白色之外的顏色繪畫時,繪畫工具繪製的連續描邊產生逐漸變暗的顏色。這與使用多個標記筆在圖像上繪圖的效果類似。
  • 顏色加深(Color Burn):查看每一個通道中的顏色信息,並經過增長兩者之間的對比度使基色變暗以反映出混合色。與白色混合後不產生變化。
  • 線性加深(Linear Burn):查看每一個通道中的顏色信息,並經過減少亮度使基色變暗以反映混合色。與白色混合後不產生變化。
  • 深色(Darker Color):比較混合色和基色的全部通道值的總和並顯示值較小的顏色。「深色」不會生成第三種顏色(能夠經過「變暗」混合得到),由於它將從基色和混合色中選取最小的通道值來建立結果色。

  • 變亮(Lighten):查看每一個通道中的顏色信息,並選擇基色或混合色中較亮的顏色做爲結果色。比混合色暗的像素被替換,比混合色亮的像素保持不變。
  • 濾色(Screen):查看每一個通道的顏色信息,並將混合色的互補色與基色進行正片疊底。結果色老是較亮的顏色。用黑色過濾時顏色保持不變。用白色過濾將產生白色。此效果相似於多個攝影幻燈片在彼此之上投影。
  • 顏色減淡(Color Dodge):查看每一個通道中的顏色信息,並經過減少兩者之間的對比度使基色變亮以反映出混合色。與黑色混合則不發生變化。
  • 線性減淡/添加(Linear Dodge):查看每一個通道中的顏色信息,並經過增長亮度使基色變亮以反映混合色。與黑色混合則不發生變化。
  • 淺色(Lighter Color):比較混合色和基色的全部通道值的總和並顯示值較大的顏色。「淺色」不會生成第三種顏色(能夠經過「變亮」混合得到),由於它將從基色和混合色中選取最大的通道值來建立結果色。

  • 疊加(Overlay):對顏色進行正片疊底或過濾,具體取決於基色。圖案或顏色在現有像素上疊加,同時保留基色的明暗對比。不替換基色,但基色與混合色相混以反映原色的亮度或暗度。
  • 柔光(Soft Light):使顏色變暗或變亮,具體取決於混合色。此效果與發散的聚光燈照在圖像上類似。若是混合色(光源)比 50% 灰色亮,則圖像變亮,就像被減淡了同樣。若是混合色(光源)比 50% 灰色暗,則圖像變暗,就像被加深了同樣。使用純黑色或純白色上色,能夠產生明顯變暗或變亮的區域,但不能生成純黑色或純白色。
  • 強光(Hard Light):對顏色進行正片疊底或過濾,具體取決於混合色。此效果與耀眼的聚光燈照在圖像上類似。若是混合色(光源)比 50% 灰色亮,則圖像變亮,就像過濾後的效果。這對於向圖像添加高光很是有用。若是混合色(光源)比 50% 灰色暗,則圖像變暗,就像正片疊底後的效果。這對於向圖像添加陰影很是有用。用純黑色或純白色上色會產生純黑色或純白色。
  • 亮光(Vivid Light):經過增長或減少對比度來加深或減淡顏色,具體取決於混合色。若是混合色(光源)比 50% 灰色亮,則經過減少對比度使圖像變亮。若是混合色比 50% 灰色暗,則經過增長對比度使圖像變暗。
  • 線性光(Linear Light):經過減少或增長亮度來加深或減淡顏色,具體取決於混合色。若是混合色(光源)比 50% 灰色亮,則經過增長亮度使圖像變亮。若是混合色比 50% 灰色暗,則經過減少亮度使圖像變暗。
  • 點光(Pin Light):根據混合色替換顏色。若是混合色(光源)比 50% 灰色亮,則替換比混合色暗的像素,而不改變比混合色亮的像素。若是混合色比 50% 灰色暗,則替換比混合色亮的像素,而比混合色暗的像素保持不變。這對於向圖像添加特殊效果很是有用。
  • 實色混合(Hard Mix):將混合顏色的紅色、綠色和藍色通道值添加到基色的 RGB 值。若是通道的結果總和大於或等於 255,則值爲 255;若是小於 255,則值爲 0。所以,全部混合像素的紅色、綠色和藍色通道值要麼是 0,要麼是 255。此模式會將全部像素更改成主要的加色(紅色、綠色或藍色)、白色或黑色。

  • 差值(Difference):查看每一個通道中的顏色信息,並從基色中減去混合色,或從混合色中減去基色,具體取決於哪個顏色的亮度值更大。與白色混合將反轉基色值;與黑色混合則不產生變化。
  • 排除(Exclusion):建立一種與「差值」模式類似但對比度更低的效果。與白色混合將反轉基色值。與黑色混合則不發生變化。
  • 減去(Subtract):查看每一個通道中的顏色信息,並從基色中減去混合色。在 8 位和 16 位圖像中,任何生成的負片值都會剪切爲零。
  • 劃分(Divide):查看每一個通道中的顏色信息,並從基色中劃分混合色。

  • 色相(Hue):用基色的明亮度和飽和度以及混合色的色相建立結果色。
  • 飽和度(Saturation):用基色的明亮度和色相以及混合色的飽和度建立結果色。在無 (0) 飽和度(灰度)區域上用此模式繪畫不會產生任何變化。
  • 顏色(Color):用基色的明亮度以及混合色的色相和飽和度建立結果色。這樣能夠保留圖像中的灰階,而且對於給單色圖像上色和給彩色圖像着色都會很是有用。
  • 明度(Luminosity):用基色的色相和飽和度以及混合色的明亮度建立結果色。此模式建立與「顏色」模式相反的效果。

相關的計算公式,也能夠直接經過這個在線地址查看混合效果:jamieowen.github.io/glsl-blend/ (圖片用的很差,不太好看出效果)oop

2、使用

讓人欣喜的是,咱們不用重複的去實現上面的邏輯了,這個 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.);
}
複製代碼

這個效果是符合預期的。實際上咱們在抖音上獲得的「霓虹」效果也是這樣:淺色底效果較不明顯,而深色底較明顯。因此能夠簡單到猜想:抖音的這個特效一樣是經過濾色的方式來添加霓虹效果。

3、適用場景

上面經過一個簡單的案例來講明瞭混合模式的使用,實際上經過這些圖層混合模式,咱們能夠獲得一批能直接上線的濾鏡效果了。然而並不是全部狀況都適合圖層混合模式,好比在轉場上,mix()會更適合於兩個圖層之間的過渡融合。

圖層混合模式更適合於那種 局部效果+背景純色 須要應用在具體圖像上的狀況。好比上面是一個案例,另一個案例就是漏光(Light Leak):

或者鏡頭光暈(Lens flare):

相關文章
相關標籤/搜索