Help us understand the problem. What is going on with this article?

Starling2.0でカスタムフィルターを作ってみる:ポスタリゼーションフィルタ

More than 3 years have passed since last update.

Starling2.0から、カスタムレンダリングの仕組みが少し変わりました。カスタムレンダリングの実装方法にはフィルターとメッシュスタイルの2つがあり、一長一短です。公式のガイドにのっとり、作るのが簡単なフィルタの方に手を出してみます。今回は、以前AGALを生で触って勉強していた時に作ったポスタリゼーション(階調飛ばし)のコードをフィルターとして移植してみました。

Starling2.0フィルタの特徴

  • 派手なエフェクトを作成できる
  • 多重にフィルタを重ねがけできる
  • Drawコールが増えるので、処理が重め
  • 作るのが(MeshStyleに比べて)簡単
  • DisplayObjectだけでなく、DisplayObjectContainerにも適用できる

ざっとこんな感じです。MeshStyleについては別途投稿します。

ポスタリゼーションフィルターの動作サンプル

DemoCapture_2016_5_29_15_30_16.png
デモはここにあります。各スライダーを操作して、RGBAチャンネルそれぞれでポスタリゼーションのかかり具合を調整できます。写真の女性は画像処理でおなじみのLenaさん。:woman:

a.png
画像は左上から、フィルタ無し、ポスタリゼーションフィルタのみ、ポスタリゼーションフィルタを適用してからぼかしフィルタ適用、ぼかしフィルタを適用してからポスタリゼーションフィルタ適用、と、なっています。多重フィルタも正しく動いているようです。(そのあたり考慮せずコードを書いても、かってにそのように動きます。)

ちなみにこの状態のポスタリゼーションは Red:8階調、Green:8階調、Blue:4階調で、MSXという昔のコンピュータのScreen8仕様です。うーん、それっぽい!

b.png

スライダーを操作すると、階調の飛ばし具合が変化します。この手のフィルタで透明度までポスタリゼーションをかけられるのは珍しいかもしれない。(しかしその対応が面倒だった。後述。)

ポスタリゼーションフィルタの使い方

各チャンネルの分解能を指定して使います。意味的に最低2、最大256です。

sample.as
var filter:PosterizationFilter = new PosterizationFilter();
filter.redDiv = 2; // redチャンネルの分解能 意味的にmax256まで
filter.greenDiv = 4; // greenチャンネルの分解能
filter.blueDiv = 8; // blueチャンネルの分解能
filter.alphaDiv = 2 // alphaチャンネルの分解能
dobj.filter = filter;

下記は上記と同じ

sample.as
var filter:PosterizationFilter = new PosterizationFilter(2,4,8,2);
dobj.filter = filter;

フィルタの大雑把なつくり方

公式のガイドで配布されているコードをひながたに改造していくのが簡単です。
ColorOffsetFilter.as

フィルター利用者とのインターフェースはFragmentFilterを継承したクラスに、フィルターの実装本体はFilterEffectを継承したクラスに書きます。ここが別々になってるのはFilterEffectを再利用しようとかそういう意図がありそうですが、通常は1対1の関係になるので、上記ColorOffsetFilterのように、FilterEffect側はインナークラスで書いてしまえば良いと思います。

今回のポスタリゼーションフィルターのコードはここにあります。上記ColorOffsetFilterを元に作りました。
harayoki.starling.filters.PosterizationFilter
FragmentFilter側ではフィルタのパラメータをそのままFilterEffectに伝えて再描画命令を出しているだけです。ほぼ何もしていません。FilterEffect側には実際の描画AGALコードがあり、フィルタのパラメータを2つの定数としてAGALに送っています。はい、それだけです。ここまでは簡単です。

動作サンプルのコードはここにあります。

ポスタリゼーションフィルターのAGALコード

AGAL部分はいわゆるマシン語なので難解ですが、コツをつかめば書けるようになってきます。途中でレジスタ(変数)の値がどうなっているかわからないので、うまく動かない場合は別途似たようなASコードを疑似的に書いて動作させつつ確認すると楽なようです。

Vertex ShaderのAGALコード

既存のAGALコードをそのまま使います。FilterEffect.STD_VERTEX_SHADERに定数定義されています。Starlingで頂点情報を変更するフィルターを作ることはあまりなさそうので、書かなくて良い、と認識すれば良いです。一応中身は下記のようになっています。

agal.as
//回転行列を座標に掛け合わせる
"m44 op, va0, vc0"
//カラーはそのままFragment Shaderに受けわたす
"mov v0, va1"

各レジスタが何を意味するのかなど今回は解説しませんが、ここのページIntroduction to AGAL: Part 2に詳しく書いてあります。英語なので、そのうち訳します。

Fragment ShaderのAGALコード

先に書いた通り、アルファチャンネルのポスタリゼーションに対応したので長くなりました。もっと効率の良い書き方はあると思いますが、まあそこはご容赦を。

ポスタリゼーションとしての処理は、一旦RGBA値(0.0~1.0)をN(階調段階数)倍して小数点以下を切り落とし、N-1で割って元に戻すと、うまい具合に階調が飛びます。NではなくN-1で割るのは、小数点以下を切り落とすだけだと、色の成分が下によってしまうからです。N-1で割って元に戻すことで、うまい具合の範囲で階調が同じになります。小数点以下切り落としの命令が見当たらなかったので、2つの命令を組み合わせてそこを実現しています。

agal.as
// テクスチャカラーをft0に取得するお決まりコード
tex("ft0", "v0", 0, texture) // == ft0, v0, fs0 <2d, linear>
// PMA(premultiplied alpha)演算されているのを元の値に戻す  rgb /= a
"div ft0.xyz, ft0.xyz, ft0.www"
// 各チャンネルにRGBA定数値(fc0)を掛け合わせる
"mul ft0, ft0, fc0"
// ft0の小数点以下を破棄 ft1 = ft0 - float(ft0)、ft0 -= ft1
"frc ft1, ft0"
"sub ft0, ft0, ft1"
// 定数ft0を掛けた際より1小さい値が定数1にはいっているので、それで割って戻す
"div ft0, ft0, fc1"
// 1.0を超える部分ができるので正規化 (sat : 0.0~1.0に収める命令)
"sat ft0, ft0"
// PMAをやり直す rgb *= a
"mul ft0.xyz, ft0.xyz, ft0.www"
// ocに出力
"mov oc, ft0"

アルファチャンネルが絡んでくると、公式ガイドにあるように、PMA(premultiplied alpha)を考慮しないといけないので、若干面倒です。できあがれば簡単なコードになりますが、AGALコーディング最中はどこでおかしな結果になっているかわからず、苦労しました。

フィルターのバグ?

動作サンプルを作っているに当たって、1つのフィルターを複数のDisplayObjectに適用すると、DisplayObjectが消えたり、ランタイムエラーが出ることに気づきました。バグなのか仕様なのかわかりませんが、flashネイティブではフィルタを複数のDisplayObjectに適用することが可能なので、直して欲しいですね。今回はフィルターのインスタンスを複数作って対応しました。こちら、問い合わせてみます。

まとめ

世の中にはAGALを簡単に記述するための高級言語やクラスも存在するようですが、今はまず手作業で書きつつ勉強することにしています。AGALについても何本がまとめを投稿する予定です。

なお、StarlingではAGALバージョン1.0を使っています。現在の最新はAGAL3です。AGAL2がデフォルトになる日はいつの日か。 AGAL2は2014/1のリリースなのでそろそろいいんではないかな。。
http://labsdownload.adobe.com/pub/labs/flashruntimes/shared/air16_flashplayer16_releasenotes.pdf

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away