14 October 2008
ページ ツール |
Flash CS3 ProfessionalまたはActionScriptでの一般的なアプリケーション開発経験を有することが推奨されます。この記事で紹介するサンプルは、Flash CS4 Professionalで制作されています。コードは、レイヤー名「code」の1フレーム目に配置されています。シェーダが適用されるオブジェクトおよび各種コントロールは、「display」レイヤーに配置されています。Flash CS4を使用していない場合は、独自の表示オブジェクトとコントロール群をActionScriptまたはAdobe Flexで用意して、サンプルコードを応用することが可能です。
中級
この記事付属のサンプルファイルには、以下のプロジェクトが収録されています。
この記事にはこれらのサンプルのSWFコンテンツが掲載されています。 サンプルを詳しく検証したい場合は、付属のサンプルファイルをダウンロードし、Pixel Bender ToolkitおよびFlash CS4 Professionalでこれらのファイルを開くようにしてください。
Pixel Benderは、Adobe Flash Player 10、Adobe After EffectおよびAdobe Photoshop(近々)がサポートするグラフィックス処理エンジンです。Pixel Benderの言語は、3Dレンダリングピクセル描画処理の最適化に用いられる、フラグメントシェーダ言語(GLSL:OpenGL Shading Languageなど)に基づいて開発されています。Flash Playerでは、このPixel Benderのプログラムを利用してフィルタやブレンド、面または線の塗りを作成することができます。
Pixel Benderのエフェクトは、画像、ベクトルグラフィックスからデジタルビデオに至るまで、あらゆる表示オブジェクトに適用できます。実行処理速度の速さも特筆に値します。これまでActionScriptではフレームあたり数秒かかっていたようなエフェクトでさえ、これからはリアルタイムで処理できるようになります。
Pixel Benderのプログラムは「カーネル」と呼ばれ、Adobe Pixel Bender Toolkitを利用して記述・コンパイルします。このツールキットによって生成されるコンパイル済みのバイトコードは、Shaderオブジェクトに読み込むことで、SWFコンテンツ上で利用することができます。なお、Pixel Bender ToolkitはFlash CS4 Professionalのインストール時に自動的にインストールされます。
Adobe Flash CS4 ProfessionalでのPixel Benderの使用法についてのドキュメンテーションは『ActionScript 3.0のプログラミング』の「Pixel Benderシェーダの操作」の章、および『ActionScriptコンポーネント・言語リファレンス』の「Shaderクラス」の節に用意されています。このドキュメンテーションには、Flash CS4 + Pixel Benderでの作業時に利用可能なオブジェクトの詳しい解説が含まれています。また、Pixel Bender言語のドキュメンテーションには、Pixel Bender Toolkitのヘルプメニューからアクセスできます。
Pixel Benderによって何が可能になるかを知りたい、あるいは新しい技を習得したい、と考えるユーザには、Pixel Benderのプログラムサンプルをチェックすることをお勧めします。この記事で紹介するサンプル以外にも、アドビがホスティングするカーネルのパブリックリポジトリである Pixel Bender Exchangeには、様々なサンプルが掲載されています。これらのカーネルプログラムの作成者は、Flashデベロッパーコミュニティに自らの作品を披露することに同意した親切な方々ばかりです。是非皆さんも、作成したカーネルをExchangeに投稿してみてください。また、Pixel Benderプログラミングについてのディスカッションフォーラムも存在します。
カーネルのコーディングを始める前に、まずPixel Bender言語について簡単に説明します。 この記事を読むだけでPixel Bender言語のエキスパートになれるわけではありませんが、以下を読む進むことで、この言語で記述されたプログラムが理解しやすくなるはずです。より詳しい情報については、Pixel Bender ToolkitアプリケーションのHelpメニューからアクセスできる、「Adobe Pixel Bender言語1.0チュートリアルおよびリファレンスガイド*」を参照するようにしてください。
Pixel BenderではC、Java、ActionScriptなどの開発言語に似た、手続き型のシンタックスを使用します。Pixel Benderには画像処理に特化した既製のデータ型および関数が収録されています。既にActionScriptに慣れていれば、Pixel Benderカーネルの記述法を習得するのは難しくありません。以下に、ActionScriptとPixel Bender言語のシンタックスの主な相違点を示します。
var foo:int;
代わりに、次の構文を使用します。
int foo;
newキーワードはサポート(必要と)されません。float4 rgbaPixel = float4( 1.0, 0.3, 0.2, 0.8 );
rgbaPixelの名の新しいベクトル変数が宣言され、これに色の値が代入されています。float4( 1.0, 0.3, 0.2, 0.8 )の部分の表現では、rgbaPixel変数に代入されるfloat4ベクトルの定数リテラルが定義されています。r,g,b,a、x,y,z,wまたはs,t,p,qの3つの要素名のセットのいずれかと、ドット表記を用いることでアクセスできます。スウィズリングがサポートされていることにより、これらの要素の並び順は単に要素名を入れ替えるだけで変更できます。例えば、次に示すステートメントでは、ピクセルの値を他の変数に代入する際に、ピクセルベクトルの赤と緑のカラーチャンネルが置き替えられます。pixel4 mixedUp = rgbaPixel.grba;
次のように、チャンネルを繰り返すことも可能です。
pixel4 allRed = rgbaPixel.rrra;
また、次に示すように、チャンネルを削除することも可能です。
pixel2 redAndBlue = rgbaPixel.rb;
メモ:ベクトル変数で使用する変数名のセットは任意に選択できます。例えば、myVector.rはmyVector.xと同じものを指します。ベストプラクティスとしてはrgbaのセットを色指定、xyzwまたはstpqのセットを位置指定にそれぞれ使用することが推奨されます。一度の参照において、異なるセットの要素名を混同させることはできません。
evaluatePixel()以外の関数)および配列はサポートされません。メモ:Flash Player向けのPixel Benderカーネルを開発する際には、(ツールキットウィンドウのBuildメニューにある)「Turn on Flash Player Warnings and Errors」オプションがオンになっていることを確認するようにしてください。このオプションがオンになっていれば、サポート対象外のPixel Bender言語機能が用いられた際に、コンパイラが即座にその旨を通知します。(オプションがオフになっている場合、カーネルをFlash Playerに書き出すまではエラーを確認できません。)
Pixel Benderの典型的なカーネルは以下の処理を行います。
次に示す簡潔なPixel Benderカーネルは、これらの処理をすべて行います。このプログラムは「ChannelScrambler」という名のカーネルを定義するものです。
<languageVersion : 1.0;>
kernel ChannelScramblerFilter
< namespace : "com.adobe.example";
vendor : "Adobe Systems Inc.";
version : 1;
description : "Changes the color channel order from rgba to gbra.";
>
{
input image4 inputImage;
output pixel4 outPixel;
void evaluatePixel()
{
pixel4 samplePixel = sampleNearest( inputImage, outCoord() );
outPixel = samplePixel.gbra;
}
}
このカーネルでは、inputImageと名付けられた入力画像、およびoutPixelと名付けられた出力画像が宣言されています。evaluatePixel()関数では、現在処理されている出力座標ピクセルにアクセスするために、内蔵関数のsampleNearest()とoutCoord()が用いられています。そして、サンプリング済みのピクセルは、スウィズリングを利用してカラーチャンネルの順序が並べ替えられた後、outPixel変数に割り当てられています。
Flash CS4で生成したSWFファイル上でこのカーネルをフィルタとして使用すると、以下の結果が得られます。
Pixel Benderプログラムには、必須要素であるlanguageVersionタグを含める必要があります。
<languageVersion: 1.0;>
また、カーネル名の宣言も必須です。
kernel ChannelScrambler{...}
カーネル内には、必ず単一の出力宣言(output pixel4 outPixelなど)とevaluatePixel()関数を配置する必要があります。利用できる入力数に制限はありません。入力を一切使用しないことも可能です。ただし、カーネルをFlash Playerでどのように使用するかによっては、入力数に対して他の要件が発生することがあります。ブレンドとして用いられるシェーダには2つの入力、ファイルとして用いられるものには1つの入力が必要とされます。塗りとして用いられるシェーダに関しては、入力は一切不要です。
Pixel Benderカーネルは、出力画像の各ピクセルに対して一度実行されます。なお、実行間にステート情報は一切保存されません。したがって、各実行時のサンプリング済みピクセルを蓄積し、平均のピクセル値を求めるようなことはできません。また、必要な情報はすべて、カーネルの各実行時に演算される必要があります。ただし、あらかじめActionScriptで演算処理を行い、この結果を入力またはパラメータとして渡すことは可能です。
入力画像のピクセル値には、必ずサンプリング関数を用いてアクセスします。サンプリング処理は次の内蔵関数を用いて行います。
sampleNearest():所定の座標に最も近いピクセルのチャンネル値が含まれたベクトルを返します。(動作がやや異なるsampleLinear()関数もあります。) outCoord():現在の出力ピクセルの座標を返します。 Pixel Benderが画像を処理する際、カーネルは出力画像のすべてのピクセルに対して実行されます。現在のピクセルの座標は、outCoord()関数によって返されます。Pixel Benderの座標体系はFlash Playerのそれに似たものです。原点は左上の隅にあり、正の値は右側および下側への移動を示します。ピクセルは常に正方形です。
サンプリングの対象ピクセルは、outCoord()のポジションに限定されるわけではありません。 例えば、次のサンプリングステートメントを利用すれば、現在のピクセルから10ピクセル右、かつ5ピクセル下のピクセルをサンプリングできます。
pixel4 inputPixel = sampleNearest( inputImage, outCoord() + float2( 10.0, 5.0 ) );
表現outCoord() + float2( 10.0, 5.0 )は、outCoord()関数によって生成される座標ベクトルと2要素からなるベクトルの和を求めます。このコードは、float2(outCoord().x + 10.0, outCoord().y + 5.0)と記述するのに相当します。
サンプリング対象の座標が入力画像の範囲外になるような場合は、すべて0が含まれたカラーベクトルが返されます。例えば、入力のデータ型がimage4の場合に範囲外のピクセルをサンプリングすると、完全に透明な黒のピクセルが返されます。入力のデータ型がimage3であれば、(アルファチャンネルなしの)黒のピクセルが返されます。Pixel Benderの座標空間の大きさは、理論的には無限です。ただし、表現できる座標の範囲については実用的な制限があります。また、現存しないサンプリングデータの有用性についても制約が存在します。
一旦ピクセルを表現するベクトルが取得できたら、これらのカラー値を様々な方法で操作できます。仮に、pixの名のpixel4変数があった場合、個々のカラーチャンネルまたはチャンネルの組み合わせは以下の様々な方法で参照できます。
pix.rまたはpix[0] pix.gまたはpix[1] pix.bまたはpix[2] pix.aまたはpix[3]pix.ra pix.bgr個々のカラーチャンネルは32-bitの浮動小数点数で表され、通常、0.0(黒)から1.0(白)の間の値になります。 出力側のカラー値はこの範囲外でも指定できますが、この場合はレンダリング結果には表現されません。つまり、pixel3(-1.0, -1.0, -1.0)とpixel3(0.0, 0.0, 0.0)は、ビットマップとしてレンダリングされる際、いずれも同じ黒になります。(ただし、同一画像に対して複数のフィルタを実行するような場合は、当該ピクセルをサンプリングするにあたり2番目のフィルタが(0.0, 0.0, 0.0)ではなく、(-1.0, -1.0, -1.0)を検出するので、実行結果に大きな差が現れることがあります。)
ピクセルベクトルの演算処理には、スカラー値とベクトル値のどちらも利用できます。スカラー値を使用する場合は、演算処理が各チャンネルに適用されます。例えば、次に示す演算処理では、各チャンネル(アルファチャンネルを含む)の値が半減されます。
pixel4 pix = sampleNearest( inputImage, outCoord());
pix = pix / 2.0;
ベクトルを用いてこれと同じことを記述すると、次のようになります。
pix = pix * pixel4( 0.5, 0.5, 0.5, 0.5 );
Pixel Bender上の画像にはチャンネルあたり32-bitが用意されていますが、Flash CS4のグラフィックスにはチャンネルあたり8-bitしか用意されていません。カーネルをFlash Playerで実行する際、入力画像のデータは一旦チャンネルあたり32-bitに変換され、その後カーネルの実行完了時に、チャンネルあたり8-bitへと再変換されます。
入力の宣言には、次に示すように、inputキーワードを用います。
input image4 sourceImage;
宣言時に使用できるデータ型は、image1、image2、image3またはimage4です。カーネル内の個々の入力に、異なる数のチャンネルが含まれていても問題ありません。シェーダによって生成される出力画像のチャンネル数は、入力側のデータ型ではなく、出力ピクセルのデータ型によって決定されます。
宣言する入力の数は、カーネルがFlash Playerによって使用される際に必要な数より多くても問題ありません。ただし、Flash Playerは自動的に、必要とされる入力に対してのみ画像データを割り当てます。なお、この余分な入力に対しては、カーネルがブレンド、フィルタ、塗りのいずれであるかを指定する前に、画像を割り当てる必要があります。例えば、テクスチャ付きのエフェクトを作成するためにフィルタカーネルで追加の画像を使用した場合、当該カーネルが含まれたShaderFilterを表示オブジェクトのフィルタ配列に割り振る前に、必ず当該テクスチャをカーネルの入力として割り当てる必要があります(この方法については後ほど詳しく解説します)。
カーネルには、入力画像以外の値もパラメータとして供給できます。パラメータはparameterキーワードを用いて宣言し、データ型としてはimage(およびregion(いずれにせよFlash Player向けに記述したカーネルでは使用不可))以外のすべてのものが利用できます。パラメータのメタデータを宣言して、デフォルト、最小および最大の値を指定することも可能です。また、説明を供給することも可能です。メタデータは山括弧(<>)で挟む形で宣言します。メタデータにはFlash Player上でもアクセスできます。パラメータを使用する際には、あらかじめ合理的なデフォルト値を定義しておくことが推奨されます。
次のパラメータステートメントでは、メタデータが備わったfloat3パラメータが宣言されています。
parameter float3 weights
<
defaultValue : float3( 0.5, 0.5, 0.5 );
minValue : float3( 0.1, 0.1, 0.1 );
maxValue : float3( 0.9, 0.9, 0.9 );
description : "A three element vector of weight values."
>;
Flash Player上でパラメータの値にアクセスするには、当該カーネルを含むActionScript Shaderオブジェクトのdataプロパティを用います。前出のパラメータの現在値、最小値、最大値にActionScriptからアクセスしたい場合は、次のステートメントが利用できます。(ここでは、これらのパラメータが備わったカーネルを含むShaderオブジェクトの名がmyShaderであったとします。)
var currentWeights:Array = myShader.data.weights.value;
var minWeights:Array = myShader.data.weights.minimumValue;
var maxWeights:Array = myShader.data.weights.maximumValue;
この例のパラメータはfloat3のベクトル型であるため、返されるActionScript配列には3つの要素が含まれます。仮に、パラメータがスカラー型であった場合は(floatなど)、返される配列に単一の要素が含まれます。
Flash Playerで使用するカーネルのコンパイルと書き出しは、Pixel Bender ToolkitのFileメニューにある「Use the Export Kernel Filter for Flash Player」コマンドを用いて行います。 書き出したカーネルには、.pdjのファイル拡張子が付きます(図1参照)。
Pixel BenderカーネルをFlash Playerに読み込むには、コンパイル済みカーネルを埋め込むが読み込む必要があります。
Embedタグ(Flash CS4 Professionalから新たにサポート)は、ActionScriptコンパイラに対して、SWFファイルの作成時にPixel Benderカーネルを埋め込むことを指示します。Embedタグは、次の例に示されている通り、変数型定義のClassとともに使用します。
[Embed(source="channelscrambler.pbj", mimeType="application/octet-stream")]
var ChannelScramblerKernel:Class;
カーネルを使用するには、当該クラスのインスタンスを作成します(ここではChannelScramblerFilter)。次のコードでは、埋め込まれたカーネルを利用して、ステージ上のMovieClipインスタンスに適用される、新規のシェーダとShaderFilterオブジェクトが作成されます。
var camellia_mc:MovieClip;
//Embed the PixelBender kernel in the output SWF
[Embed(source="channelscrambler.pbj", mimeType="application/octet-stream")]
var ChannelScramblerKernel:Class;
var shader:Shader = new Shader(new ChannelScramblerKernel() );
var shaderFilter:ShaderFilter = new ShaderFilter( shader );
camellia_mc.filters = [ shaderFilter ];
大半のケースでは、Embedタグを使う方法がPixel Benderカーネルを読み込むための最も簡単な手段です。しかし、必要であればカーネルをランタイム時に読み込むことも可能です。次のサンプルでは、URLLoaderクラスを利用してカーネルを読み込んでいます。
var camellia_mc:MovieClip;
var urlRequest:URLRequest = new URLRequest( "channelscrambler.pbj" );
var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener( Event.COMPLETE, applyFilter );
urlLoader.load( urlRequest );
function applyFilter( event:Event ):void
{
trace("apply");
urlLoader.removeEventListener( Event.COMPLETE, applyFilter );
var shader:Shader = new Shader( event.target.data );
var shaderFilter:ShaderFilter = new ShaderFilter( shader );
camellia_mc.filters = [ shaderFilter ];
}
メモ:Embedタグが用いられている場合、Flash CS4 Professionalは、Flex SDKに収録されているFlex.swcライブラリを利用します。通常、このSDKはFlash CS4とともにインストールされ、 Common/Configuration/ActionScript 3.0/libs/flex_sdk_3 サブディレクトリに配置されます。Embedタグを用いて初めてムービーをテスト・パブリッシュする際には、Flex SDKの位置を確認するよう促されます。Flash CS4とともにインストールされたFlex SDKを使用する場合は「OK」をクリックします。他のバージョンのFlexを使用する方が良い場合は、パスを任意に変更することも可能です。プロジェクトで用いられた設定詳細は、「ライブラリパス」タブの高度なActionScript 3.0設定ダイアログボックス上で後からでも変更できます。(高度なActionScript 3.0設定ダイアログボックスには、パブリッシュ設定のFlashタブ内、スクリプトドロップダウンメニューのそばにある「設定」ボタンをクリックすることでアクセスできます。)
ブレンドは、ブレンド適用対象の表示オブジェクトのカラーと、ステージ上の当該オブジェクトの背後のカラーを組み合わせます。 Flash Playerは数種類の既製ブレンドをサポートしており、これらはBlendModeクラスにて定義されています。ここでは練習課題として、既製ブレンドのいくつかを再現してみることにします。また、既製のブレンドを使用するだけでは簡単に達成しえないような、ブレンドの作成にも挑戦します。
表示オブジェクトにブレンドを適用するには、読み込み済みのカーネルバイトコードでShaderオブジェクトを作成し、これを当該表示オブジェクトのblendShaderプロパティに割り当てます。ブレンドカーネルには必ず2つの入力が必要となります。1つ目の入力は(blendShaderプロパティが設定される)前景の表示オブジェクト、そして2つ目の入力は前景オブジェクトの下にあるなんらかのものです。他の入力も使用したい場合は(マスクまたはテクスチャを作成する場合など)、ブレンドを適用する前に、これらの入力に明示的にBitmapDataオブジェクトとして画像を割り当てる必要があります。
乗算ブレンドでは、前景オブジェクトの各カラーと背景オブジェクトのカラーの積が求められます。片方の画像が純白なケースを除き、このブレンドは結果を暗くする効果があります。
次に示すカーネルには「foreground」と「background」の名が付いた2つの入力と、出力、および名前付きの結果が宣言されています。evaluatePixel()関数では、各画像の現在の座標のピクセルがsampleNearest()関数によってサンプリングされ、最後にピクセル同士の積が求められています。
<languageVersion : 1.0;>
kernel MultiplyBlend
< namespace : "com.adobe.example";
vendor : "Adobe Systems Inc.";
version : 1;
description : "A simple multiply blend";
>
{
input image4 foreground;
input image4 background;
output pixel4 result;
void evaluatePixel()
{
pixel4 a = sampleNearest( foreground, outCoord() );
pixel4 b = sampleNearest( background, outCoord() );
result = a * b;
}
}
メモ:Pixel Bender言語上で2つのベクトルの乗算を行うと、対応する要素の値同士の積が求められます。つまり、仮にaとbがいずれもfloat2のベクトルで、次に示すステートメントが記述されていたとします。
a * b
この場合、上記ステートメントは、次の2つのステートメントと同じことになります。
a.x * b.x
a.y * b.y
ブレンドの読み込みと適用には、次のActionScriptコードを使用します。
var camellia_mc:MovieClip;
//Embed the Pixel Bender kernel in the output SWF
[Embed(source="multiplyblend.pbj", mimeType="application/octet-stream")]
var MultiplyBlendKernel:Class;
var shader:Shader = new Shader( new MultiplyBlendKernel() );
camellia_mc.blendShader = shader;
スクリーンブレンドでは、カラーの積が求められた後、その値が反転されます。つまり、このブレンドには乗算ブレンドの適用と正反対の効果があります。片方の画像が黒であるケースを除き、このブレンドは結果を明るくする効果があります。
<languageVersion : 1.0;>
kernel ScreenBlend
< namespace : "com.adobe.example";
vendor : "Adobe Systems Inc.";
version : 1;
description : "Screen blend";
>
{
input image4 foreground;
input image4 background;
output pixel4 result;
void evaluatePixel()
{
pixel4 a = sampleNearest( background, outCoord() );
pixel4 b = sampleNearest( foreground, outCoord() );
result = 1.0 - (1.0 - a) * (1.0 - b);
}
}
このカーネルは、先ほどの乗算カーネルとほぼ同じであることが確認できます。 resultに対する数値演算処理の内容が、唯一の変更点です。このサンプルには、読み込み・適用対象のシェーダが異なるという例外を除けば、同じActionScriptコードを使用できます。
ハードライトブレンドは、乗算ブレンドとスクリーンブレンドを組み合わせたものです。前景のピクセルが50%グレーより明るい場合はスクリーンブレンドが適用され、それ以外の場面では乗算ブレンドが適用されます。
<languageVersion : 1.0;>
kernel HardLightBlend
< namespace : "com.adobe.example";
vendor : "Adobe Systems Inc.";
version : 1;
description : "Hard light blend";
>
{
input image4 foreground;
input image4 background;
output pixel4 result;
void evaluatePixel()
{
pixel4 a = sampleNearest( background, outCoord() );
pixel4 b = sampleNearest( foreground, outCoord() );
float gray = (b.r + b.g + b.b)/3.0;
if( gray < 0.5 )
{
result = 2.0 * a * b;
}
else
{
result = 1.0 - 2.0 * (1.0 - a) * (1.0 - b);
}
}
}
ハードライトブレンドは、これまでのものよりやや複雑です。このブレンドでは、まず前景画像のピクセルのカラーチャンネルの平均値を用いて、当該ピクセルのグレーレベルが算出されます。そして、ifステートメントを用いることで、乗算とスクリーンのいずれかのブレンド処理が行われます。
このサンプルに対しても、読み込み・適用対象のシェーダが異なるという例外を除けば、これまでと同じActionScriptコードを使用できます。
次に、既製のブレンドだけでは達成しがたいブレンドにチャレンジすることにします。ここで紹介するフィルタは、ノイズテクスチャとsin()関数を用いて、木目または大理石模様のエフェクトを作成します。このエフェクトの出来映えは、ノイズテクスチャの特性に左右されやすいといえますが、たいがいの場合、パーリンタイプのノイズが良好な結果をもたらせます。
このシェーダは、ノイズ画像のピクセル値をサンプリングすることで機能します。また、このシェーダは画像で直接ノイズピクセルを使用する代わりに、ノイズの値を一連のsin()関数にフィードします。そして、その結果と背景の積が求められます。結果エフェクトの曲線具合は、turbulenceパラメータを用いて制御されます。
<languageVersion: 1.0;>
kernel GrainBlend
< namespace : "com.adobe.example";
vendor : "Adobe Systems Inc.";
version : 1;
description : "Creates a wood grain or marbling effect"; >
{
input image4 background;
input image4 noise;
output pixel4 dst;
parameter float turbulence
<
maxValue : 500.0;
minValue : 0.0;
defaultValue : 150.0;
>;
void evaluatePixel()
{
pixel4 a = sampleNearest(background, outCoord());
pixel4 b = sampleNearest(noise, outCoord());
float alpha = a.a; //save the original alpha
if( (b.a > 0.0) && (a.a > 0.0)){
float seed = outCoord().x + (((b.r + b.g + b.b)/3.0) * turbulence);
float grain = (0.7 * sin(seed) + 0.3 * sin(2.0 * seed + 0.3) + 0.2 * sin(3.0 * seed + 0.2));
dst = sampleNearest(background, outCoord()) * (grain + 0.5);
dst.a = alpha; //restore the original alpha
}
else {
//Just copy the background pixel outside the area of the noise image
dst = sampleNearest(background, outCoord());
}
}
}
このサンプルに対して使用するActionScriptでは、これまでのサンプルと同じ方法でシェーダの読み込みと適用が処理されます。ただし、このコードにはturbulenceパラメータを制御するためのスライダを生成する、ActionScriptも含まれています。
import fl.controls.Slider;
import fl.events.SliderEvent;
var noise_mc:MovieClip;
var turbulence:Slider;
//Embed the Pixel Bender kernel in the output SWF
[Embed(source="grainblend.pbj", mimeType="application/octet-stream")]
var GrainBlendKernel:Class;
//Create the Shader object
var shader:Shader = new Shader( new GrainBlendKernel() );
//Set the slider values based on the parameter metadata
turbulence.minimum = shader.data.turbulence.minValue;
turbulence.maximum = shader.data.turbulence.maxValue;
turbulence.value = shader.data.turbulence.defaultValue;
turbulence.liveDragging = true;
turbulence.addEventListener( SliderEvent.CHANGE, updateFilter );
//Apply the blend
noise_mc.blendShader = shader;
function updateFilter( event:SliderEvent ):void
{
shader.data.turbulence.value = [turbulence.value];
noise_mc.blendMode = BlendMode.NORMAL;
noise_mc.blendShader = shader;
}
このサンプルでは、シェーダのturbulenceパラメータを制御するためにスライダが用いられています。 スライダの最小値、最大値およびデフォルト値は、パラメータのメタデータに基づいて設定されます。そして、Sliderオブジェクトによって変更イベントがディスパッチされた際には、パラメータの値を変更するためにupdateFilter()メソッドが用いられます。なお、表示オブジェクトのblendShaderプロパティを設定する際には、Shaderオブジェクトがクローンされます。したがって、単に元のShaderオブジェクトのパラメータ値を変更すれば良いというわけではなく、必ず、新たに更新されたShaderオブジェクトをblendShaderプロパティに割り当て直す必要があります。
このサンプルでは、説明を簡素化するためにビットマップをノイズのテクスチャとして使用しましたが、BitmapDataクラスのperlinNoise()関数を利用して他のテクスチャを作成することも可能です。
フィルタとして使用するシェーダは単一の画像に適用されます。ブレンドでの作業同様に、ここでもShaderオブジェクトを作成しますが、他にもカーネルが含まれたShaderを渡す、ShaderFilterオブジェクトを作成する必要があります。
var shader:Shader = new Shader( loadedBytes );
var shaderFilter:ShaderFilter = new ShaderFilter( shader );
ShaderFilterオブジェクトはシェーダを「包む」役割を担い、単に、表示オブジェクトのfilters配列に追加するだけの操作で、シェーダを既製フィルタ同様に扱うことを可能にします。
displayObject.filters = [ shaderFilter ];
シェーダが適用されるオブジェクトは、当該カーネルの最初の入力に自動的に設定されます。フィルタカーネルが入力として他の画像も受け付ける場合は、フィルタが表示オブジェクトに割り当てられる前にこれらの画像を設定しておく必要があります。
簡単なフィルタについてはChannelScramblerの説明で既に触れているので、ここではより複雑な「ぼかし(ガウス)」を例に解説を進めることにします。
ぼかし(ガウス)は、畳み込みフィルタの1つです。(「畳み込み」フィルタとは、周辺のピクセルの加重平均を計算するフィルタのことです。)Flash CS4 Professionalには任意のサイズの畳み込みフィルタを作成するための内蔵クラスが用意されていますが、Pixel Benderでぼかし(ガウス)をプログラミングしてみることは、Pixel Benderのいくつかの重要ポイントを紹介する上でとても有効です。
一般的な用途の畳み込みフィルタをPixel Benderで実現することは簡単ではありません。これは、Flash Playerがカーネルコード内でのループ処理をサポートしていないからです。ここでは、forループを使用する代わりに、周囲の各ピクセルをサンプリングするための、個々のプログラムステートメントを記述する必要があります。 ここで紹介するサンプルカーネルは、サンプリング半径が1から6(畳み込み行列のサイズ、3 × 3から13 × 13に相当)のガウスぼかしを作成することができます。
このフィルタでは、ガウスぼかしが分割処理可能であるという特性を生かして、処理が2段階に分けて行われます。最初の段階では画像に水平方向のぼかし処理が行われ、次の段階では垂直方向のぼかしが処理されます。これにより、出力1ピクセルにつき、「2x半径^2」ではなく「4x半径」の数のピクセルに対してのみサンプリングと加重平均を求める処理を行えば済むようになるため、ピクセルあたりの総演算量を減らすことができます。例えば、このフィルタがサポートする最大半径を使用する場合、最終的な出力1ピクセルあたり、水平、垂直の処理を合わせて26個の入力ピクセルがサンプリングされます。仮に、フィルタがこのぼかしを1段階で処理するのであれば、出力ピクセルにつき169もの入力ピクセルをサンプリングする必要があります。
このカーネルでは、forループが使えないことを克服するために、整数の半径値が個別に扱われます。有効なそれぞれの半径値に対しては、現在のピクセルから所定の半径分両側に離れた、2つのピクセルがサンプリングされます。この2つのピクセルに対するガウス加重は同じであることから、これらは単に加算されます。必要なピクセルのサンプリングがすべて完了したら、ウェイトとスケールの係数が適用されます。
水平方向の処理には、次のカーネルコードが用いられます(垂直方向の処理でも、同様のカーネルを使用)。
<languageVersion: 1.0;>
kernel HorizontalGaussianBlur
< namespace : "com.adobe.example";
vendor : "Adobe Systems Inc.";
version : 1;
description : "The horizontal convolution of a Gaussian blur"; >
{
input image4 src;
output float4 result;
parameter int radius
<
minValue : 1;
maxValue : 6;
defaultValue : 6;
>;
void evaluatePixel()
{
pixel4 center, band1, band2, band3, band4, band5, band6;
float2 pos = outCoord();
//Sample image in bands
if( radius > 5 )
{
band6 = sampleNearest(src, float2(pos.x - 6.0, pos.y))
+ sampleNearest(src, float2(pos.x + 6.0, pos.y));
}
if( radius > 4 )
{
band5 = sampleNearest(src, float2(pos.x - 5.0, pos.y))
+ sampleNearest(src, float2(pos.x + 5.0, pos.y));
}
if( radius > 3 )
{
band4 = sampleNearest(src, float2(pos.x - 4.0, pos.y))
+ sampleNearest(src, float2(pos.x + 4.0, pos.y));
}
if( radius > 2 )
{
band3 = sampleNearest(src, float2(pos.x - 3.0, pos.y))
+ sampleNearest(src, float2(pos.x + 3.0, pos.y));
}
if( radius > 1 )
{
band2 = sampleNearest(src, float2(pos.x - 2.0, pos.y))
+ sampleNearest(src, float2(pos.x + 2.0, pos.y));
}
band1 = sampleNearest(src, float2(pos.x - 1.0, pos.y))
+ sampleNearest(src, float2(pos.x + 1.0, pos.y));
center = sampleNearest(src, pos);
//Apply weights and compute resulting pixel
if( radius == 6 )
{
result = (band6 + (band5 * 12.0) + (band4 * 66.0) + (band3 * 220.0) + (band2 * 495.0) + (band1 * 792.0) + (center * 924.0))/4096.0;
}
if( radius == 5 )
{
result = (band5 + (band4 * 10.0) + (band3 * 45.0) + (band2 * 120.0) + (band1 * 210.0) + (center * 252.0))/1024.0;
}
if( radius == 4 )
{
result = (band4 + (band3 * 8.0) + (band2 * 28.0) + (band1 * 56.0) + (center * 70.0))/256.0;
}
if( radius == 3 )
{
result = (band3 + (band2 * 6.0) + (band1 * 15.0) + (center * 20.0))/64.0;
}
if( radius == 2 )
{
result = (band2 + (band1 * 4.0) + (center * 6.0))/16.0;
}
if( radius == 1 )
{
result = (band1 + (center * 2.0))/4.0;
}
}
}
エフェクトを完成させるには、両方のカーネルをフィルタとして適用する必要があります。なお、フィルタを適用する順序に決まりはありません。次に示すActionScriptコードでは、半径パラメータを制御するためにスライダが用いられています。
import fl.controls.Slider;
import fl.events.SliderEvent;
var camellia_mc:MovieClip;
var radiusSlider:Slider;
//Embed the Pixel Bender kernel in the output SWF
[Embed(source="verticalgaussianblur.pbj", mimeType="application/octet-stream")]
var VerticalBlurKernel:Class;
[Embed(source="horizontalgaussianblur.pbj", mimeType="application/octet-stream")]
var HorizontalBlurKernel:Class;
//Create the shaders
var vBlurShader:Shader = new Shader( new VerticalBlurKernel() );
var hBlurShader:Shader = new Shader( new HorizontalBlurKernel() );
//Create the filters
var hBlurFilter:ShaderFilter = new ShaderFilter( hBlurShader );
var vBlurFilter:ShaderFilter = new ShaderFilter( vBlurShader );
//Initialize the slider using the radius parameter metadata
radiusSlider.value = hBlurShader.data.radius.value;
radiusSlider.minimum = hBlurShader.data.radius.minValue;
radiusSlider.maximum = hBlurShader.data.radius.maxValue;
radiusSlider.liveDragging = true;
radiusSlider.addEventListener( SliderEvent.CHANGE, updateFilters );
//Apply the filters
camellia_mc.filters = [ hBlurFilter, vBlurFilter ];
//Reapply the filters when the slider is changed
function updateFilters( event:SliderEvent ):void
{
hBlurShader.data.radius.value = vBlurShader.data.radius.value = [radiusSlider.value];
camellia_mc.filters = [ hBlurFilter, vBlurFilter ];
}
このサンプルの場合、各フィルタの半径には同じ値が設定されます。前出のブレンドと同じように、パラメータの値が変更された際には、シェーダフィルタを表示オブジェクトに再度割り当てる必要があります。
カーネルを面の塗りとして使用するには、カーネルバイトコードが含まれたShaderオブジェクトを作成し、これをオブジェクトの描画時に、表示オブジェクトのgraphicsプロパティのbeginShaderFill()関数に渡します。 ビットマップの塗り同様に、シェーダの塗りも表示オブジェクトのオリジンに登録されます。この登録情報は、変換行列を用いることで調整できます。
面または線の塗りとして使用されるシェーダにおいて、入力画像は自動的に割り当てられません。(塗りアルゴリズムの一環として画像が必要になる場合は、beginShaderFill()メソッドを呼び出す前に、その画像を入力に明示的に割り当てる必要があります。)
次に取り上げる塗りのサンプルでは、市松模様を作成することにします。模様の正方形の大きさおよび色は、カーネルのパラメータによって制御されます。
この模様のアルゴリズムでは、モジュロ除算を用いて現在のピクセル位置の値と模様サイズを倍にした値の演算が行われます。
float vertical = mod(position.x, checkerSize * 2.0);
このモジュロ関数は、x座標をcheckerSize x2で割った時の余りの値を返します。例えば、仮にcheckerSizeが10であるとした場合、画像のx値の増加に伴って、パターンは0-19, 1-19, 2-19,...になります。演算結果が模様のサイズ(checkerSize)より小さな場合は、カーネルがAの色を描画し、それ以外の場面ではBの色を描画します。これで縞模様が作成されます。この縞模様を市松状にするには、次に示すように、このテクニックを縦と横の両方に適用する必要があります。
float vertical = mod(position.x, checkerSize * 2.0);
float horizontal = mod(position.y, checkerSize * 2.0);
後は、これらの結果をどのように組み合わせるかが問題です。ここは、片方の入力がtrueの場合に限りtrueを返す、論理演算子XOR(^^)の出番です。
( vertical < checkerSize ) ^^ ( horizontal < checkerSize )
以下に、完全なカーネルコードを示します。
<languageVersion : 1.0;>
kernel CheckerFill
< namespace : "com.adobe.example";
vendor : "Adobe Systems Inc.";
version : 1;
description : "A checkered field generator";
>
{
output pixel4 dst;
parameter float checkerSize
<
defaultValue : 10.0;
minValue : 1.0;
maxValue : 75.0;
>;
parameter pixel4 colorA
<
defaultValue : pixel4(0.0, 1.0, 1.0, 1.0);
>;
parameter pixel4 colorB
<
defaultValue : pixel4( 0.0, 0.0, 0.0, 1.0 );
>;
void evaluatePixel()
{
float2 position = outCoord();
float vertical = mod(position.x, checkerSize * 2.0);
float horizontal = mod(position.y, checkerSize * 2.0);
dst = (( vertical < checkerSize ) ^^ ( horizontal < checkerSize )) ? colorA : colorB;
}
}
このサンプルに対して使用するActionScriptコードは、これまでのものよりやや複雑です。これは、カーネルに一段と多くのパラメータが用いられていることと、パラメータ自体がより複雑であることに起因しています。
このサンプルにおいては、CheckerFillカーネルの読み込みが完了した時点で、initialize()関数が呼び出されます。この関数はShaderオブジェクトを作成し、カーネルパラメータのメタデータを使用してコントロール群の初期値を設定します。そして、シェーダの塗りを用いて描画を行うdrawShape()関数を呼び出します。
塗りの更新は、いずれかのコントロールによってchangeイベントが発せられた際に、再びdrawShape()関数が呼び出されることで処理されます。この関数は、現在のコントロール値に基づいてカーネルのパラメータを規定し、現在のグラフィックスを消去してから当該シェイプを再描画します。
import fl.controls.Slider;
import fl.events.SliderEvent;
import fl.controls.ColorPicker;
import fl.events.ColorPickerEvent;
var filledShape:Shape = new Shape();
var checkerSize:Slider;
var colorA:ColorPicker;
var colorB:ColorPicker;
var areaShader:Shader;
//Embed the Pixel Bender kernel in the output SWF
[Embed(source="checkerfill.pbj", mimeType="application/octet-stream")]
var CheckerFillKernel:Class;
//Create the Shader object using the embedded kernel
areaShader = new Shader( new CheckerFillKernel() );
//Set controls using shader parameter metadata
checkerSize.minimum = areaShader.data.checkerSize.minValue;
checkerSize.maximum = areaShader.data.checkerSize.maxValue;
checkerSize.value = areaShader.data.checkerSize.value;
checkerSize.liveDragging = true;
checkerSize.addEventListener( SliderEvent.CHANGE, updateFilters );
colorA.selectedColor = vectorToColor( areaShader.data.colorA.value );
colorA.addEventListener( ColorPickerEvent.CHANGE, updateFilters );
colorB.selectedColor = vectorToColor( areaShader.data.colorB.value );
colorB.addEventListener( ColorPickerEvent.CHANGE, updateFilters );
filledShape.x = 150;
drawShape();
addChild( filledShape );
function updateFilters( event:Event ):void
{
areaShader.data.checkerSize.value = [ checkerSize.value ];
areaShader.data.colorA.value = colorToVector( colorA.selectedColor | 0xff000000 );
areaShader.data.colorB.value = colorToVector( colorB.selectedColor | 0xff000000 );
drawShape();
}
function drawShape():void
{
with( filledShape.graphics )
{
clear();
beginShaderFill( areaShader );
drawCircle( 100, 75, 75 );
endFill();
}
}
function vectorToColor( pixelChannels:Array ):uint
{
return (pixelChannels[3] * 0xff << 24) |
(pixelChannels[0] * 0xff << 16) |
(pixelChannels[1] * 0xff << 8) |
pixelChannels[2] * 0xff;
}
function colorToVector( color:uint ):Array
{
var result:Array = new Array(4);
result[3] = ((color >> 24) & 0x000000ff)/0xff;
result[0] = ((color >> 16) & 0x000000ff)/0xff;
result[1] = ((color >> 8) & 0x000000ff)/0xff;
result[2] = (color & 0x000000ff)/0xff;
return result;
}
前出のサンプルでは、カーネルパラメータを規定するためのスライダが組み込まれていました。ここではカラーピッカーコントロールも使用することにしますが、この実装はスライダよりやや複雑になります。Flash Player上では、パラメータの値は配列としてアクセスされます。checkerSizeのようなスカラー値のパラメータであれば、配列には単一の値が含まれますが、色のようなベクトル型の場合は、ベクトルの各要素に対して配列に1つずつ配列要素が含まれます。colorAとcolorBのパラメータはいずれもpixel4型であるため、配列には、ピクセルのチャンネルあたり1つ、合計4つの値が配置されます。一方のActionScript上では、カラーは、すべてのチャンネル情報を含む単一の32-bitユニット値で表現されます。また、アルファチャンネルは、このユニットカラーでは最初のチャンネルであるものの、Shaderオブジェクトのパラメータ配列では最後のチャンネルとして扱われています。サンプル関数のvectorToColor()とcolorToVector()は、選択したカラーをColorPickerオブジェクトとシェーダパラメータの間でやり取りできるようにするために、この2種類のカラーを変換することができます。
vectorToColor()関数では、Pixel Benderカーネルから返された各カラーチャンネルと256(16進法の値:0xff)の積が求められます。この演算結果は、ビットワイズ左シフト演算子(<<)を用いて32-bitユニット値の適切なagrb位置にシフトされます。そして最後に、ビットワイズOR演算子(|)を用いて4つのチャンネルが1つのユニットに組み合わされた後、返されます。
colorToVector()関数は、上記手順の逆を処理するためのものです。この関数では、各チャンネルに対してビットワイズ右シフト演算(>>)が行われ、そしてビットワイズAND演算子(&)によって他のチャンネルに属するビットがマスクアウトされます。そして、その結果を256(0xff)で割ることによって、値が0から1の間に補正されます。この結果は、Pixel Benderで必要とされる順序で配列の適切な要素へと割り当てられた後、返されます。