WebGL で高速 BlendMode

Posted :

WebGL を使えば、色の加算、減算、乗算といったブレンドモードが高速で処理できるというお話。

Canvas2D でもピクセルマニピュレーションができるので、様々なブレンドモードを自前で実装することができるのだが、ピクセル数 * 4色の処理負荷が CPU にかかるためパフォーマンスがよくない。しかし WebGL のフラグメントシェーダーを利用すれば色を高速で処理できる。

demo

WebGL は簡単なことをするにも、初期化の処理が冗長なためコードは長くなってしまうが、実際は大したことはやっていない。

  1. 2つのトライアングルを用意する
  2. 2枚のテクスチャー画像を適用する
  3. フラグメントシェーダーでテクスチャー画像の色をブレンドする

GLSL では、4成分 (Red, Green, Blue, Alpha) をそのまま足したり掛けたりすることができるので、ブレンドの公式に当てはめるだけで処理できる。

考え方は Shader Effects: Blend Modes がとても参考になる。

ブレンドモードの計算式

  • src は vec3 型の前景 (source) テクスチャー色
  • dst は vec3 型の背景 (dist) テクスチャー色

ADD

src + dst;

SUBTRACT

src - dst;

MULTIPLY

src * dst;

DARKEN

min( src, dst );

COLOUR BURN

vec3(
    ( src.r == 0.0 ) ? 0.0 : ( 1.0 - ( ( 1.0 - dst.r ) / src.r ) ),
    ( src.g == 0.0 ) ? 0.0 : ( 1.0 - ( ( 1.0 - dst.g ) / src.g ) ),
    ( src.b == 0.0 ) ? 0.0 : ( 1.0 - ( ( 1.0 - dst.b ) / src.b ) )
 );

LINEAR BURN

( src + dst ) - 1.0;

LIGHTEN

max( src, dst );

SCREEN

( src + dst ) - ( src * dst );

COLOUR DODGE

vec3(
    ( src.r == 1.0 ) ? 1.0 : min( 1.0, dst.r / ( 1.0 - src.r ) ),
    ( src.g == 1.0 ) ? 1.0 : min( 1.0, dst.g / ( 1.0 - src.g ) ),
    ( src.b == 1.0 ) ? 1.0 : min( 1.0, dst.b / ( 1.0 - src.b ) )
);

LINEAR DODGE

src + dst;

OVERLAY

vec3(
    ( dst.r <= 0.5 ) ? ( 2.0 * src.r * dst.r ) : ( 1.0 - 2.0 * ( 1.0 - dst.r ) * ( 1.0 - src.r ) ),
    ( dst.g <= 0.5 ) ? ( 2.0 * src.g * dst.g ) : ( 1.0 - 2.0 * ( 1.0 - dst.g ) * ( 1.0 - src.g ) ),
    ( dst.b <= 0.5 ) ? ( 2.0 * src.b * dst.b ) : ( 1.0 - 2.0 * ( 1.0 - dst.b ) * ( 1.0 - src.b ) )
);

SOFT LIGHT

vec3(
    ( src.r <= 0.5 ) ? ( dst.r - ( 1.0 - 2.0 * src.r ) * dst.r * ( 1.0 - dst.r ) ) : ( ( ( src.r > 0.5 ) && ( dst.r <= 0.25 ) ) ? ( dst.r + ( 2.0 * src.r - 1.0 ) * ( 4.0 * dst.r * ( 4.0 * dst.r + 1.0 ) * ( dst.r - 1.0 ) + 7.0 * dst.r ) ) : ( dst.r + ( 2.0 * src.r - 1.0 ) * ( sqrt( dst.r ) - dst.r ) ) ),
    ( src.g <= 0.5 ) ? ( dst.g - ( 1.0 - 2.0 * src.g ) * dst.g * ( 1.0 - dst.g ) ) : ( ( ( src.g > 0.5 ) && ( dst.g <= 0.25 ) ) ? ( dst.g + ( 2.0 * src.g - 1.0 ) * ( 4.0 * dst.g * ( 4.0 * dst.g + 1.0 ) * ( dst.g - 1.0 ) + 7.0 * dst.g ) ) : ( dst.g + ( 2.0 * src.g - 1.0 ) * ( sqrt( dst.g ) - dst.g ) ) ),
    ( src.b <= 0.5 ) ? ( dst.b - ( 1.0 - 2.0 * src.b ) * dst.b * ( 1.0 - dst.b ) ) : ( ( ( src.b > 0.5 ) && ( dst.b <= 0.25 ) ) ? ( dst.b + ( 2.0 * src.b - 1.0 ) * ( 4.0 * dst.b * ( 4.0 * dst.b + 1.0 ) * ( dst.b - 1.0 ) + 7.0 * dst.b ) ) : ( dst.b + ( 2.0 * src.b - 1.0 ) * ( sqrt( dst.b ) - dst.b ) ) )
);

HARD LIGHT

vec3(
    ( src.r <= 0.5 ) ? ( 2.0 * src.r * dst.r ) : ( 1.0 - 2.0 * ( 1.0 - src.r ) * ( 1.0 - dst.r ) ),
    ( src.g <= 0.5 ) ? ( 2.0 * src.g * dst.g ) : ( 1.0 - 2.0 * ( 1.0 - src.g ) * ( 1.0 - dst.g ) ),
    ( src.b <= 0.5 ) ? ( 2.0 * src.b * dst.b ) : ( 1.0 - 2.0 * ( 1.0 - src.b ) * ( 1.0 - dst.b ) )
);

VIVID LIGHT

vec3(
    ( src.r <= 0.5 ) ? ( 1.0 - ( 1.0 - dst.r ) / ( 2.0 * src.r ) ) : ( dst.r / ( 2.0 * ( 1.0 - src.r ) ) ),
    ( src.g <= 0.5 ) ? ( 1.0 - ( 1.0 - dst.g ) / ( 2.0 * src.g ) ) : ( dst.g / ( 2.0 * ( 1.0 - src.g ) ) ),
    ( src.b <= 0.5 ) ? ( 1.0 - ( 1.0 - dst.b ) / ( 2.0 * src.b ) ) : ( dst.b / ( 2.0 * ( 1.0 - src.b ) ) )
);

LINEAR LIGHT

2.0 * src + dst - 1.0;

PIN LIGHT

vec3(
    ( src.r > 0.5 ) ? max( dst.x, 2.0 * ( src.r - 0.5 ) ) : min( dst.x, 2.0 * src.r ),
    ( src.r > 0.5 ) ? max( dst.y, 2.0 * ( src.g - 0.5 ) ) : min( dst.y, 2.0 * src.g ),
    ( src.b > 0.5 ) ? max( dst.z, 2.0 * ( src.b - 0.5 ) ) : min( dst.z, 2.0 * src.b )
);

DIFFERENCE

abs(dst - src);

EXCLUSION

src + dst - 2.0 * src * dst;