レイヤーインスタンシングのまとめ

Substance Painter 2017.4 がリリースされました。
SPの大型アップデートとしては多分今年最後です。以降はバグフィックスのみでしょう。

今回のアップデートではプラグインとしてDCC Live Linkが追加されています。
名前の通り、他のDCCツール(MayaやMODOなど)でメッシュを表示しながらSPでペイントをするためのもののようです。
レンダリングしながらペイントとかもできるのでしょうかね?
ゲーム開発の現場の場合、Maya上にゲームと同じレンダリング状態を提供しているチームにとっては便利かもですね。

さて、今回のアップデートの目玉機能はというと、やはりレイヤーインスタンシングでしょう。
これまでのSPでは、テクスチャセット(FBX上のマテリアル)が別のものに同じマテリアルを割り当てたい場合、コピペして対応するしかありませんでした。
ある程度絵面が固まった終盤でならコピペでも行けるかもですが、序盤だと元のマテリアルを何度も修正すると思いますのでコピペが多発します。
このような状態を回避することができるのが今回のインスタンシング機能です。

使い方は簡単。
まず、インスタンス元のレイヤー(塗りつぶしレイヤーやフォルダ)を選択し、マウス右クリック→[Instantiate across texture sets...]を選択します。
f:id:monsho:20171125195032p:plain
すると、以下のようなインスタンシングウィンドウが表示されます。
f:id:monsho:20171125195235p:plain
インスタンシング先レイヤーを選択して[OK]ボタンを押すと、選択していたレイヤーがインスタンス先のテクスチャセットの最上位レイヤーにインスタンシングされます。
f:id:monsho:20171125195532p:plain
インスタンス元/先レイヤーには文書のようなアイコンが設定されます。
このアイコンをクリックするとインスタンスプロパティウィンドウが表示され、このレイヤーのインスタンス元と先がわかりやすいように一覧表示されます。
f:id:monsho:20171125200032p:plain
このプロパティウィンドウから編集中のテクスチャセットを切り替えることも可能になっています。
インスタンスレイヤーの調整はインスタンス元からしかできないので、どこからインスタンスしたのかわからないという状況になったら押してみるとよいでしょう。

なお、インスタンスレイヤーをさらにインスタンスすることもできます。
f:id:monsho:20171125202824p:plain
しかし闇雲にインスタンス機能を使用してもあとで迷ってしまうのがオチです。あまり深くインスタンシングしないようにしましょう。

インスタンス先ではインスタンス元のレイヤーの内容を引き継ぎます。
引き継がれるのはマスクやフィルタなどほとんどすべてです。
インスタンス元がフォルダの場合、フォルダ内の設定も基本的にすべて引き継がれます。
インスタンス元にフィルタを追加したり、フォルダに新しいレイヤーを配置したりすればそれらも反映されます。

そしてインスタンス先で新たにマスクを追加することが可能です。
例えば以下のようにインスタンス元レイヤーにEdge Wearマスクを適用していると、インスタンス先にも適用されます。
f:id:monsho:20171125203928p:plain
ここでBodyには元のマスクに加えて別のマスクも追加したいということもあるでしょう。
このような場合はインスタンス先レイヤーでマスクを設定、まずは塗りつぶしフィルタで真っ白にしておきます。
こうすることで元のマスク情報を取得できますのでこの状態に別のジェネレータなどを適用します。
以下の例ではGlass Edge Wearを減算で適用しています。
f:id:monsho:20171125204227p:plain
このようにインスタンス元の状態を生かしつつマスクの追加が可能になります。
残念ながらインスタンス先でペイント側にはフィルタが適用できないので、適用したい場合はパススルーレイヤーを追加してそちらでフィルタをかけましょう。

インスタンスはほとんどの情報をインスタンスできますが、例外が存在します。
ペイントレイヤーは残念ながらインスタンス対象外です。ペイントフィルタも同様です。
まあ、ペイントレイヤーをインスタンスできてもどう使えばいいのかわかりませんがね…
もちろん、パーティクルブラシもインスタンスできないということになります。

もう1つの例外はアンカーポイントです。
アンカーポイントは特にノーマル情報を元にしてマスクを生成したい場合に用いられます。
アンカーポイントを使わないと、使用できるノーマル情報はベイクしたテクスチャの情報に限られますが、アンカーポイントを使うことでペイントした結果生成されたノーマル情報も使えるようになります。
大変便利な機能ではあるのですが、これは特定条件でのみインスタンス可能となります。
それは、フォルダをインスタンスしていること、そのフォルダ内からアンカーポイントを参照している場合です。
つまり、このような状態です。
f:id:monsho:20171125210043p:plain
この状態であればインスタンス先でも同じ効果を得ることができます。

もしもインスタンス元が普通の塗りつぶしレイヤーで、そこでアンカーポイントが参照されているとどうなるかというと、こうなります。
f:id:monsho:20171125210450p:plain
画像にもあるように、インスタンス元のアンカーポイントが不正扱いされ、結果としてインスタンス元/先ともにアンカーポイントが無視されます。
これらのことから、インスタンスレイヤーはできるだけフォルダで利用しておく方がよいのではないかと思います。

レイヤーインスタンシングについての検証まとめは以上になります。
もし他にも情報がありましたらこっそり教えていただけるとありがたいです。

小ネタ 簡易ワームホール的エフェクト

なんか思いついたので小ネタでエフェクト用テクスチャを簡易で作ってみるテスト。
表題通り、ワームホール的な奴です。
作ったのはこちら。

f:id:monsho:20171116083558g:plain

ブラウザで見ると奥の方が動いてないように見えるけど…まあいいや。

ノードはこちら。

f:id:monsho:20171116083807p:plain

Transform2DのOffsetパラメータ内はこうなってます。

f:id:monsho:20171116083854p:plain

あとはSubstance Playerで再生するだけ。
Frequencyパラメータで周期の変更もできます。
ちゃんとループもするのでエフェクトでも使いやすいでしょう。

以上、小ネタでした。

Substance Automation Toolkit で Pixel Processor を作成する

前回、Substance Automation Toolkit (SAT) を使ってノードを接続する方法について解説しました。
しかしこの手法、普通にマテリアルを作成するには不向きです。
テンプレート的な処理を大量に作る分にはいいのですが、1点もののマテリアルを作るなら普通にSubstance Designerを使った方がよいでしょう。

しかし現在のSATには式グラフを比較的簡単に生成する機能が提供されています。
sbsMathというモジュールがそれです。
SATの標準モジュールだけではプログラムコードを使ってノードを繋げることしかできませんが、sbsMathを利用することでプログラムで数式を実装する感覚で式グラフを作成できます。
が、このモジュールはなぜかSATの標準モジュールとして提供されていません。
いや、正確には提供されているのですが、なぜか "MyDocument/Allegorithmic/Substance Automation Toolkit" 内にインストールされます。

sbsMathを利用しているサンプルは同じフォルダ内にある raytracer.py です。
このサンプルは Pixel Processor を利用して各ピクセルごとにレイトレースして球をライティングしてレンダリングするサンプルです。
複雑に見えますが、sbsMath の機能を一通り使っているのでサンプルとしては十分でしょう。
とは言え、いきなりサンプルを全部解説、というのもあれなので、とりあえずより簡易なサンプルで解説しましょう。

今回作成したサンプルは、2枚のノーマルマップの各ピクセル内積を取るものです。
何かに使えるのか?という話をされると…使い道は全くないんですが、これを Pixel Processor で作成しようとすると意外と面倒だったりします。

内積計算自体は式ノードに存在するのでそのまま使えばいいのですが、ノーマルマップをサンプルした値はそのままベクトルとして使用できません。
カラーマップは負の値を保存できないため、ノーマルマップに格納されている値は -1~1 を 0~1 に圧縮しています。
つまりベクトルとして利用するには 0~1 を -1~1 に伸張しなければなりません。
そのための計算式は以下の通りです。

v = color * 2 - 1

まあ、正直な話、この程度の処理ならやっぱりSDで作った方が早いような気もしますがね…

とにかく、コードを見ていきましょう。
まず、インポートするモジュールですが、sbsMathを追加する必要があります。

import sbsMath.sbsmath as sm
import sbsMath.sbsmath_tools as st

sbsmathとsbsmath_toolsをそれぞれ sm, st という名前空間で取り扱います。

次にノードグラフの作成部分ですが、基本的には前回と同じです。
作成するのはカラーの入力ノードを2つ、それぞれID normal_0, normal_1 とします。
出力ノードはグレイスケールです。
最後の1つは Pixel Processor です。

    # pixel proc
    pix_proc_node = sbsGraph.createCompFilterNode(aFilter = sbsenum.FilterEnum.PIXEL_PROCESSOR,
        aGUIPos      = input_normal_0.getOffsetPosition(xOffset),
        aParameters  = {sbsenum.CompNodeParamEnum.COLOR_MODE:sbsenum.ColorModeEnum.GRAYSCALE})

出力はグレイスケールにしておきます。

問題なのは Pixel Processor への入力です。
このノードは最初の段階では入力ピンが1つしかありませんが、そのピンへノードを繋ぐと次のピンが出現するという仕組みになっています。
そのため、あるノードを Pixel Processor の入力ピンへノードを接続する場合、名前を直接指定する必要があります。

    # connection
    sbsGraph.connectNodes(aLeftNode = input_normal_0, aRightNode = pix_proc_node,
        aRightNodeInput = 'input')
    sbsGraph.connectNodes(aLeftNode = input_normal_1, aRightNode = pix_proc_node,
        aRightNodeInput = 'input:1')

最初のピンは input という名前で接続できます。
1つ目のピンへの接続はピンの名前を指定しなくても勝手に接続されるのでさほど問題はありません。
これに対して2つ目のピンは input:1 という名前で指定する必要があります。
ここから先に接続する場合も input:n という形で順次接続できます。
接続順番も、かならず0番から進めてください。1つ以上飛ばして接続しようとすると失敗します。

では、Pixel Processor の式グラフの作成です。命令は簡単で、以下のようにします。

    # pixel proc function
    st.generate_function(NormalMapDot, sbsDoc, fn_node = pix_proc_node.getPixProcFunction())

sbsmath_tools.generate_function() を使うと、特定の式グラフに対して式ノード生成関数をを走らせます。
第1引数に渡している NormalMapDot という関数がノードの作成を行ってくれます。
実装は以下のようになっています。

def NormalMapDot(fn):
    pixel_pos = fn.variable('$pos', widget_type=sbsenum.WidgetEnum.SLIDER_FLOAT2)

    normal0 = sm.create_color_sampler(pixel_pos, 0)
    normal1 = sm.create_color_sampler(pixel_pos, 1)

    normal0 = sm.swizzle_float3(normal0, [0, 1, 2])
    normal1 = sm.swizzle_float3(normal1, [0, 1, 2])

    normal0 = normal0 * fn.constant([2.0, 2.0, 2.0]) - fn.constant([1.0, 1.0, 1.0])
    normal1 = normal1 * fn.constant([2.0, 2.0, 2.0]) - fn.constant([1.0, 1.0, 1.0])

    dot_p = sm.dot(normal0, normal1)
    return fn.generate(dot_p)

sbsmathモジュールには式グラフで使用可能なノードが登録されています。
ここではベクトル要素のスイズルを行う SwizzleFloat3 や内積演算の Dot を使っています。
ノードや計算式の結果は変数に登録することができ、最終出力をgenerate()関数に引き渡せば自動的にグラフが生成されるというわけです。
ね、簡単でしょ?

作成したグラフは以下のようになります。
f:id:monsho:20171115003858p:plain
そして Pixel Processor の中身は以下です。
f:id:monsho:20171115003952p:plain
割といい感じにノードの配置もしてくれてますね。
ノード数も少ないので、やはりこれくらいならSD上で作成した方が早いかもしれませんが、パッと見でどういう計算しているのかわかりにくいのもノードベースならではかなとも思います。

さらに複雑な計算になるとノードベースではやりたくなくなるかもしれませんので、特にエンジニアの方はPythonでの生成法も覚えておくとあとあと便利かもしれませんよ。

Substance Automation Toolkit の紹介

Substance Designer 2017のライセンスを持っている方はSubstance Automation Toolkit(以下SAT)の使用が可能です。
f:id:monsho:20171103103811p:plain Allegorithmic公式サイトの自身のアカウントからDLできるはずです。

このツールキットはその名の通り、自動化を主な目的としたツール群です。
ドキュメントは以下です。

Automation Toolkit Home - Substance Automation ToolKit - Allegorithmic Documentation

Substance Designer 6以前のライセンスであればSubstance Batch Tools(以下SBT)というものが使えましたが、SATはSBTを引き継いで機能を追加したものと考えてください。

同梱されているコマンドラインツールは以下のものがあります。

sbsbaker

メッシュからAO、Normal等のメッシュ固有のマップを生成します。
ハイポリメッシュの指定もできるので、マップのベイクを一括で行いたい場合に使えます。

sbscooker

.sbsファイルから.sbsarファイルを生成するツールです。
SD作業者がSP作業者にマテリアルやフィルタを提供する場合、.sbsarで提供するのが基本です。
このツールを使えば自動的に更新された.sbsファイルから.sbsarファイルを生成するようにすることが可能です。

sbsmutator

.sbsファイルに修正をかけることができるツールのようです。
例えば.sbsで使用しているイメージデータを入れ替えたり、パラメータを変更したりして別の.sbsを生成することができるようです。

sbsrender

.sbsarからイメージをレンダリングするツールです。
SDからテクスチャイメージをエクスポートするのを自動化することができます。
入力イメージ、パラメータ変更もできるので、テクスチャエクスポートをしたいだけならsbsmutatorは不要です。

sbsupdater

.sbsの対応バージョンを更新するツールです。
SDが更新されると、以前作成した.sbsを読み込む際に更新処理が入ります。
SDで使用する際にいちいちそれらが出力されるのは面倒なので、このツールで一括更新してしまうとよいでしょう。

多分、sbsupdater以外はSBTにもあったツールだと思います。

SATではこれらのコマンドラインツールに加え、Python用のモジュールが追加されています。
PythonAPIによってこれらのツールと同じことができるようで、かなり便利になりました。

また、このモジュールによって.sbsファイルの生成もできるようになっています。
通常であればSDを立ち上げて作成する.sbsですが、PythonAPIによってノード追加、接続などができるわけです。
使い道は…これがなかなか難しい。
簡単な接続ならPythonでも簡単に行えますが、SDを使った方がより簡単です。
複雑なマテリアルを作成するならそれこそSD使った方がいいです。
あるとすると、2つのマテリアルを指定のマスクイメージでブレンドするというノード接続において、マテリアルをいろいろ変化させて様々な組み合わせのマテリアルを作成したい場合でしょうか。
C#などでUIを作り、自身のライブラリ内のマテリアルとマスクイメージを選択できるようにしてとっかえひっかえ組み合わせを試すとか?

まあ、イマイチ使い道が思い浮かばないのですが、簡単なPythonコードを紹介しましょう。

# coding: utf-8
import os
import sys
import pysbs

from pysbs import python_helpers
from pysbs import context
from pysbs import sbsenum
from pysbs import sbsgenerator

def CreateHelloWorld(destFileAbsPath):
  aContext = context.Context()

  sbsDoc = sbsgenerator.createSBSDocument(aContext,
    aFileAbsPath = destFileAbsPath,
    aGraphIdentifier = 'TestMaterial')

  sbsGraph = sbsDoc.getSBSGraph('TestMaterial')

  startPos = [48, 48, 0]
  xOffset = [192, 0, 0]
  yOffset = [0, 192, 0]
  xyOffset = [192, 96, 0]

  # input nodes
  input_0 = sbsGraph.createInputNode(aIdentifier = 'input_0',
    aGUIPos = startPos,
    aColorMode = sbsenum.ColorModeEnum.COLOR,
    aAttributes = {sbsenum.AttributesEnum.Label: 'Input0'})
  input_1 = sbsGraph.createInputNode(aIdentifier = 'input_1',
    aGUIPos = input_0.getOffsetPosition(yOffset),
    aColorMode = sbsenum.ColorModeEnum.COLOR,
    aAttributes = {sbsenum.AttributesEnum.Label: 'Input1'})

  # noise
  noise_node = sbsGraph.createCompInstanceNodeFromPath(aSBSDocument = sbsDoc,
    aPath = 'sbs://crystal_1.sbs/crystal_1',
    aGUIPos = input_1.getOffsetPosition(yOffset))

  # blend
  blend_node = sbsGraph.createCompFilterNode(aFilter = sbsenum.FilterEnum.BLEND,
    aGUIPos = input_1.getOffsetPosition(xOffset),
    aParameters = {sbsenum.CompNodeParamEnum.BLENDING_MODE: sbsenum.BlendBlendingModeEnum.COPY,
    sbsenum.CompNodeParamEnum.OPACITY: 0.8},
    aInheritance = {sbsenum.CompNodeParamEnum.OUTPUT_SIZE: sbsenum.ParamInheritanceEnum.PARENT})

  # output nodes
  output_node = sbsGraph.createOutputNode(aIdentifier = 'BlendResult',
    aGUIPos = blend_node.getOffsetPosition(xOffset),
    aOutputFormat = sbsenum.TextureFormatEnum.DEFAULT_FORMAT,
    aAttributes = {sbsenum.AttributesEnum.Description: 'noise masked blending result.'})

  # connection
  sbsGraph.connectNodes(aLeftNode = input_0, aRightNode = blend_node,
    aRightNodeInput = sbsenum.InputEnum.SOURCE)
  sbsGraph.connectNodes(aLeftNode = input_1, aRightNode = blend_node,
    aRightNodeInput = sbsenum.InputEnum.DESTINATION)
  sbsGraph.connectNodes(aLeftNode = noise_node, aRightNode = blend_node,
    aRightNodeInput = sbsenum.InputEnum.OPACITY)
  sbsGraph.connectNodes(aLeftNode = blend_node, aRightNode = output_node)

  sbsDoc.writeDoc()

  return True

if __name__ == "__main__":
  destFileAbsPath = python_helpers.getAbsPathFromModule(sys.modules[__name__], 'sample/TestMaterial.sbs')
  CreateHelloWorld(destFileAbsPath)

まずpysbsからコンテキストを取得し、これを元にして.sbsファイルを生成します。これはドキュメントと呼ばれます。
ドキュメントには複数のグラフを作成することができます。これはそのままSubstanceのグラフです。
今回は TestMaterial.sbs というドキュメントに TestMaterial というグラフを作成しました。

作成したグラフ内に各ノードを作成していきます。
まずは入力ノードを2つ作成します。createInputNode()関数を用いています。
なお、ノードの位置は直接指定することもできれば、あるノードからの相対位置も指定できます。

次に適当なノイズノードを持ってきます。
createCompInstanceNodeFromPath() 関数を使用することで、特定のパスにある.sbsから特定のノードを取得できます。
SDの標準ノードについては "sbs://" から始まるパスでOKのようです。

次はブレンドノード。
組み込みノードについては createCompFilterNode() 関数で生成できますが、一部のノード(入力や出力など)は特定の関数があります。

ノードの最後は出力ノードです。Descriptionにコメントを残すことも可能です。

コメントと言えば、フレームも作成できるみたいですね。

配置したノードはそれだけでは置かれているだけなので、connectNodes() 命令で接続を行います。
左と右のノードと、それぞれどこのピンに接続するかを指定しますが、ピンが1つだけの場合は省略可能です。
なお、存在しないピンに入出力しようとすると例外を発生させます。入出力のどちらが悪いのかは教えてくれません。うぼぁぁ

最後にドキュメントを writeDoc() すれば.sbsが出力されます。
出力された.sbsがこちらです。

f:id:monsho:20171103125009p:plain

ちゃんと接続されてますね。
まあ、正直な話、これだけコード書くよりノード接続した方が速いです。
なので、こういうことをしたいのであればPythonを使う理由はほとんどありません。
しかし、ノード接続では面倒なものもありまして…
次回はその辺の、ノード接続では面倒なものを簡単に作成する方法について書きます。

USDを使ってみた話

CGアニメでおなじみのPixer社がオープンソースで公開している汎用的なシーン記述フォーマット「Universal Scene Description (USD)」について、使ってみたので感想や引っかかったこと、メリット・デメリットなんかをつらつらと書いていきます。

USDについては元Pixer社の手島さんが今年のCEDECで基本的な部分を講演されています。

ピクサー USD 入門 新たなコンテンツパイプラインを構築する

大変わかりやすく、USDの利点もうまく語られていますので、興味がある方はご一読ください。

GitHubソースコードが公開されていますので、こちらをビルドすれば誰でも使用できるようになります。

GitHub - PixarAnimationStudios/USD: Universal Scene Description

ライセンスは改変Apache2.0ライセンスとなっているようです。
Apache2.0ライセンスは改変した場所を明示する必要があったはずなので、ゲームのランタイムで使用するにはちょっと不向きかもしれません。
まあ、ゲーム開発の場合は社内ツールとかで使うことになると思いますのであまり気にしなくても大丈夫だとは思います。

Windowsでのビルドは結構大変ですが、最近はかなり難易度が下がりました。
詳しくはこちらの記事をお読みください。

Pixar USD の Windows ビルド方法(2017/9 版) - Qiita

さて、ここまでが前提。
ここからは、MODO用の簡易メッシュエクスポータとそこから独自メッシュ形式へのコンバータを作成した際にわかったことなどをつらつらと書いていきます。
なお、私の書いたコードは以下になります。

GitHub - Monsho/ModoUsd: USD Exporter for MODO10

GitHub - Monsho/D3D12Samples

後者のUSDtoMeshは前者のエクスポータで出力したUSDファイルのみで確認していますので、他のどんなUSDファイルでも動作するかは保証しかねます。

デバッグビルドがうまくいかない

結局解決してないのがこの問題。
前述のWindowsビルド方法はRelease版のビルドはできるのですが、この方法ではデバッグ版のビルドはできません。デバッグ版のライブラリが生成されないわけです。

ライブラリはRelease版を使うとして、これをリンクするプロジェクトがデバッグ版だとどうなるかというと、単純にビルドが通りません。
原因はインクルードするヘッダファイルに_Debugなどの定義を用いて分岐しているので、USDライブラリはRelease、アプリはDebugという方法がどうもうまくいかないのではないかと思われます。

なお、無理やりDebugでビルドを通したらUSD使用時にクラッシュしたので、やっぱりちゃんとDebugビルドを作成しなければならないようです。
VSプロジェクト作成とかちゃんとやるべきだろうとは思いますが、とりあえず使ってみる分には自信のプロジェクトのDebug設定をRelease設定からコピー、最適化だけO0にするなどで対処するのが一番の近道じゃないかと思います。

サンプルはあまり多くない

まだ使っている人が少ないためだと思いますが、やはりサンプルが少ないです。
GitHubで公開されているソースコードにはいくつかのサンプルがtutorrialとして置いてあります。
extras/usd 内にいつくかあるので、まずはこれでUSDのファイルフォーマットやHello Worldなんかを学ぶとよいでしょう。

ただしこれらはPythonコードがほとんどで、C++コードはありません。
C++での使用方法も基本的には同じですが、やはりサンプルは欲しいところです。

私が参考にしたのはUnreal Engine 4のUSDインポータ、およびMayaのUSDプラグインです。
UE4の該当ソースコードは Engine/Plugins/Editor/USDImporter にあります。
また、MayaプラグインソースコードはUSDの third_party/maya 内にあります。
インポート/エクスポートプラグインや自前フォーマットへのコンバータを作成するのに十分なサンプルだと思います。
もっと複雑なことをやろうと思うとサンプルが足りなくなるのではないかとは思いますが、ゲーム開発現場だけなら何とかなるのではないでしょうか?

USDViewがとにかく便利

USDエクスポータを作成する際に非常に役に立ったのがUSDViewです。
USDをインストールすると標準でついてくるViewerで、メッシュデータの表示も可能です。
アニメーションは…どうなんでしょう?サンプルが見当たらなくて試せてません。USDViewのインターフェースを見る限りはアニメーションも可能だと思います。

エクスポートしてポリゴン等が正常に出力されているか、各種データが正しいかを比較的簡単に確認できるViewerの存在は大変便利です。
そもそも、これがないと正常に出力されているか確認する手段がほとんどないので頼らざるを得ないという部分もあります。

エクスポータを書く際には必ずUSDViewで確認する、という手順は必要だと思います。

エラーがわかりにくい

何らかの処理を行った場合にそれがうまくいってるのかいってないのかがわかりにくいと感じました。
この辺は設計の文化の違いもあると思いますので一概に悪いというわけではないのですが、慣れないと感じたのは事実です。

まず迷ったのはパスです。
USDでは各種ノードをSdfPathというスラッシュ区切りのパスで指定します。ディレクトリ構造みたいな形ですね。
形式が形式なだけに、命名規則もほぼディレクトリと似た感じです。
試した結果、数字始まりはダメ、空白やいくつかの記号もアウトです。
NGな文字列で生成されたSdfPathでは正常なノードは生成されず、それにアクセスするとアクセス違反でクラッシュします。

また、一部のデータは与えられるデータの形式が決まっていて、それ以外のデータを提供しても何も起こりません。
クラッシュしないだけマシですが、エクスポートしてみたら何も出力されてない、という状況になります。

VSのデバッグ機能でUSDのパラメータは現段階ではまともに見ることはできないので、エラーのわかりにくさとデバッグしにくさで軽く扱うには苦労すると思います。

全体的な感想

USDは思想については大変共感できるところで、Autodesk主導でいろいろ困ったちゃんなFBXの代わりになる可能性は十分にあると思います。
しかし、まだ実践に使うには不安が残る感じです。
ソースコードは公開されているので何かあったらソースを読むという手段はあるものの、なかなか大きなものなので読むとなるとなかなか大変です。

個人的にはこのまま使っていこうとは思っていますが、将来的に見ても仕事で使うかは今のところ不明です。

余裕ができたらMODOからスケルトン情報やアニメーション情報も出力できるようにしたいとは思いますが、いつになるかは不明ですね。

Substance Designer 2017.2 の新ノード その他

引っ越し一発目は[Flood Fill]ノード以外の新ノードを紹介。

・Cube 3D

名前の通り3次元のキューブ形状を2D画像として出力できるノードです。

f:id:monsho:20171022143909p:plain

Yaw, Pitchの回転、全体のスケール、各軸のサイズを指定できます。
Roll回転はありません。
軸はZ-upとなっているので、高さを変更したい場合は[Size]→[Z]を変更しましょう。

3DのDCCツールではないため、これによって3次元形状をこねこねできるわけではないですが、新しい特殊な[Shape]ノードとしてパターン作成に使用できます。

・Shape Mapper

入力された形状を円状に配置するノードです。
[Splatter Circular]ノードと違うのは[Radius]パラメータを小さくしていった時の挙動です。

[Splatter Circular]ノードでは普通に円状に配置されるだけなのに対して、[Shape Mapper]では画像の中心に近い方向のスケールが小さくなり、画像中心に吸い寄せられるような形になります。
実装を見てみたのですが読み解けていません。せめてプログラムコードなら読み解ける可能性もあるんですが…

使い道としてはやはりパターン形状の作成でしょう。特にレリーフ的なものの表現としては色々使い道があるのではないかと思います。

・作例 その1

[Cube 3D]と[Shape Mapper]を使って花のレリーフのようなものを作ってみました。

f:id:monsho:20171022150321p:plain

[Cube 3D]を左右対称にして形状を作成し、これを[Shape Mapper]で配置しています。
実際の花びらというよりは花びらを模したレリーフという感じになります。
実際の花びらっぽい形状も作れるとは思いますが、レリーフとかの堅い感じの方が向いてる印象があります。

f:id:monsho:20171022151317p:plain

・Swirl / Swirl Grayscale

[Transform 2D]ノードのようなUIを使って特定位置に渦巻きを作成できるノードです。

f:id:monsho:20171022152105p:plain

綺麗に渦巻きが作れますが、何に使うの?と言われるとなかなか困りますね。
ウルトラマンのタイトル演出とかですかね?

Vector Morph / Vector Morph Grayscale

[Warp]ノードに似ていますが、歪ませるためのノイズはノーマルマップを用います。
この仕組みはFlow Mapとも呼ばれていて、水の流れなどをノーマルの方向で指定するための技術です。
これまでのSDでも[Pixel Processor]を利用することで実現できましたが、より簡単になったと考えていいでしょう。

f:id:monsho:20171022153708p:plain

・作例 その2

ゲーム制作で使用されるFlow Mapは同じマップから生成される互い違いの歪んだテクスチャをブレンドして作成します。
SDには$timeという時間を指定するFloat値が存在しており、関数内で使用することが可能です。
ただし、Substance Playerでなければこの値を変化させることができないというイマイチな機能ですが、Playerを使うと連番のテクスチャも出力できます。
エフェクトでこのような流れを作った連番テクスチャを作成したい場合には$timeと[Vector Morph]を使って望みのテクスチャを作成できます。

f:id:monsho:20171022162926p:plain

関数は2つの[Vector Morph]の[amount]パラメータと、[Blend]ノードの[Opacity]ノードで実装しています。

[Vector Morph]上側

f:id:monsho:20171022163053p:plain

[Vector Morph]下側

f:id:monsho:20171022163132p:plain

[Blend]

f:id:monsho:20171022163155p:plain

Substance Playerで時間を変化させればうまくいく…はず(試してない)

・その他

[Histgram Select]ノードはある位置からある範囲までのヒストグラムを0~1の値に伸張し、それ以外を0にするノードです。
使い道としては高さブレンドのアルファ値として使用できます。
[Position]パラメータで指定したヒストグラムが1.0、[Range]パラメータで指定した範囲が[Position]の両端に展開される感じのようです。

[Arc Pavement]ノードはアーチ形状のタイルを作成するノードです。
使い方が限られてくる感はありますが、ヨーロッパの石畳とか作る上では便利かもしれません。

あと、[Splaetter Circular]ノードが刷新しています。
以前のものと比べるとかなり使いやすくなってる感じです。

 今回は以上です。

Substance Designer 2017.2 の新ノード Flood Fill

最近、ブログを書くのが面倒でほとんどTwitterで済ませてしまっていました。

なんとかブログを書こうという気力がほんのちょっとだけ湧いたので頑張って書いています。

いやまあ、記事を書くこと自体より、画像のコピペができないとかそういうのが面倒ってのが大きいです。

そのうち引っ越すかもしれませぬ…

今回はSubstance Designer 2017.2 が登場したので、そこで追加されているFlood Fillノードについてまとめました。

・Flood Fillとは?

Flood Fillアルゴリズムというものがあります。

これは画像処理アルゴリズムの1つで、画像内の特定アイランドに色を充填するためのアルゴリズムです。

簡単に言ってしまえばPhotoshopのペンキ缶ですね。

SDの[Flood Fill]ノードはこのアルゴリズムを用いてアイランドごとに情報を持たせているものと思われます。

・何ができる?

白黒で何らかのパターンを作るのはSDではよく行われますが、それをそのままハイトマップやノーマルマップにしてしまうとあまりにも整列しすぎていて不自然です。

だからと言って適当なノイズを与えるだけだとそれはそれで不自然だったりします。

出来ればアイランドごとにIDを持たせるような印象で情報を割り振り、それを元にして他のマップを生成したいという場面があります。

[Tile Generator]ノードなどはその手の情報をランダムに生成してくれる機能がありますが、[Cells 3]のようなノイズノードではアイランドごとに情報をランダムに変更する機能はありません。

[Flood Fill]ノードとそれに続くノードを用いることでこのような情報をあとから生成することができるようになった、というのが今回のアップデートの大きなところです。

・使い方

まず、[Flood Fill]ノードに白黒のパターン画像を接続します。

すると、なんだか不可思議な画像が出来上がります。

この画像はこのままでは基本的に使えませんが、ここには各アイランドに関する情報が保存されているようです。

どのような情報が保存されているかは不明ですが、アイランドのバウンディングボックスのサイズなんかは入っているようです。

この[Flood Fill]ノードの出力を[Flood Fill to ~]ノードに繋ぐと、その繋いだノードに合わせた情報を取得することができます。

2017-10-21_18h12_39.png

[Flood Fill]ノードに続くノードは5種類あります。

[Flood Fill to BBox Size]は各アイランドのバウンディングボックスサイズをアイランドに割り当てます。アイランドの大きさを求めることができるわけです。

[Flood Fill to Gradient]は各アイランドにグラデーションをかけることができます。

グラデーションの方向とランダム性を指定することができるため、かなり使い勝手がいいのではないでしょうか?

[Flood Fill to Position]は各アイランドの中心座標を色として割り当てます。

[Flood Fill to Random Color/Grayscale]は各アイランドにランダムにカラー/グレースケールを割り当てます。

これらのノードは[Flood Fill]ノードから繋げなければほとんど意味がないノードです。

かならず[Flood Fill]と一緒に使いましょう。

・作例

以前のSubstance勉強会の時に作った石畳は石の形状を作成するのにちょっと面倒な方法を採用していました。

2017-10-21_22h19_18.png

[Histgram Shift]を用いて無理やりノーマルマップを作り、そこからハイトマップに戻す方法ですね。

勉強会ではこのようなハイトマップを2種類作り、ブレンドしていました。

この方法でも悪くはないのですが、胡散臭い形状ができてしまうこともあって調整が面倒です。

[Flood Fill]を使ってよりスマートで安定的な形状を作る方法がAllegorithmicの公式動画にあります。

2017-10-21_22h38_11.png

[Flood Fill to Gradient]ノードを用いて各アイランドに傾斜をつけます。

この傾斜をそれぞれ別方向で3つ作り、すべてをMinブレンドします。

するとこのようなノーマルマップが生成できます。

2017-10-21_22h41_12.png

安定的な星型の形状ができました。

この状態だとあまりにも綺麗すぎるので、

・[Flood Fill to Gradient]の[Multiply by Bounding Box Size]パラメータを調整して星の中心をアイランドの重心付近から移動させる

・[Flood Fill to Random Grayscale]を使ってマスクを作り、星の足の数をアイランドごとに配置する

などの方法でよりランダム性を出すことができるでしょう。

・不具合?

不具合なのかアルゴリズム的な仕様なのかは不明ですが、[Flood Fill]を使用した場合に一部のアイランドのエッジ部分にノイズが乗ることがあります。

2017-10-21_22h49_29.png

[Flood Fill]の入力にゴミが残っているというわけでもなく、角度的な問題という感じもしませんが、何らかの理由でこのようなノイズが出てしまいます。

形状を変えたりすれば出なくなったりするのですが、出てしまうのがそもそも困るわけで。

対処の方法としては、[Flood Fill]へ入力するノードに[Blur HQ Grayscale]で少しだけブラーをかけてやると出なくなったりします。

ブラーの強度は0.2くらいなら形状に影響も与えず、ノイズ問題も解消できるようです。

ただし、どんな形状に対してもノイズ問題を解決できるかは不明なので注意してください。

以上、[Flood Fill]ノードの紹介でした。