メインコンテンツまでスキップ

合成モード (BlendMode) 仕様

OpenMotion の各クリップで指定可能な合成モード(blendMode)の数学的定義と視覚的効果についての詳細な仕様です。


共通の計算アルゴリズム

OpenMotion のレンダラーは、以下の計算手順でピクセルごとの合成を行います(BLEND_RGB 定義がある特殊モードを除く)。

1. 前処理

ソース色 (CsC_s) と背景色 (CdC_d) のアルファ値を分離(Unpremultiply)し、ソースの不透明度 (OO) を適用します。

Cs=src.rgbsrc.a,Cd=dst.rgbdst.aC_s = \frac{src.rgb}{src.a}, \quad C_d = \frac{dst.rgb}{dst.a} As=src.a×O,Ad=dst.aA_s = src.a \times O, \quad A_d = dst.a

const Cs = src.rgb / src.a; // ソース色成分
const Cd = dst.rgb / dst.a; // 背景色成分
const As = src.a * opacity; // 実効ソースアルファ
const Ad = dst.a; // 背景アルファ

2. 重なりの領域計算 (Overlap)

OpenMotion は標準的な非相関オーバーラップを採用しています。

p0=As×Ad(ソース背景)p_0 = A_s \times A_d \quad (\text{ソース} \cap \text{背景}) p1=As×(1Ad)(ソース背景)p_1 = A_s \times (1 - A_d) \quad (\text{ソース} \setminus \text{背景}) p2=Ad×(1As)(背景ソース)p_2 = A_d \times (1 - A_s) \quad (\text{背景} \setminus \text{ソース})

const p0 = As * Ad;
const p1 = As * (1.0 - Ad);
const p2 = Ad * (1.0 - As);

3. 最終色の出力

各モードごとに定義された関数 f(Cs,Cd)f(C_s, C_d) と、重み係数 kk (coeff) を用いて算出します。

Cres=f(Cs,Cd)p0kx+Csp1ky+Cdp2kzC*{res} = f(C_s, C_d) \cdot p_0 \cdot k_x + C_s \cdot p_1 \cdot k_y + C_d \cdot p_2 \cdot k_z Ares=p0kx+p1ky+p2kzA*{res} = p_0 \cdot k_x + p_1 \cdot k_y + p_2 \cdot k_z

const resRGB = f(Cs, Cd) * p0 * coeff.x + Cs * p1 * coeff.y + Cd * p2 * coeff.z;
const resA = p0 * coeff.x + p1 * coeff.y + p2 * coeff.z;
gl_FragColor = vec4(resRGB, resA);

Porter-Duff 演算(アルファ合成)

重ね合わせの論理的な領域を制御します。色成分の変化を伴わず、f(Cs,Cd)f(C_s, C_d)CsC_s または CdC_d を返します。

モード名kk (coeff)f(Cs,Cd)f(C_s, C_d)説明
zero[0,0,0]00何も描画しない。
src[1,1,0]CsC_sソースのみを表示。
dest[1,0,1]CdC_d背景のみを表示。
src-over[1,1,1]CsC_s標準。背景の上にソース。
dest-over[1,1,1]CdC_dソースの下に背景。
src-in[1,0,0]CsC_s背景がある所にのみソース。
dest-in[1,0,0]CdC_dソースがある所にのみ背景。
src-out[0,1,0]00背景がない所にのみソース。
dest-out[0,0,1]00ソースがない所にのみ背景。
src-atop[1,0,1]CsC_s背景の範囲内で上にソース。
dest-atop[1,1,0]CdC_dソースの範囲内で上に背景。
xor[0,1,1]00重なっていない所。

ブレンディング演算

背景とソースの色を計算によって混合します。これらはすべて coeff = [1,1,1] を使用します。

multiply (乗算)

f(Cs,Cd)=Cs×Cdf(C_s, C_d) = C_s \times C_d

f(Cs, Cd) = Cs * Cd

screen (スクリーン)

f(Cs,Cd)=Cs+CdCs×Cdf(C_s, C_d) = C_s + C_d - C_s \times C_d

f(Cs, Cd) = Cs + Cd - Cs * Cd

overlay (オーバーレイ)

f(Cs,Cd)={2CsCd(Cd0.5)12(1Cs)(1Cd)(Cd>0.5)f(C_s, C_d) = \begin{cases} 2C_s C_d & (C_d \le 0.5) \\ 1 - 2(1-C_s)(1-C_d) & (C_d > 0.5) \end{cases}

f(Cs, Cd) = (Cd <= 0.5) ? 2.0*Cs*Cd : 1.0 - 2.0*(1.0-Cs)*(1.0-Cd)

darken (比較(暗))

f(Cs,Cd)=min(Cs,Cd)f(C_s, C_d) = \min(C_s, C_d)

f(Cs, Cd) = Math.min(Cs, Cd)

lighten (比較(明))

f(Cs,Cd)=max(Cs,Cd)f(C_s, C_d) = \max(C_s, C_d)

f(Cs, Cd) = Math.max(Cs, Cd)

color-dodge (覆い焼き)

f(Cs,Cd)=min(1.0,Cd1.0Cs)f(C_s, C_d) = \min(1.0, \frac{C_d}{1.0 - C_s})

f(Cs, Cd) = Math.min(1.0, Cd / (1.0 - Cs))

color-burn (焼き込み)

f(Cs,Cd)=1.0min(1.0,1.0CdCs)f(C_s, C_d) = 1.0 - \min(1.0, \frac{1.0 - C_d}{C_s})

f(Cs, Cd) = 1.0 - Math.min(1.0, (1.0 - Cd) / Cs)

hard-light (ハードライト)

f(Cs,Cd)={2CsCd(Cs0.5)12(1Cs)(1Cd)(Cs>0.5)f(C_s, C_d) = \begin{cases} 2C_s C_d & (C_s \le 0.5) \\ 1 - 2(1-C_s)(1-C_d) & (C_s > 0.5) \end{cases}

f(Cs, Cd) = (Cs <= 0.5) ? 2.0*Cs*Cd : 1.0 - 2.0*(1.0-Cs)*(1.0-Cd)

soft-light (ソフトライト)

f(Cs,Cd)={Cd(12Cs)Cd(1Cd)(Cs0.5)Cd+(2Cs1)(CdCd)(Cs>0.5,Cd>0.25)Cd+(2Cs1)Cd((16Cd12)Cd+3)(Cs>0.5,Cd0.25)f(C_s, C_d) = \begin{cases} C_d - (1-2C_s)C_d(1-C_d) & (C_s \le 0.5) \\ C_d + (2C_s-1)(\sqrt{C_d}-C_d) & (C_s > 0.5, C_d > 0.25) \\ C_d + (2C_s-1)C_d((16C_d-12)C_d+3) & (C_s > 0.5, C_d \le 0.25) \end{cases}

f(Cs, Cd) = (Cs <= 0.5) ?
Cd - (1.0 - 2.0*Cs) * Cd * (1.0 - Cd) :
(Cd > 0.25) ?
Cd + (2.0*Cs - 1.0) * (Math.sqrt(Cd) - Cd) :
Cd + (2.0*Cs - 1.0) * Cd * ((16.0*Cd - 12.0) * Cd + 3.0)

difference (差分)

f(Cs,Cd)=CdCsf(C_s, C_d) = |C_d - C_s|

f(Cs, Cd) = Math.abs(Cd - Cs)

exclusion (除外)

f(Cs,Cd)=Cs+Cd2CsCdf(C_s, C_d) = C_s + C_d - 2C_s C_d

f(Cs, Cd) = Cs + Cd - 2.0 * Cs * Cd

invert (階調反転)

f(Cs,Cd)=1.0Cdf(C_s, C_d) = 1.0 - C_d

f(Cs, Cd) = 1.0 - Cd

invert-rgb

f(Cs,Cd)=Cs(1.0Cd)f(C_s, C_d) = C_s(1.0 - C_d)

f(Cs, Cd) = Cs * (1.0 - Cd)

linear-dodge (線形覆い焼き)

f(Cs,Cd)=Cs+Cdf(C_s, C_d) = C_s + C_d

f(Cs, Cd) = Cs + Cd

linear-burn (線形焼き込み)

f(Cs,Cd)=Cs+Cd1.0f(C_s, C_d) = C_s + C_d - 1.0

f(Cs, Cd) = Cs + Cd - 1.0

vivid-light (ビビットライト)

f(Cs,Cd)={1min(1,1Cd2Cs)(Cs0.5)min(1,Cd2(1Cs))(Cs>0.5)f(C_s, C_d) = \begin{cases} 1 - \min(1, \frac{1-C_d}{2C_s}) & (C_s \le 0.5) \\ \min(1, \frac{C_d}{2(1-C_s)}) & (C_s > 0.5) \end{cases}

f(Cs, Cd) = (Cs <= 0.5) ?
1.0 - Math.min(1.0, (1.0 - Cd) / (2.0 * Cs)) :
Math.min(1.0, Cd / (2.0 * (1.0 - Cs)))

linear-light (リニアライト)

f(Cs,Cd)=2Cs+Cd1.0f(C_s, C_d) = 2C_s + C_d - 1.0

f(Cs, Cd) = 2.0 * Cs + Cd - 1.0

pin-light (ピンライト)

c=2Cs1c = 2C_s - 1 f(Cs,Cd)=min({c(cCd)c+1(c>Cd),Cd)f(C_s, C_d) = \min\left( \begin{cases} c & (c \le C_d) \\ c + 1 & (c > C_d) \end{cases}, \quad C_d \right)

const c = 2.0 * Cs - 1.0;
f(Cs, Cd) = Math.min((c <= Cd ? c : c + 1.0), Cd);

hard-mix (ハードミックス)

f(Cs,Cd)={0(Cs+Cd1.0)1(Cs+Cd>1.0)f(C_s, C_d) = \begin{cases} 0 & (C_s + C_d \le 1.0) \\ 1 & (C_s + C_d > 1.0) \end{cases}

f(Cs, Cd) = (Cs + Cd <= 1.0) ? 0.0 : 1.0;

HSL演算

色相 (HH), 彩度 (SS), 輝度 (LL) を用いた合成です。

hue (色相)

f(Cs,Cd)=SetLumSat(Cs,Sat(Cd),Lum(Cd))f(C_s, C_d) = SetLumSat(C_s, Sat(C_d), Lum(C_d))

f(Cs, Cd) = SetLumSat(Cs, Cd, Cd)

saturation (彩度)

f(Cs,Cd)=SetLumSat(Cd,Sat(Cs),Lum(Cd))f(C_s, C_d) = SetLumSat(C_d, Sat(C_s), Lum(C_d))

f(Cs, Cd) = SetLumSat(Cd, Cs, Cd)

color (カラー)

f(Cs,Cd)=SetLum(Cs,Lum(Cd))f(C_s, C_d) = SetLum(C_s, Lum(C_d))

f(Cs, Cd) = SetLum(Cs, Cd)

luminosity (輝度)

f(Cs,Cd)=SetLum(Cd,Lum(Cs))f(C_s, C_d) = SetLum(C_d, Lum(C_s))

f(Cs, Cd) = SetLum(Cd, Cs)

特殊演算 (RGBA 直接合成)

add (plus / 加算)

f(src,dst)=src+dstf(src, dst) = src + dst

f(src, dst) = src + dst

subtract (減算)

f(src,dst)=dstsrcf(src, dst) = dst - src

f(src, dst) = dst - src

add-darker

アルファ値を考慮しつつ、各チャンネルの「暗さ」を合算して差し引く特殊な加算合成です。

Ares=min(1.0,As+Ad)A*{res} = \min(1.0, A_s + A_d) Rres=max(0.0,Ares((AsRs)+(AdRd)))R*{res} = \max(0.0, A*{res} - ((A_s - R_s) + (A_d - R_d))) Gres=max(0.0,Ares((AsGs)+(AdGd)))G*{res} = \max(0.0, A*{res} - ((A_s - G_s) + (A_d - G_d))) Bres=max(0.0,A_res((AsBs)+(AdBd)))B*{res} = \max(0.0, A\_{res} - ((A_s - B_s) + (A_d - B_d)))

const resA = min(1.0, src.a + dst.a);
const resR = max(0.0, resA - (src.a - src.r + (dst.a - dst.r)));
const resG = max(0.0, resA - (src.a - src.g + (dst.a - dst.g)));
const resB = max(0.0, resA - (src.a - src.b + (dst.a - dst.b)));
return vec4(resR, resG, resB, resA);

contrast (コントラスト)

背景のコントラストをソースを基準に強調します。

C_res=Ad2+2(CdAd2)(CsAs2)C\_{res} = \frac{A_d}{2} + 2(C_d - \frac{A_d}{2})(C_s - \frac{A_s}{2})

f(src, dst).rgb =
dst.a * 0.5 + 2.0 * (dst.rgb - dst.a * 0.5) * (src.rgb - src.a * 0.5);

red / green / blue (チャンネル抽出)

f(src,dst)=(Sr,Dg,Db,Da)(red の場合)f(src, dst) = (S_r, D_g, D_b, D_a) \quad (\text{red の場合})

f(src, dst) = vec4(src.r, dst.g, dst.b, dst.a)