株式会社デジタル・フロンティア-Digital Frontier

Header

Main

  • TOP
  • DF TALK
  • NukeのBlinkScriptで自作のフレアを作る

NukeのBlinkScriptで自作のフレアを作る

2014/1/21

Tag: ,

皆さん、お久しぶりです。開発室の山口です。

前回、前々回とUI周りのことばかり紹介していましたので、今回はNuke8.0v1からの新機能
BlinkScriptで自作のフレアを作る方法を紹介したいと思います。

最終的な結果はこんな感じです。デフォルトのFlareノードなどで作成するよりも、少しクオリティーの
高いフレアが作成できるのではないかなと思います。

glowFlare

そして、これが今回使用するBlinkScriptのコードです。

kernel GlowFlare : ImageComputationKernel<ePixelWise>
{
    Image<eRead, eAccessPoint, eEdgeNone> src;
    Image<eWrite> dst;

    param:
        bool effectOnly;
        float2 anchorPoint;
        float coreRadius;
        float gain;
        float gamma;
        float4 color;

    void define()
    {
        defineParam(effectOnly,  "EffectOnly",  false);
        defineParam(anchorPoint, "AnchorPoint", float2(0.0f, 0.0f));
        defineParam(coreRadius,  "CoreRadius",  100.0f);
        defineParam(gain,  "Gain",  1.0f);
        defineParam(gamma, "Gamma", 1.0f);
        defineParam(color, "Color", float4(0.45f, 0.3f, 0.95f, 1.0f));
    }

    void process(int2 pos)
    {
        float mapx = fabs(pos.x-anchorPoint[0]);
        float mapy = fabs(pos.y-anchorPoint[1]);
        float distanceMap = sqrt(mapx*mapx + mapy*mapy);
        float remap = max(0.005f, distanceMap / coreRadius);

        float glow = 1.0f/remap/remap;
        float glowShape = pow(glow*gain, 1.0f/gamma);

        dst(0) = src(0)*(1-effectOnly) + glowShape*color[0];
        dst(1) = src(1)*(1-effectOnly) + glowShape*color[1];
        dst(2) = src(2)*(1-effectOnly) + glowShape*color[2];
        dst(3) = min(1.0f, src(3)*(1-effectOnly) + glowShape);
     }
};

このコードをBlinkScriptノードのプロパティのテキストフィールド(下図の①)にペーストし、Recompileボタン(下図の②)を
押してコンパイルするとノードが更新されます。後はこれを画像にコネクトすると最初に紹介したフレアがインプットの画像に
反映されます。

BlinkScriptNode

BlinkScriptはC++をベースに作られている言語ですので、今までPythonしか書いたことない人には
とっつきにくいかもしれませんが、たったこれだけのプログラムでちょっとしたエフェクトが作成できるので
ぜひチャレンジしてみてください。

基本的な書き方やサンプルコードはここの英語のサイトに全てのっていますので
この記事とあわせてみていただければと思います。

http://docs.thefoundry.co.uk/nuke/80/BlinkKernels/index.html

では早速見ていきたいと思います。


kernelの宣言

kernelと紹介しましたが、C++でいう所のクラスみたいなもので、BlinkScriptノードがkernelで宣言された
プログラムを認識して初めて効果が現れます。ですのでBlinkScript内で必ず宣言します。

まず最初に下記の要領でkernelの宣言を行います。

kernel カーネル名 : 継承するクラス

カーネル名にはそのまま任意の名前を指定し継承するクラスは以下の二つから選択します。

・ImageComputationKernel<ePixelWise>
・ImageComputationKernel<eComponentWise>

RGBA全てのチャンネルに同じ計算式を適用したい場合には<eComponentWise>を、RGBA各チャンネルに
個別の計算式を適用したり、情報を取得したい場合は<ePixelWise>を使用します。

今回はフレア自体に色を持たせるためRGB各チャンネルに対し違う計算式を
適用することになるので<ePixelWise>使用します。

下記が今回の宣言文で”GlowFlare”と言う名前で”ImageComputationKernel”を継承しています。

kernel GlowFlare : ImageComputationKernel

インプットとアウトプットの設定

次にインプットとアウトプットの設定です。
下記のコマンドを使用して変数を宣言します。

Image<ReadSpec, AccessPattern, EdgeMethod> 変数;

引数の設定方法は以下になります。

■ReadSpec
ReadSpecにはeReadeWriteが指定できます。
・eRead ・・・ eReadとして宣言した変数にはインプットにつながれた情報が代入されます。
・eWrite ・・・ eWriteとして宣言した変数にはアウトプットの情報を代入します。

■AccessPattern
AccessPatternにはeAccessPointeAccessRanged1DeAccessRanged2DeAccessRandomが指定できます。
・eAccessPoint ・・・ 現在のピクセル情報のみ取得します。
・eAccessRanged1D ・・・ 縦、または横に限定してそのピクセルの周囲の情報も一緒に取得します。
・eAccessRanged2D ・・・ 周囲のピクセルの情報も一緒に取得する。
・eAccessRandom ・・・ ランダムにピクセルの情報を取得します。

例えばBlur等のアルゴリズムは周囲のピクセルをブレンドする必用があるので”eAccessRanged2D”を使用します。

■EdgeMethod
AccessPatternにはeEdgeClampedeEdgeConstanteEdgeNoneが指定できます。
これはイメージのエッジ(バウンディングボックス外の情報の取り方)の処理をどうするかを指定します。
・eEdgeClamped ・・・ バウンディングボックス外のピクセルの情報を端のピクセルがリピートされたものとして計算します
・eEdgeConstant ・・・ バウンディングボックス外の情報を0として扱います。
・eEdgeNone ・・・ これは特に処理を行わない設定ですので、バウンディングボックス外の情報は保証されません。

例えば eEdgeClampedの場合、Blurの処理を行ったときに、周囲のピクセルが黒く落ち込むのを防ぐことが出来ます。

これを踏まえてコードを見ていきたいと思います。

Image<eRead, eAccessPoint, eEdgeNone> src;
Image dst;

まずインプットの説明ですが、今回はインプットに対しフレアの加算の処理を行うだけですので
周囲の情報は必要ありません。ですのでそのピクセルの情報のみを取得する<eAccessPoint>を使用します。

同様に、エッジの処理も必用無いので<eEdgeNone>を使用します。

アウトプットの設定はアクセスの方法や、エッジの設定は行わないので、<eWrite>のみを宣言します。
これでBlinkScriptノードにインプットの矢印が表示されるようになります。


アトリビュートを作成する

次にフレアの調節を行うアトリビュートの作成です。
アトリビュートを作成する際に、アトリビュートのタイプと表示名/デフォルト値は別々に設定しなければなりません。

まずアトリビュートのタイプを設定します。
各アトリビュートの説明はコメントに書きましたので参考にしてください。

param:
    bool effectOnly;    //エフェクトだけを表示させるためのチェックボックスです
    float2 anchorPoint; //フレアのアンカーポイントのアトリビュートです
    float coreRadius;   //フレアのコアの大きさを調節するアトリビュートです
    float gain; //フレアの強度を調節するアトリビュートです(掛け算)
    float gamma;  //フレアの広がりを調節するアトリビュートです(冪乗)
    float4 color;  //フレアの色を調整するアトリビュートです

BlinkScriptでは、宣言の型を変えると自動的にその型にあったUIの表示を行ってくれます。
下記が使用できる型と、それに対応するUIの表示です。

・bool ・・・ チェックボックスのアトリビュートが作成されます。
・int ・・・ 整数値のフィールドが作成されます。
・int2~4 ・・・ 整数値のフィールドが指定した数だけ作成されます。
・float ・・・ 浮動小数点のスライダーが作成されます。
・float2 ・・・ 2Dポジションのアトリビュートが作成されます。アンカーが自動生成されます。
・float3 ・・・ 3Dポジションのアトリビュートが作成されます。3D空間上にポイントが生成されます。
・float4 ・・・ カラーのアトリビュートが作成されます。


※おそらくこれらのアトリビュート以外は用意されていないので、その他のUIを使用したい場合には
BlinkScriptをパブリッシュし、パブリッシュで作成されたグループノードに対し、アトリビュートを追加し
再度コネクトするのが良いと思います。

続いてパラメータのデフォルト値、表示名を設定してます。
下記の関数を使用して設定していきます。

defineParam(対応するアトリビュート名, 表示名, デフォルト値)

ここで注意したいのが表示名にスペースを使用できないという所とmin,max値が設定できないということです。
スペースを使えないのはどうもバグらしく、min,maxが設定できないのは仕様らしいです。

void define()
{
    defineParam(effectOnly,  "EffectOnly",  false);
    defineParam(anchorPoint, "AnchorPoint", float2(0.0f, 0.0f));
    defineParam(coreRadius,  "CoreRadius",  100.0f);
    defineParam(gain,  "Gain",  1.0f);
    defineParam(gamma, "Gamma", 1.0f);
    defineParam(color, "Color", float4(0.45f, 0.3f, 0.95f, 1.0f));
}

これで下記のようなアトリビュートが作成できました。

attributeImage


フレアのアルゴリズムの実装

やっと準備が整いました。ここから本題のフレアのアルゴリズム実装に入ります。
基本的には以下のコードだけです。

void process(int2 pos)
{
    float mapx = fabs(pos.x-anchorPoint[0]);
    float mapy = fabs(pos.y-anchorPoint[1]);
    float distanceMap = sqrt(mapx*mapx + mapy*mapy);
    float remap = max(0.005f, distanceMap / coreRadius);
 
    float glow = 1.0f/remap/remap;
    float glowShape = pow(glow*gain, 1.0f/gamma);
 
    dst(0) = src(0)*(1-effectOnly) + glowShape*color[0];
    dst(1) = src(1)*(1-effectOnly) + glowShape*color[1];
    dst(2) = src(2)*(1-effectOnly) + glowShape*color[2];
    dst(3) = min(1.0f, src(3)*(1-effectOnly) + glowShape);
}

このコードがどのような処理をするかは、文章だけではわかりづらいので
まずは簡単な図で説明したいと思います。(コードの説明は更に下で行います)

1.まず最初にアンカーポイントから各点までの距離のマップを作成します。
画像にすると下図のような白黒のグラデーションマップになります。

distanceMap

2.次に1で作成したマップを使用して、距離に応じて光が減衰するようなマップを作成します。

flareMap

3.ここまでくれば簡単ですね、各種調整用のアトリビュートを実装し、色づけすると完成です。

glowFlare

以上がこのフレアのアルゴリズムの流れです。
では計算式を見ていきたいと思います。


■processの宣言

まずprocessの宣言です。

void process(int2 pos)

基本的にはこの中にアルゴリズムを記述します。ここで重要なのは”int2 pos”という部分です。
この引数を追加することでprocess内で各ピクセルの座標がpos.x, pos.yと言う形で取得できるようになります。

■距離のマップを作成

次にアンカーポイントを中心に外側に広がっていく距離のマップを作成します。

float mapx = fabs(pos.x-anchorPoint[0]);
float mapy = fabs(pos.y-anchorPoint[1]);
float distanceMap = sqrt(mapx*mapx + mapy*mapy);
float remap = max(0.005f, distanceMap / coreRadius);

順を追って説明します。

・まず現在のピクセルのポジションからアンカーポジションを引き、それを絶対値に変換し
アンカーポイントを基準にそれぞれ上下左右に広がるグラデーションを作成します。
※これを表示させると一見真っ白なマップに見えますが、gain等で調節すると
下図のようにグラデーションが見えるようになると思います。

float mapx = fabs(pos.x-anchorPoint[0]);
float mapy = fabs(pos.y-anchorPoint[1]);

xyMap

・次に上記の情報と、三平方の定理を利用してアンカーからの距離のマップに変換します。
※このマップも一見真っ白になってしまいますが、情報はちゃんと格納されています。

float distanceMap = sqrt(mapx*mapx + mapy*mapy);

distanceMap

・続いて次の工程で、中心部の一番明るく光る部分の範囲を定義する処理(coreRadiusで除算)と0で除算を行わないようにするために、最低でも0.005f以上の値を保つ処理(maxを使用)を追加します。

float remap = max(0.005f, distanceMap / coreRadius);

■光の減衰を適用

次にこの距離のマップを利用して光の減衰を表現します。
計算式は非常に簡単で、光は距離に応じて逆2乗の法則で減衰していくので、これを利用します。

float glow = 1.0f/remap/remap;

これでだいぶフレアっぽくなりました。

flareMap

■調整用のアトリビュートの関連付け

最後にフレアの調節用のアトリビュートの関連づけを行います。

まずgainとgammaのアトリビュートを足して、フレアの強度や広がりをコントロールできるようにします。
gammaとgainの計算の順番は好みに応じて変えてください。

float glowShape = pow(glow*gain, 1.0f/gamma);

次に色づけと、エフェクトのON、OFFの効果を足して行きます。

アウトプットの変数dstの括弧0~3にはRGBAが割りあたっています。
ですので以下のようにRGBA別に計算式を代入していきます。

dst(0) = src(0)*(1-effectOnly) + glowShape*color[0];
dst(1) = src(1)*(1-effectOnly) + glowShape*color[1];
dst(2) = src(2)*(1-effectOnly) + glowShape*color[2];
dst(3) = min(1.0f, src(3)*(1-effectOnly) + glowShape);

まず下記のコマンドですが、こうしておくとeffectOnlyのチェックボックスがOnの時に”(1-effectOnly)”の
計算が0になり、インプットの情報に0が乗算され無視されるようになります。
こうしておくことでエフェクトの効果のみをチェックすることが出来るようになります。

src(0)*(1-effectOnly)

次のコマンドはカラー値を、それぞれチャンネル別に掛け算しているだけです。
これでフレアの色が表現できるようになります。

glowShape*color[0];

最後の行はアルファチャンネルを定義している行です。

dst(3) = min(1.0f, src(3)*(1-effectOnly) + glowShape);

アルファチャンネルは基本的には1以上の値が入ることは良くないので、それを防ぐためにインプットの
アルファとグローのアルファの和が1を超えない様にmin関数を利用して防いでいます。

ちなみこの一連のプログラムは以下のように書くことも可能です。

dst() = src()*(1-effectOnly) + glowShape*color;
dst(3) = min(1.0f, src(3)*(1-effectOnly) + glowShape);

以上がBlinkScriptでのフレアの作成例です。


最後に、

実はこれぐらいのアルゴリズムであればエクスプレッションノードとその他のノードの組み合わせでも実装できます。
ただエクスプレッションノードでは色々と制限もあり、自由に変数などを使って表現できないので作成するのが困難です。

その点BlinkScriptは処理速度は落ちますがコードも理解しやすく、管理が簡単になりますので
少し複雑なエクスプレッションであれば、BlinkScriptを使用しても良いのではないかと思います。
また習得すれば表現の幅も広がりますのでやってみる価値はあると思います。


※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

Pocket

コメント

コメントはありません

コメントフォーム

コメントは承認制ですので、即時に反映されません。ご了承ください。

*