混色與力場

顏色怎麼交融,筆觸怎麼被風吹彎,墨跡怎麼開始流動

目錄

  1. 三種混色模式:Mix / Multiply / Darken
  2. 不同顏色的混色效果比較
  3. 為什麼背景色很重要?
  4. Path Rotation:扭轉筆觸的方向
  5. Flow Effect 八種造型一覽
  6. Last Stroke Only:只影響最後一筆
  7. Spectral:光譜混色模式
1

三種混色模式:Mix / Multiply / Darken

想像你拿兩枝不同顏色的蠟筆重疊畫。「Mix」是直接混在一起(像調色盤上攪勻),「Multiply」是兩色相乘、越疊越深(像兩片有色玻璃重疊),「Darken」是兩色比較、留下較暗的那個(像取最深的影子)。

encode.frag 裡,keyBlendMode 控制每筆新顏色怎麼和畫面上既有的顏色融合:

模式keyBlendModeShader 邏輯視覺效果
Mix0 mix(existing, new, alpha) 新舊色按透明度線性混合,疊越多越飽和
Multiply1 existing * new 顏色相乘,越疊越深、越沉穩
Darken2 min(existing, new) 逐通道取較暗值,保留最深的色調

以下是橘色+藍色兩筆交叉,分別用三種混色模式的效果:

Mix 混色模式 — 橘藍交叉
Mix(線性混合)
Multiply 混色模式 — 橘藍交叉
Multiply(相乘)
Darken 混色模式 — 橘藍交叉
Darken(取暗)

Shader 裡的關鍵程式碼

// encode.frag — keyBlendMode 決定混合方式 if (keyBlendMode == 0) { // Mix: lerp 線性混合 blended = mix(existing, newColor, alpha); } else if (keyBlendMode == 1) { // Multiply: 顏色相乘(像有色玻璃疊加) blended = existing * newColor; } else { // Darken: 取每個通道的較暗值 blended = min(existing, newColor); }
2

不同顏色的混色效果比較

同一種混色模式搭配不同顏色會有截然不同的視覺結果。以下是三組顏色的交叉比對:

橘色 + 藍色

橘藍 Mix
Mix
橘藍 Multiply
Multiply
橘藍 Darken
Darken

紅色 + 青色

紅青 Mix
Mix
紅青 Multiply
Multiply
紅青 Darken
Darken

灰色 + 灰色(單色參考)

灰灰 Mix
Mix
灰灰 Multiply
Multiply
灰灰 Darken
Darken

觀察重點

  • 互補色(橘藍、紅青)在 Multiply 模式下,交疊處顏色急速變暗——因為互補色的 RGB 值相乘後數字很小
  • Darken 會保留兩色中各通道最暗的值,交疊處常出現第三種色相
  • 同色(灰灰)可以清楚看出三種模式對「濃度」的不同影響,排除了色彩干擾
3

為什麼背景色很重要?

想像你拿彩色玻璃紙疊在一張白紙上——看起來像有色光。但疊在黑紙上呢?什麼都看不到了。Blend Mode 的效果完全取決於「底色」是什麼。

encode.frag 裡有一個關鍵判斷——isWhiteBase

isWhiteBase 判斷

Shader 會檢查當前像素的底色是否「接近白色」。如果是,就走加法合成的路線(適合白底);如果不是,才用 keyBlendMode 的混色邏輯。

// 判斷條件(encode.frag) bool isWhiteBase = hasWhiteTag // 有白色筆刷標記 || isHighLuminanceGray // 高亮度灰(接近白色) || isVeryBright; // 非常亮的顏色

這就是為什麼純白或純黑背景上,Multiply 和 Darken 效果不明顯——白底被判定為 isWhiteBase,走了加法路線,三種模式效果幾乎一樣。

最佳實踐

如果你想看到明顯的 Blend Mode 差異,請選擇中間色調背景,例如:

  • 米色 [222, 212, 195](預設背景)
  • 暖灰 [180, 160, 140](本頁截圖用的背景)
  • 冷灰 [150, 160, 170]

避免使用純白 [255, 255, 255] 或純黑 [0, 0, 0] ——這些背景會讓 Blend Mode 的差異消失。

4

Path Rotation:扭轉筆觸的方向

想像你拿著一枝寬扁的毛筆寫字。筆尖不旋轉的話(Mode 1),筆觸方向就是你手移動的方向——平穩、一致。稍微旋轉(Mode 2)就像書法家自然地轉腕,筆觸有微妙的方向變化。大幅旋轉(Mode 3)就像用力甩動手腕,筆觸劇烈扭轉。

pathRotation 控制筆觸在行進過程中,噪波如何擾動方向。數值越大,扭轉越劇烈:

模式pathRotation效果
Mode 10無旋轉,粒子沿著路徑方向直線前進
Mode 25 ~ 10微幅擾動,像自然書寫的手腕轉動
Mode 310 ~ 25大幅扭轉,筆觸邊緣散開、形狀更野性
Path Rotation Mode 1 — 無旋轉
Mode 1(pathRotation = 0)
Path Rotation Mode 2 — 微幅擾動
Mode 2(pathRotation = 7)
Path Rotation Mode 3 — 大幅扭轉
Mode 3(pathRotation = 17)

技術原理

在 sketch.js 的繪製迴圈中,每個粒子的噴射方向會加上一個基於 Perlin Noise 的偏移量。pathRotation 是這個偏移量的強度係數:

// pathRotation 越大,噪波對方向的影響越強 let noiseAngle = noise(x * scale, y * scale) * pathRotation; particleDir += noiseAngle;

Mode 1 時 noiseAngle 為零,粒子沿原始方向直行;Mode 3 時噪波大幅偏轉方向,筆觸邊緣像被風吹散。

5

Flow Effect 八種造型一覽

Flow 力場是 InkField 最強大的後處理特效(詳細原理請見第 6 篇:特效工廠)。這裡展示每種 blendType 在同一組筆觸上的實際效果:

Flow Type 0 — 基礎模式
0:基礎模式
Flow Type 2 — 同心圓
2:同心圓
Flow Type 3 — 縱向
3:縱向
Flow Type 4 — 橫向
4:橫向
Flow Type 5 — 花紋
5:龜裂花紋
Flow Type 6 — 馬賽克碎片
6:馬賽克碎片
Flow Type 7 — 漩渦
7:漩渦
Flow Type 8 — 細胞
8:細胞

每種造型的特色

blendType名稱位移方式
0基礎Simplex Noise 隨機位移,搭配 globalStyle 分區強度調整
2同心圓兩個隨機圓心產生徑向漣漪,用 mix() 取代基礎噪聲——圓心附近漣漪主導,外圍保留有機噪聲
3縱向多層 sin/cos/tan 組合,產生垂直紋路
4橫向和縱向相似但翻轉 90°,產生水平紋路
5龜裂花紋雙層 Voronoi/cellular 噪聲驅動,cell 邊界處產生強位移形成龜裂/碎裂紋理,cell 內部保留噪聲
6馬賽克碎片畫面切成隨機大小的格子,每格獨立隨機偏移——像打碎的磁磚各自位移,格線邊界清晰
7漩渦兩個渦心從原始座標計算極座標旋轉,Gaussian 衰減——靠近渦心漩渦主導,遠離渦心過渡到噪聲
8細胞Voronoi 細胞紋路+Simplex 擾動,像顯微鏡下的組織

觀察重點

所有圖片使用相同的兩筆(黑色+白色)作為基礎,只改變 Flow 的 blendType。注意每種模式如何把同一組筆觸變成完全不同的視覺效果——這就是 Flow 力場的威力。

6

Last Stroke Only:只影響最後一筆

想像你在紙上畫了十筆。開啟「Last Stroke Only」就像用玻璃紙蓋住前九筆,讓 Flow 的風只吹動最後一筆——其他筆觸完全不受影響。關閉的話,所有筆觸都會被風吹動。
Last Stroke Only OFF — 所有筆觸都受 Flow 影響
OFF — 所有筆觸都受 Flow 影響
Last Stroke Only ON — 只有最後一筆受影響
ON — 只有最後一筆受影響

技術實作

flowEffectLastStrokeOnly 為 true 時,flow.frag 會利用 flowEffectStrokeBounds(最後一筆的矩形邊界)來限制 Flow 效果的作用範圍。只有落在這個邊界內的像素才會被位移。

// flow.frag — 只在最後一筆的邊界內套用位移 if (lastStrokeOnly) { bool inBounds = all(greaterThan(coord, bounds.xy)) && all(lessThan(coord, bounds.zw)); if (!inBounds) displace = vec2(0.0); }

何時使用?

  • OFF:適合一次性的全畫面效果——畫完所有筆觸,最後統一套用 Flow
  • ON:適合逐筆控制——每畫一筆就套用 Flow,讓每筆有獨立的力場效果,同時保護之前的筆觸不被破壞
7

Spectral:光譜混色模式

想像你把紅色顏料和黃色顏料擠在調色盤上攪勻——你期望看到橙色。但如果用 Multiply(RGB 相乘),紅×黃的結果是深黑色,因為 RGB 數學不理解「顏料」。Spectral 模式把顏色轉換成 38 個波長的光譜反射率曲線,在物理世界的維度上混合,再轉回 RGB——紅+黃真的變成橙色。

為什麼需要光譜混色?

RGB 是光的加法模型(紅光+綠光=黃光),但顏料是減法模型(紅顏料吸收綠光,黃顏料吸收藍光,混合後只剩橙光)。用 RGB 的 Multiply 模擬顏料混色,互補色(如黃+藍)會變成接近黑色,而非物理正確的綠色。

Spectral 模式透過光譜反射率曲線解決這個問題:每個顏色被展開為 380nm–730nm 共 38 個波長的反射率,在這個空間中混合後再轉回螢幕色彩。

三次迭代的演進

v1:原始 Kubelka-Munk(失敗)

直接使用 spectral.js 的 KM 公式,含 luminance 權重。問題:亮色壓過暗色(黃色濃度 13× 藍色),A/B 測試看不出與 Multiply 的差異。

v2:Flat KM(部分成功)

移除 luminance 權重,50/50 等權混色。互補色成功(黃+藍=綠、紅+藍=紫),但類似色失敗(紅+黃=更紅,不是橙色)。

根因:KS 函數 (1-R)²/(2R) 在低反射率時產生極大的吸收係數。紅色在綠色波段 R≈0.05 → KS≈9.0;黃色 R≈0.9 → KS≈0.006。平均後 KS=4.5,反轉回反射率只有 0.026——紅色的吸收完全壓制了黃色的反射。

v3:加權幾何平均(當前版本)

棄用 KM 的 KS/KM 轉換,改用反射率空間的加權幾何平均:

sR[i] = pow(R_canvas[i], 0.35) * pow(R_brush[i], 0.65); // canvas = 先畫的(35% 權重) // brush = 後畫的(65% 權重)

兩個設計決策

  • 幾何平均取代 KM:保留減法混色特性(兩色都吸收的波段反射率降低),但不會讓一個顏色的極端吸收壓制另一個
  • 加權 0.35/0.65:後畫的顏色對明度影響更大——亮黃色蓋暗紅色會偏亮橙色,暗紅色蓋亮黃色會偏暗橙色,符合「最後一層顏料主導外觀」的直覺

混色結果對照

色彩組合 Multiply Spectral
黃 + 藍 深黑 綠色
紅 + 藍 深黑 紫色
紅 + 黃 深紅 橙色

技術管線

// encode.frag — Spectral 混色路徑(!isWhiteBase 時) // 1. sRGB → 線性光 vec3 lrgb1 = spectral_srgb_to_linear(oldColor); vec3 lrgb2 = spectral_srgb_to_linear(adjustedColor); // 2. 線性光 → 38-band 反射率曲線 float sR1[38], sR2[38]; spectral_linear_to_reflectance(lrgb1, sR1); spectral_linear_to_reflectance(lrgb2, sR2); // 3. 加權幾何平均(後畫的顏色權重 65%) float sR[38]; for (int i = 0; i < 38; i++) { sR[i] = pow(sR1[i], 0.35) * pow(sR2[i], 0.65); } // 4. 反射率 → XYZ → sRGB vec3 targetColor = spectral_xyz_to_srgb( spectral_reflectance_to_xyz(sR)); // 5. 彩度微調 + 與畫布混合 targetColor = hsb_boost(targetColor, sat * 1.2); result = mix(oldColor, targetColor, intensity);

致謝

光譜反射率係數與 XYZ 色匹配數據來自 spectral.js(Ronald van Wijnen,MIT 授權)。光譜混色整合至繪畫 shader 的架構受 p5.brush(Alejandro Campos,MIT 授權)啟發。

← 上一篇:特效工廠   |   下一篇:錄影帶的秘密 →

InkField 教學系列 — 用國中生也能懂的方式,認識數位水墨的秘密