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

_Hahn_發表於2018-10-22

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

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

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

同樣的,當我們希望通過 Shader 給圖片增加不一樣的濾鏡效果時,圖層混合模式將非常適用。

一、定義

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

混合模式是影像處理技術中的一個技術名詞,不僅用於廣泛使用的 Photoshop 中,也應用於 After Effect、illustrator、 Dreamweaver、Fireworks 等軟體。主要功效是可以用不同的方法將物件顏色與底層物件的顏色混合。當您將一種混合模式應用於某一物件時,在此物件的圖層或組下方的任何物件上都可看到混合模式的效果。 —— 百度百科

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/ (圖片用的不好,不太好看出效果)

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

二、使用

讓人欣喜的是,我們不用重複的去實現上面的邏輯了,這個 Github 庫已經幫我們實現了大部分的混合模式:github.com/jamieowen/g… Shader 都可以直接看到:

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

什麼情況下需要圖層混合模式?下面舉個抖音的例子,可以看到這個視訊下面有一個叫做「霓虹」的特效:

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

實際應用到效果是這樣的:

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

那我們可以怎麼來實現呢?首先實現出一個霓虹的效果來,簡單來說就是一個邊緣羽化的圓形,如下所示:

// 封裝了一個函式
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.);
}
複製程式碼

效果如下:

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

這個時候,這個效果可以理解為 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.);
}
複製程式碼

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

看樣子還行啊,但是如果 Base Layer 是白色背景,則會出現一些問題:

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

由於使用了加法,符合加色系的規則,顏色的混合最終會往最亮的顏色靠攏,當任何顏色跟一個趨近於白色底相加,都會越來越亮,失去了原來的濾鏡顏色。那加法不能同時適用於暗色底和白色底。乘法呢?如果要用乘法,首先必須把 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.);
}
複製程式碼

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

然後使用乘法(雖然不太好看,但好歹是上了色):

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

再看看暗色底是什麼表現:

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

簡單來說,通過乘法計算顏色實際上是減色系的操作。顏色的混合只會越來越暗,濾鏡並不能帶來更鮮活的表現。所以不管是加法或是乘法,都沒有辦法將濾鏡和底圖很好的融合起來。所以這個時候,圖層混合模式才這麼重要。

當設計師拿到 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.);
}
複製程式碼

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

這個效果是符合預期的。實際上我們在抖音上得到的「霓虹」效果也是這樣:淺色底效果較不明顯,而深色底較明顯。所以可以簡單到猜測:抖音的這個特效同樣是通過濾色的方式來新增霓虹效果。

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

三、適用場景

上面通過一個簡單的案例來說明了混合模式的使用,實際上通過這些圖層混合模式,我們可以得到一批能直接上線的濾鏡效果了。然而並非所有情況都適合圖層混合模式,比如在轉場上,mix()會更適合於兩個圖層之間的過渡融合。

圖層混合模式更適合於那種 區域性效果+背景純色 需要應用在具體影像上的情況。比如上面是一個案例,另外一個案例就是漏光(Light Leak):

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

或者鏡頭光暈(Lens flare):

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

相關文章