セピア調変換
今回のサンプルの実行結果
哀愁のある色合いに
前回はグレイスケール変換を扱いました。
レンダリングされるモデルだけでなく、背景をクリアした領域の色まで全てモノクロに変換するために、一度フレームバッファに対してレンダリングを行なった後、それをテクスチャとしてフラグメントシェーダでモノクロに変換しました。
一度フレームバッファを経由する都合上、必然的に 2 パスでのレンダリングが必要になるのでしたね。そして、canvas にぴったりと収まるようにレンダリングを行なうために、正射影による座標変換が必要になるというのもポイントでした。
今回は、せっかくなのでグレイスケールだけでなく、セピア調に色変換を行なえるフィルタをフラグメントシェーダで作成してみます。ほとんどのコードは前回の使いまわしですが、フラグメントシェーダを上手に使うことで様々なフィルタが掛けられることを実感してもらえればと思います。
そもそもセピアとは
セピアというのは、本来イカ墨のことを指す言葉らしいです。グレイスケールと同様に、いわゆる色の濃淡だけで表現されるセピアですが、不思議とレトロな風合いというか、味わいのある色調に感じられるのだから不思議です。
セピアは、RGB で表すと(107, 74, 43)というふうになるようです。今回はフラグメントシェーダ内で全てのフラグメントをグレイスケール化した後で、上記の RGB の比率に各フラグメントの値を調整します。
ここまでを読んで勘のいい人なら気づいたかと思いますが、正直今回のサンプルは、グレイスケール化するフラグメントシェーダに少々の変更を加えるだけで実装できてしまいます。というわけで出し惜しみしても仕方ないので、さっくりとシェーダのソースを見てみましょう。
セピア調変換フラグメントシェーダ
precision mediump float;
uniform sampler2D texture;
uniform bool grayScale;
uniform bool sepia;
varying vec2 vTexCoord;
const float redScale = 0.298912;
const float greenScale = 0.586611;
const float blueScale = 0.114478;
const vec3 monochromeScale = vec3(redScale, greenScale, blueScale);
const float sRedScale = 1.07;
const float sGreenScale = 0.74;
const float sBlueScale = 0.43;
const vec3 sepiaScale = vec3(sRedScale, sGreenScale, sBlueScale);
void main(void){
vec4 smpColor = texture2D(texture, vTexCoord);
float grayColor = dot(smpColor.rgb, monochromeScale);
if(grayScale){
smpColor = vec4(vec3(grayColor), 1.0);
}else if(sepia){
vec3 monoColor = vec3(grayColor) * sepiaScale;
smpColor = vec4(monoColor, 1.0);
}
gl_FragColor = smpColor;
}
セピア調への変換では、上記のコードで vec3
型の定数として定義されている sepiaScale
を係数として、グレイスケール化した色に掛け合わせています。
グレイスケール化には、前回使った NTSC 系加重平均法をそのまま使っています。RGB の各色に掛け合わせる NTSC 系加重平均法の係数と、テクスチャから取得した RGB 値とで内積を取っているところがそれですね。
グレイスケール化した色に、さらにセピア調に変換する係数を掛けることで最終的な色を決定します。今回のシェーダではカラー、グレイスケール、セピア調、この三つのタイプでの出力が可能になっています。どのタイプで色を出力するのかは、uniform 変数として入ってきた bool 値によって決まる仕組みです。
ベクトルの内積
余談ですが、そもそもどうしてグレイスケール化に内積を使っているのか、ちょっと疑問に感じる人もいるかもしれません。
3D のプログラミングでは内積を使う場面が結構あります。その代表格はライティングの係数を算出する場合などですね。実は内積の計算というのは非常に単純で、たとえば三次元空間上の座標を表す XYZ という三つの座標情報同士で内積を取るときの計算方法は、展開すると以下のようになります。
dot = x1 * x2 + y1 * y2 + z1 * z2
要は、各要素を掛けたあとに足しているわけですね。
内積の計算方法は三次よりも大きな次元であっても同様の方法で行なわれます。GLSL には dot
という便利なビルトイン関数があるので、上記のように各要素を掛け合わせてから足すような処理を行なう場合には、内積をうまく活用することで高速に処理できることを覚えておくといいでしょう。
まとめ
さて、今回はグレイスケール化のちょっとした改造版でセピア調の変換を行ないました。変更点はフラグメントシェーダに少々の修正を加えるだけでしたので、内容的には簡易なものです。前回の内容を理解できていれば、それほど難しいことはないでしょう。
今回のサンプルでは HTML に埋め込まれた input 要素から値を取得し、カラーでも、グレイスケールでも、またセピア調でも処理できるようにしています。実際に動作するサンプルはいつものように以下のリンクから参照できます。
フラグメントシェーダを使ってフィルタリングする処理は、工夫次第で本当にいろいろなことができます。その可能性を感じていただけたらいいのですが。