SubstanceDesignerで関数を使うためのちょっといい話

初めに

本テキストではSubstanceDesigner(以下SD)で関数を使う場合の足掛かりとしての情報をまとめています。

すでにSDを使っており基本的な操作ができ関数を使ってみたい方向けです。

本テキストは個人的に検証した情報をまとめているものでありSubstanceDesignerの開発元であるAllegorithmicの公式の情報を紹介しているものではありません。そのため内容に誤謬がある可能性があります。

本テキストを使用した場合に発生する事態のいかなる責任も負いかねますのでご了承ください。

Allegorithmic社について

SDに関しての公式の情報はAllegorithmic社が提供しているドキュメントやチュートリアルを確認することをお勧めします。

Allegorithmic社公式サイト
https://www.allegorithmic.com/

SubstanceDesignerはAllegorithmic社の登録商標です。

SDで関数で関数を使うために知っておきたいこと

パラメータ

各ノードにはさまざまなパラメータがあります。

これらのパラメータを使って位置を変えたり、色を変えたり、角度を変えたりできますよね

各パラメータは一つの数値だけでなくいくつかの値をもっていることが多いです。

SDで関数を使う場合は関数内でパラメータの値を扱うことがほとんどかと思います。

つまりパラメータを関数で使う際の方法を知る必要があります。

パラメータの値の種類

パラメータの値には大きく分けて3つの種類があります。この種類のことを値の型(SDのノードの場合は変数の型と呼んだ方が正しいかもしれません)などと呼びます。

  • Float 小数点のある数値
  • Integer 小数点の無いちょっきりな数値(つまり整数)
  • Boolean スイッチのようなどちらかを選ぶような値(TrueとFalse)

これを踏まえたうえでパラメータをどの型に見分けられるのかをまとめてみたのが以下です。

  • Float1 パラメータの項目が1つで小数点のある数値をつかっている
  • Float2 パラメータの項目が2つで小数点のある数値をつかっている
  • Float3 パラメータの項目が3つで小数点のある数値をつかっている
  • Float4 パラメータの項目が4つで小数点のある数値をつかっている
  • Integer1 パラメータの項目が1つで整数をつかっている
  • Integer2 パラメータの項目が2つで整数をつかっている
  • Integer3 パラメータの項目が3つで整数をつかっている
  • Integer4 パラメータの項目が4つで整数をつかっている
  • Boolean パラメータの項目が切り替えの時

値の例

Colorは?

例えばColorはどの型になるでしょうか?Colorにはグレースケールと三原色カラーがあります。

グレースケールの場合はFloat1として扱います。グレースケールは黒の値しかありませんので1項目ですよね。ですのでFloat1です。

それではカラーはどうでしょうか?カラーは光の三原色である赤・緑・青の三要素で定義できます。ですのでFloat3と思いきや、実はFloat4です。カラーには赤・緑・青のほかにアルファチャンネルの存在があります。
赤・緑・青・アルファの4項目になるのでFloat4です。

※アルファがない場合のRGBカラーはFloat3で処理できます。
※Color関係の項目は表面上整数値に見えますが内部的にはFloatで動いているようです

ColorModeは?

いくつかのノードではフルカラーとグレースケールを切り替えることができます。

このスイッチのようなパラメータの場合はBooleanを使います。

基本的にSDでは切り替え項目は2つになっており、3つや4つの選択肢があるスイッチパラメータはありません。

Booleanの値との対応はパラメータの左側の項目がTrueで右側の項目がFalseになります。

確認方法は?

あまり多くの種類の型ではないため値を見ればほとんどの場合判別ができるかと思います。

解らなくなった場合はパラメータをエクスポーズするとInputParametersタブで値の型を確認することができます。

またEmptyFunctionを作ってみてEditすると必要な入力値の型が左下に表示されています。そこで確認してみてもいいかもしれません。

さらに関数の処理結果の出力はそのパラメータの型でなければ関数のOutputにすることができないので間違ってしまうことはほとんどないと思います。

関数でつかえるノードについて

Float系、Integer系ノード、Boolean系ノード

先にも説明した通りSDでは大まかに3つの値の型があります。

Floatは小数点、Integerは整数、BooleanはTrue、Falseのどちらかを扱います。

SDのパラメータの多くはFloatなので関数を作る時はFloatの方が出番が多いかと思います。

しかしIntegerは単純増減、Booleanは処理の切り替えなどに使う重要な役目があります。

Constant系ノード(Constant Nodes)

例えばFloatノードをグラフエリアに入れるとノードには”Constant/Float”と表示されます。Constantはプログラミング上では「定数」を表します。変化しない値(関数実行時に変化しないという意味で値は自由に設定できます)のことです。

これは関数内で任意の値を扱うときに使います。

Constant系ノードは以下の種類があります。

  • Float 
  • Float2
  • Float3
  • Float4
  • Integer
  • Integer2
  • Integer3
  • Integer4
  • Boolean
  • String

Get系ノード(Variables nodes)

Float系、Integer系、Boolean系にはそれぞれGet系ノードがあります。Getという名前からわかる通り値を取得するノードです。

SDではInputParametersにて任意のパラメータを作ることができます。このパラメータを関数で使うために使うのがGet系ノードです。

つまり値の入り口ですね。

組み込み変数を使うときもその変数に対応した型のGet系ノードを使います。

Get系ノードは以下の種類があります。

  • Get Float 
  • Get Float2
  • Get Float3
  • Get Float4
  • Get Integer
  • Get Integer2
  • Get Integer3
  • Get Integer4
  • Get Boolean
  • Get String

Swizzle系ノード(Swizzle nodes)

基本的に関数内では同じ種類の型でなければ計算することができません。

integerとFloatは混ぜられませんし、Float1とFloat4を混ぜることもできません。

しかしそれだと値を扱うのに不都合な場合があります。

例えばFloat2のYだけを扱いたい場合やFloat4のXとWだけを扱いたい場合などです。

この時に活躍するのがSwizzle系ノードです。

Swizzle系ノードは使いたい値だけを抽出することができます。またXをYとして扱ったり、WをZとして扱うなどの「あべこべ繋ぎ」をすることもできます。

  • 1つの値だけ抽出したいときはSwizzle Float1、Swizzle Integer1
  • 2つの値を抽出したいときはSwizzle Float2、Swizzle Integer2
  • 3つの値を抽出したいときはSwizzle Float3、Swizzle Integer3
  • 4つの値を「あべこべ繋ぎ」にしたい場合はSwizzle Float4、Swizzle Integer4

To系ノード(Cast nodes)

先ほど説明した通り関数内では同じ種類の型でなければ計算できません。

SwizzleではFloat1と、Float4の値を分解して計算することができるようになりますが、 FloatとIntegerなどの型の種別が違うものではSwizzleで分解しても計算できません。

しかしFloatとIntegerで計算したい場合があるはずです。この時にTo系ノードを使います。

このノードはプログラミングでいうところのキャスト、型の変換をするノードです。

Float4をInteger4にしたり、Integer3をFloat3にしたりできます。

To系ノードとSwizzle系ノードを連携させて使うことで任意の値を計算させることが可能になります。

To系ノードには以下の種類があります。

  • To Float
  • To Float2
  • To Float3
  • To Float4
  • To Integer
  • To Integer2
  • To Integer3
  • To Integer4

※To Booleanノードは用意されておらず、プリセット関数としてBoolean to Flaot1が存在しています。

Vector系ノード(Vector nodes)

Swizzle系ノードが分解するノードならVector系ノードは合体させるノードです。

例えば計算過程でFloat4をSwizzle系ノードでFloat1に分解したとします。

しかしOuputの型はFloat4だとしたら、値を合体させてFloat4にしなくてはなりません。

そんな時にVector系ノードが役に立ちます。

Vector系ノードには以下の種類があります。

  • Vector Float2
  • Vector Float3
  • Vector Float4
  • Vector Integer2
  • Vector Integer3
  • Vector Integer4

演算子ノード(Operator nodes)

関数内で値の計算を行うときに使うノードです。

  • Add 足し算
  • Subtraction 引き算
  • Multiplication 掛け算
  • ScalarMultiplication ベクトルのスカラ倍
  • Division 割り算
  • Modulo 剰余計算(余り)
  • Negation マイナス変換
  • DotProduct 内積計算

これらのノードでさまざまな計算を行って値を加工していくことになります。四則計算は計算の根幹ですのできわめて重要なノードです。

また周期性のある処理にModulo、ベクトルの処理を行うためにScalarMultiplication、DotProductも多用することになるでしょう。

比較ノード(Comparison nodes)

値の比較を行って処理を分岐させたくなる場合があります。このとき比較ノードを使います。

いずれも比較の真偽でTrueかFalesを出力します。

後述する論理ノードとの組み合わせ、またIf…Elseノードとの組み合わせで多用することになります。

  • Equel 等しい(AとBが等しい時にTrue、等しくない時にFalseを出力)
  • NotEquel 等しくない(AとBが等しい時にFalse、等しくない時にTrueを出力)
  • Greater 以上(AがB以上の場合True、それ以外はFalseを出力)
  • Greater or Equel 以上か等しい(AがB以上か等しい場合True、それ以外はFalseを出力)
  • Lower 未満(AがB未満の場合True、それ以外はFalseを出力)
  • Lower or Equel 以下(AがB以下の場合True、それ以外はFalseを出力)

論理ノード(Logical nodes)

関数内で条件の真偽が必要になる場合があります。この場合に使うのが論理ノードです。比較ノードや後述するIfElseノードとの組み合わせで使われる場合が多いです。

  • And どちらもTrueならTrueを出力、それ以外はFalseを出力
  • Or どちらかがTrueもしくはどちらもTrueならTrueを出力、それ以外はFalseを出力(つまりどちらもFalseならFalseを出力)
  • Not 入力がTrueならFalseを、FalseならTrueを出力

関数ノード(Function nodes)

値に対して丸め等を行ったり、三角関数で値を求めたりするときに使うノードです。

  • Absolute 絶対値
  • Floor 小数点以下切り捨て
  • Ceil 小数点以下切り上げ
  • Random ランダムな値を出力
  • Cosine コサイン
  • Sine サイン
  • Tangent タンジェント
  • ArcTangent2 アークタンジェント2
  • Logarithmic 対数
  • Exponential 指数
  • Cartesian デカルト座標
  • Exponential 指数
  • SquareRoot 平方根
  • Max 最大値
  • Min 最小値
  • LinerInterpolation 線形補完
  • Pow2 二乗

三角関数系のノードは座標計算に極めて重要なノードです。また値の丸め系ノードも値の加工に大変便利です。便利なノードがいろいろ用意されています。

制御ノード(Control nodes)

ノードの処理進行の分岐を行うためのノードです。

  • If…Else Booleanによる処理の分岐
  • Sequence 関数内変数を出力する場合に使用

    SDの関数にはfor構文等のループがないためIf…Elseは極めて重要になります。

Samplerノード(Sampler nodes)

処理上でインプット画像のピクセルの色情報を取得する場合に使います。フィルタ系など画像に対してなんらかの加工を行う関数では必須のノードです。

カラーとグレースケール用が用意されています。

  • SampleGray グレイスケール用のサンプラー
  • SampleColor カラー用のサンプラー

関数内にパラメータを引き込む

パラメータをより理解するためにノードの構造を知っておきましょう。

アトミックノードとインスタンスノード

アトミックノード

アトミックノードはそれ以上分解できないハードコードなノードです。

アトミックノードの場合、BaseParametersSpecificParametersという分類のパラメータがあります。

Base Parametersはサイズやタイリングなどに関する基本的でほぼすべてのノードで共通のパラメータです。

Specific Parametersはそのノードに固有のパラメータです。多くの場合このSpecificParametersを使用することになります。

インスタンスノード

アトミックノード以外のSDのライブラリ等にに入っているノードはすべてアトミックノード(と関数)の組み合わせでできているノードです。

これらのノードはインスタンスノードと呼ばれています。

インスタンスノードの場合、BaseParametersとInstanceParametersという分類のパラメータがあります。

Base Parametersは先ほどの説明と同じで、InstanceParametersはアトミックノードで言うところのSpecificParametersとほぼ同じものです。

違うのはこのInstance Parametersは既存のノードのパラメータをExposeしたか、InputParameters機能によって作られたパラメータであるという点です。(この点は後述します)

インスタンスノードは編集はできませんが、その構造を確認することが可能です。

例えばSlopeBlurノードの中身をのぞいてみましょう。

ノードを右クリックしてOpenReferenceを選択します。編集の確認のダイアログが表示されるのでOKをクリックするとSlopeBlurノードがどのようなアトミックノードや関数から作成されているかが確認できます。

SlopeBlurノードは大量のWarpノードとBlendeノード、またそれらのノードのパラメータに関数を設定して作られていることがわかります。

Blendeノードを確認してみるとOpacityとBlendeingModeに関数が設定されていることがわかります。その関数内ではSamplesというパラメータを取得して値の計算をしていることがわかります。

SlopeBlurノードのグラフルートに戻ってInputParametersパネルを確認してみると確かにSamplesパラメータが作成されており初期値は8になっています。

これは今後自作のノードを作る際の仕組みと何ら変わりはありません。いろいろなノードの構造を確認するとより理解が深まるのではないかと思います。

パラメータを自由に扱いたい

ノードのパラメータは普通スライダーなどで動かせるようになっています。それは各ノードに張り付いていますのでそのパラメータを操作するにはそのノードのそのパネルに行かなくてはなりません。

しかし時にはノードにパラメータが張り付いているのが面倒な時があります。フレキシブルにパラメータ値を変更したい場合などはどこかで一括してパラメータを操作できた方が楽ですよね。

ですのでSDではノードからパラメータを分離させることができます。つまりパラメータを「扱える場所」を
各ノードのパネル以外に作れる
のです。

そしてそのパラメータは変数として関数内で使うことも可能になります。

Expose

パラメータはExposeすることで簡単に分離させることができます。

ExposeするとそのパラメータはグラフルートのInputParametersパネルへ管理が移動します。

関数内で値を加工する必要のない値などは直接ExposeしてInputParametersパネルへ持っていくことで扱えるようになります

パラメータを自由に作る

グラフルートのInputParametersパネルで新しいパラメータを作ることができます。

基本的にはSDで関数を使う場合ここでパラメータを作ることが多くなるかと思います。

ここで誤解の無いようにしたいのですが、InputParametersで作成するパラメータは「パラメータそのもの」を作るのではなく「パラメータインターフェース」を作るということです。

ここで作られたばかりのパラメータはどのノードのパラメータとも接続されているわけではなくただパラメータの空箱のような状態なのです。

これはプログラミング的にいうと数値を入れられる変数とその変数を操作する操作盤を作るみたいなものです。

作った「パラメータインターフェース」から送られる数値を各ノードの各パラメータの関数に入力することで、そのパラメータと接続されることになります。

後述するノードの説明でも触れますがGet系ノードが「パラメータを取ってくる」役目を行います。

グラフのルートに作られらパラメータを任意のノードの任意のパラメータの関数内で取得することで、値を受け取り関数で加工して出力することが可能になります。

実際の操作に関しては実践編で後述します。

注意・値を扱う時はこんがらがりやすい

値はFloatにしてもIntegerにしてもX,Y,Z,Wで表せられますが、このX,Y,Z,Wで値を表すのは便宜的なもので、なんでもX,Y,Z,Wになります。例えば位置情報を扱っているときもX,Yですし、色を扱っているときでもX,Y,Z,Wです。

位置情報の場合はX,Yだけで表せますが、色の場合は例に挙げたようにRGBAの4つの値が存在ます。

色を扱うときはX=R、Y=G、Z=B、W=Aという風に対応するわけです。

自動的に値の名前に切り替わってくれるといいのですが、そうはなってくれないのでこれは頭の中で「このXはいま色情報のRの値が入っている…」と認識しないといけません。

後々見返す時のためにコメントなどで値を状況を分かりやすくするのも大切です。

組み込み変数

SDではいくつの変数(パラメータだと考えてもらってOKです)があらかじめ用意されています。いずれも非常に重要な値できわめて有用です。

種類は少ないので役割をしっかり覚えましょう。

$pos

$posは現在処理しているピクセルの位置情報が入っています。値はFloat2でUV座標と考えるといいかもしれません。

この変数はとても重要です。Fx-mapでもPixel Processorでも使えるうえに「ピクセル数分の繰り返し」処理には必須です。

$posはテクスチャのサイズにかかわらず常に0~1の値になります。つまり1ピクセル当たり$posが進む量は1/ピクセル数ということになります。
つまりたとえば2048px(2k)テクスチャの場合、1ピクセル当たりのは1/2048で0.00048828125となります。

XとYにそれぞれ2048ずつピクセル列があるわけですので全体のピクセル数は4194304となります。

実際のイメージはこうなります。座標のようにイメージするとよいかもしれません。

$posは0から始まるためX行一行目は(X,0)となります。

テクスチャサイズが2048pxの場合の$posがどのような値になっているか?

  • 最初のピクセルは(0,0)でX方向に次のピクセルは(0.00048828125,0)となります。
  • 2つめのピクセルは(0.0009765625,0)
  • 3つ目のピクセルは(0.00146484375,0)
  • ずっと進んで512コ目のピクセルは(0.25,0)になります。
  • さらに進んで1024コ目のピクセルは(0.5,0)になります。
  • そしてX行最後の2048コ目のピクセルは(1,0)となります。

イメージできましたでしょうか?

オフセット的な挙動

お気づきの方もいらっしゃるかと思いますが、例えば(0.5,0.5)とした場合それはテクスチャ画像の中央を表すことになります(つまりX方向に1024個目、Y方向に1024個目のピクセル)。

また(0.25,0.25)(0.25,0.75)(0.75,0.25)(0.75,0.75)はテクスチャ画像を4分割した場合のそれぞれ左上、左下、右上、右下の中央を表すことになります。

こう見るとピクセルの絶対数を扱うより相対で値を扱うほうがテクスチャサイズに対して柔軟に対応できることがわかるかと思います。

$size

$sizeは名前の通りテクスチャのサイズが格納されています。値はFloat2でテクスチャサイズがそれぞれX,Yに入っています。

2048px×2048pxならX=2048,Y=2048となります。

テクスチャサイズがわかると先ほど説明した$posをより柔軟に扱うことができます。

1/$sizeを計算すると$posのピクセル当たりの値を計算することができます。つまりピクセル位置の単位になりえるわけです。

$sizelog2

$sizelog2はテクスチャサイズを2の累乗で表したときの指数を取得できます。2048×2048の場合なら2048は2の11乗なので11が返ってきます。

この数字Absolut時のOutputSizeの数値と同じだと気が付いた方もいらっしゃるかもしれません。

関数実践編 PixelProcessorノードで画像を回転させてみる

PixelProcessorノード(以下PPノード)はピクセル処理に特化したノードです。Fx-mapも強力なノードですが、PPノードはFx-mapノードよりも柔軟な処理を行うことができます。

PPノードは入力された全ピクセルを対象に処理を行います。

PPノードではほぼ間違いなく処理に関数を使用することになるため関数に慣れるための題材としとは最適です。

処理としては単純にピクセル分関数が繰り返し実行されることになります。

例えば2048×2048であれば4194304回関数を実行します。

非常に繰り返しが多いように感じますが計算はほぼ一瞬で終わりますので負荷はほとんど気にしなくて大丈夫です(厳密には関数の状況によって負荷が高まりコストが高くなる計算になる可能性もあります)

それでは早速画像を加工していきましょう。

回転を実施するための画像を一枚用意してください。正方形の画像が好ましいです。大きさは任意ですがあまり大きくない方がいいでしょう。

回転させるとは?

回転の公式は以下のようになります。本テキストでは著者自身が数学に詳しくないこともあり、また本テキストの範囲外となるため公式の詳しい説明は割愛させていただきます。

もし必要であれば数学の該当する書籍(行列、ベクトル等)を参照してください。

回転の公式

原点を中心として(x,y)を(x',y')へ回転させる場合

x’=x cosθ – y sinθ
y’=x sinθ + y cosθ

この公式をノードで再現することになります。

処理のイメージ

GetFloat2で組み込み関数の$posを取得します。

$posは画像の左から右へ、上から下への順番でピクセルの座標を取得することができます。

取得した座標に加工を加えることで任意の場所の座標に変換させてあげることができます。

そして最終的にSamplerノードにて加工した座標を用いて、Input画像のピクセルの色情報を取得し左上から右下に向かって並べ直します。

今回は画像を回転させたいので回転先へInput画像から読み取ったピクセル色情報を配置させるということになります。

$posはあくまでもピクセル座標を順番に取得できるだけですし、最終的にOutputする際も順番にピクセルが出力されることになります。

「ピクセルを移動させる」というよりも、「移動先に入るピクセルを何らかの座標加工で取得して左上から右下の順に並べ直す」といったいめイメージがより近いかと思います。

この点は$posをつかった処理で少しイメージしにくい部分ですが、理解できるといろいろな処理を行えるようになります。

パラメータを準備する

今回は画像を回転させたいので回転させるための値が必要になります。そのためのパラメータを作成します。

グラフの何もないところをクリックしてInputParametersパネルにてプラスボタンを押しましょう。

そうするとinputというパラメータが一つできました。

いろいろ設定できる項目があります。順を追って確認していきます。

Identifier

グラフ内で一意の名前です。他のパラメータと名前が被ってはいけません。今回は”Rot”にしました。

Type/Editer

値の型と値を操作するためのインターフェスの形状を設定します。初期状態では型がFloat1でインターフェースはSliderになっています。今回はこのままでOKです。

ちなみにインターフェースは型ごとに若干違います。

  • Float1ではSlider、Angle、Color(GrayScale)
  • Float2ではSliderのみ
  • Float3ではSliderとColor(RGB)
  • Float1ではSlider、Tranceformation、Color(RGBA)
  • Integer1ではSlider、DropDownList
  • Integer2ではSlider、Size pow 2
  • Integer3ではSliderのみ
  • Integer4ではSliderのみ
  • BooleanではButtonsのみ
  • stringではTextのみ

使えるインターフェースからもどの型がどんなパラメータに対応しているかがわかってきますね。

Description

パラメータの説明です。他人にグラフを使ってもらうときにあると親切ですが今回はなくてOKです。

Label

パラメータの表示名です。ここで設定した名称で表示されることになります。ここも”Rot”でOKです。

Group

いくつかのパラメータが存在する場合にパラメータを分類させるためのパネル名称です。ここにグループ名を設定しておくと他のパラメータに同じグループ名を設定すればそれらのパラメータは同じパネルに分類されるのでパラメータが見やすくなります。ここは今は空白でOKです。

Defalt

初期値です。ノードを設置したときに最初から設定される値をここで指定します。0に設定してください。

Min/Max

値の最大値/最小値です。値がとれる範囲を限定できます。Minが0、Maxは1に設定しておいてください。

Clamp

値の固定のスイッチです。これはFalseのままでOKです。

Step

値の増減の単位を設定します、ここはとりあえず0.01にしてください。

UserDataおよびVisible If

UserDataには任意のインフォーメーションを記入できます。今回は空白のままでOKです。

Visible Ifは一定の条件においてパラメータを見える状態にするかどうかの設定です。条件式を記入することでパラメータの可視属性をコントロールすることができますが今回は空白でOKです。

ノードを組んでみる

さてそれでは実際にノードを組んでいきましょう。

まず回転させたい画像が必要です。Bitmapノードを設置し回したい画像を読み込みます。LinkにするかImportにするかは今回どちらでも大丈夫ですのでお好きな方法でOKです。

そしてPPノードを設置しBitmapノードの出力をPPノードのInputにつなげます。

PPノードのPerPixelFunctionのEditボタンを押してPPノードの関数編集画面に移行します。

実際に組んだノードはこのようになりました。

まずGetFloat2で組み込み変数の$posを取得しています。

$posを取得するにはGetFloat2のVariables/GetFloat2パネルのドロップダウンリストから$posを選択します。ここで選択できるパラメータはそのノードの型で規定されたパラメータのみが表示されます。

この変数は説明したとおり順次現在処理対象のピクセルの座標を出力してくれます。つまり座標を順繰り処理するために必要になります。

$posから受け取った座標はSineノードやCosineノードで計算する必要があるのでSwizleFloat1でX値とY値に分解します。

ここからは回転の公式を使って計算するためのノードです。

回転の公式は以下のようなものでした。

x’=x cosθ – y sinθ
y’=x sinθ + y cosθ

これは

X値とcosθをMultiplicationした結果から、Y値とSinθをMultiplicationした結果をSubtractionすることでX’の値を算出する
x値とsinθをMultiplicationした結果に、Y値とcosθをMultiplicationした結果をAddすることでY’の値を算出する

ということになります。

この部分を実際ノードで組んでみると以下のようになります。

作業の様子を動画にしましたのでこちらも参照してください。

計算のθは回転の角度で、今回はこのθを先ほど作ったRotパラメータから入力することになります。

cosθ sinθ がそれぞれ一つずつあればいいのですが、見た目でわかりやすさを優先したためcosθ sinθはそれぞれ2個ずつ用意しました。

効率を考えるならもちろん省略した方がいいのですが、将来的に何らかの計算を追加することが考えられる場合はできるだけ計算は別個で行っておくと値の切り分けがスムーズになることもあります。

上段部分のノードではcos×Rotの算出値にX値をMultiplicationした値からsin×Rotの算出値にy値をMultiplicationした値をSubtractionしています。

下段部分のノードではSin×Rotの算出値にX値をMultiplicationした値とcos×Rotの算出値にY値をMultiplicationした値をAddしています。

Multiplicationに関しては値をどちらのInputに挿しても結果は変わりませんが(9×3と3×9は同じになる理屈)、Subtractionの場合はノードの接続順で算出される値が異なります(9-3と3-9は違う答えになる理屈)ので注意が必要です。

SubtractionやDivisionなど計算順が結果にかかわるノードの場合は必ずノードInputのAとBの関係を把握しておきましょう。

この工程で算出された値は「処理対象のピクセルを原点を中心に回転させたときの座標値」でしたね。

ですのでこの座標値を使ってSampleColorノードで回転対象の画像からピクセルを取得して並べ替えます。

今回はカラー画像を想定していますが、グレースケールの場合はSampleGrayノードを使いますし、どちらをインプットしても処理を行えるようにするには比較ノードや論理ノード、If…Elseノードを使って処理を分岐させることもできます。

SamplerノードではFloat2をInputとして扱いますので算出した値はVectorFloat2でFloat2にまとめます。

まとまったFloat2をSampleColorノードのInputに挿します。

先ほども説明した通りSDの関数でピクセルを並べ直す場合は、「ピクセルを移動させる」というよりも、「移動先に入るピクセルを何らかの座標加工で取得して左上から右下の順に並べ直す」というイメージです。

Samplerノードはその処理をおこなうノードです。

この点はイメージしにくく直観的ではありませんがオフセット(ずらす)的に処理を行うことで相対的な値として扱えるのがSDの魅力でもありますのでなるべく理解するようにしましょう。

最後にSampleColorノードを最終的なOutputにしたいので、右クリックしてSet as Output Nodeを選択します。

これで画像を回転させる関数ができあがりました。

さて組んでみたノードで実際に画像を回転させてみてください。

グラフルートに戻りRotの値を変更すると回転するはずです。

パラメータとしての見た目にしたい場合はSwitch to the preview modeボタンを押すことで本来のパネルに表示させるパラメータの表示形式になります。

しかし回転するにはしましたが回転の中心が画像右上(0,0)の位置になっていますね。これはあまり好ましくないですね。

回転の中心を変更する

任意の位置を中心として回転させたい場合、回転の中心を計算する必要があります。

なにも仕組みを用意しない場合、回転の中心は左上つまり(0,0)になります。

多くの場合で画像中央を原点として回転させたいのではないかと思いますので原点を(0.5,0.5)にしたいですよね。

これは簡単で画像の中心である座標(0.5,0.5)を取得した座標からsubtractionし、回転の公式を計算しSamplerノードに座標を渡す直前で座標(0.5,0.5)を公式計算後の座標にaddすることで回転の中心を画像中央にすることが可能です。

SDの値は相対値であることが多いですのでこのようなオフセット的な考え方でノードを組むことが多くなるかと思います。

簡単にイメージするとずらして計算してもとに戻すといった感じの処理をしていることになります。

実際にそのようにして組んだノードが以下のようになります。

作業の様子を動画にしましたのでこちらも参照してください。

先ほどのノードよりも複雑にみえますが、単に座標をずらして計算して元に戻す処理を加えただけです。

2つのConstanceノードが増えています。

左の増えたノードは座標をずらすために必要なノードですね。これはFloat1を二つ用意してそれぞれXとYとして扱ってもかまいませんが、後々パラメータ化する可能性もあることからFloat2で取得できた方がいいかと思います。

このFloat2ノードではX=0.5、Y=0.5の値を設定しています。つまり画像の半分の幅だけずらす値ですね。

このノードをSwizleFloat1で分解して、それぞれ$posをSwizleFloat1で分解した座標値のXとYからSubtractionしています。これで$posから取得された値は上下に0.5ずつずれたことになります。

回転の計算部分に関しては変わっていません。

回転の計算をしたのちにずらした分の0.5を足してもとに戻しています。これでずれた状態を解消しています。

さらにVectorFloat2で値を合わせる直前に別な値をXとYに足していますね。

これは任意で画像の移動を行えるようにしました。

パラメータを増やしてより機能を増やす

この値とさきほど回転の原点を設定するために設置した値をパラメータ化してコントロールできるようにしましょう。

先にパラメータを作ります。

回転の原点パラメータのIdentifierとLabelは"RotationCenterCoordinates"に、画像を移動させるパラメータのIdentifierとLabelは"ImageOffset"にしてみました。

Defaltは"RotationCenterCoordinates"が0.5、0.5で"ImageOffset"は0、0です。

回転の原点を設定する値も画像を移動させる値も型はFloat2です。

インターフェースはSliderで良いでしょう。

また今回はパラメータが増えたので整理の意味合いも含めてGloupを設定してみましょう。

Rotと"ImageOffset"は変形のためのパラメータなのでTrancformationというグループにしましょう。

またこの際Rotではわかりにくくなるので"ImageRotation"にIdentifierとLabelを書き換えて意味を分かりやすくしておきましょう。

"RotationCenterCoordinates"は変化の中心のパラメータなので"RotationCentert"というグループにしてみます。

パラメータの順番も変えておきましょう。

"RotationCenterCoordinates"が上にあった方がいいと思いますので"RotationCenterCoordinates"パラメータを一番上に移動させます。

パラメータの左端部分をドラックすることで順番を変えることができます。

そのほかは初期値のままでOKです。

GetFloatノードの設置

パラメータができましたので、グラフのConstanceノードをGetFloatノードで置き換えます。

座標をずらすためのパラメータが"RotationCenterCoordinates"ですのでGetFloat2を設置してRotationCenterCoordinatesパラメータを設定し取得状態にしてグラフ右端にある0.5、0.5のFloat2ノードと差し替えます。

また画像を移動させるためのパラメータが"ImageOffset"ですので"ImageRotation"の隣にある0、0のFloat2ノードを、"ImageOffset"を取得状態にしたGetFloat2で差し替えます。

さてこれで回転の中心点を変える、画像を移動させるという機能を実装できました。

パラメータ確認

グラフルートに戻り実際のパラメータの見た目、挙動を確認してみましょう。

Switch to the preview modeを押してパラメータの見た目を確認してみてください。

ちゃんとグループ分けされており見やすくなっていますね。パラメータの名称もわかりやすくなりました。

SDに限らないことですが、再利用を多用するソフトウェアでは名称設定は非常に重要です。

1か月前に作ったものを正確に記憶できている人は稀有なはずです。

大抵なんの処理を行っていたのか、そのパラメータがなんなのかを忘れます。

そのときパラメータ名がデフォルトのinputのような場合いちいち確認しなくてはならなくなります。

非常に無駄な時間を費やすことになるので、未来の自分を楽させてあげられるように面倒でも名称設定はしっかり行っておきましょう。

そしてパラメータをいろいろ動かしてみてちゃんと機能するかどうかを確認してください。

ノードが間違っていなければちゃんと回転し中心を変えられ移動させられるノードになっているはずです。

補足 $posの挙動で重要なこと

$posから取得した値を加工した場合

便宜的に整数で説明しますが、例えば$posから取得した値が1ピクセルあたり1だった場合に、2を掛けたとします。

つまり取得した座標と加工した座標は以下のようになりますね。

(0,0) (0,0)
(1,0) (2,0)
(2,0) (4,0)
(3,0) (6,0)
(4,0) (8,0)
(5,0) (10,0)

2つのピクセル分の座標を抜かして座標が進んでいくことになります。こうして進めると加工した座標の方が先にXなりYなりの終端にたどり着くのが早くなりますよね。

もしテクスチャサイズが2048×2048の場合、本来Xのその行の終端にたどり着くには2048回の繰り返しが必要になるのですが、上記の加工した座標の場合2倍で進んでいるので1024回の繰り返しの時点でXのその行の終端にたどり着くことになります。

しかしPPノードはそんなことはお構いなくテクスチャの総ピクセル分の繰り返しを行います。

つまり加工して早く終端にたどりついた座標値もそこで処理が終わるわけではなく、もう一度最初の位置から座標を取得することになります。

1024回目の繰り返しで終端にたどり着いた座標はまだ1024分の繰り返しが残っているので、その行のXの始点の座標から再度取得していくことになります。

実際にノードを確認した方がわかりやすいかと思います。

このノード例は$posで取得した座標をSwizleで抽出してXとYそれぞれに係数を掛けています。先ほどの説明と同じく座標が設定した係数倍で進んでいくことになります。

例では係数を8にしてみました。

このノードを出力してみると画像がタイリングされました。それぞれXYとも8回繰り返し始点から終点に取得されたことを表しています。

この$posとPPノードの挙動は処理を考えるときに重要になってきますので是非理解してくださいね。

まとめ

本テキストは未完成です。Fx-mapノードやPPノードでの関数を活用した実際例を追加して更新していきます。

また質問や疑問などをいただけると記事にフィードバックできるかもしれません。

ブログのコメント欄はOFFにしているので、Twitterで問い合わせいただけると幸いです。

@MINO_8601