Substance Automation Toolkitによる簡単自動化

先日、Substanceゆるゆる会にて某氏から聞かれた話。
Substance Designerでテンプレート的なグラフを作成し、特定フォルダ内のすべてのテクスチャに対してそのグラフを適用してテクスチャを生成したいんだけど、できませんか?と。

で、できらぁ!

ということで、Substance Automation Toolkitで上のようなことをやる方法について書いてみます。
今回のサンプルには以下の環境が必要です。

さて、既存のテクスチャに何らかの処理を行いたいという話は割とよくある話だと思いますが、Substanceらしいこんな話をでっち上げてみましょう。

あなたはあるCG映像会社の社員です。
ある日、今やるべきことは流行りのリアルタイムレンダリングだ!Unreal Engine 4で昔作ったムービーをリアルタイムレンダリングするぞ!と社長に言われました。

そこでまず昔のデータをUE4に乗っけてみたところ、ノーマルマップもない状態だしPBR対応もしてないしでモデルだけだしても結果はよくありません。
とはいえ期間もないので全部作り直すのは不可能。多くのリソース、特に背景は昔のものを手続き的にブラッシュアップするだけにとどめたい。
そこでSubstance Designerを使って昔のテクスチャを手続き的に処理、ノーマルやラフネスを出力すればなんとかなりそうだというところまでは検証できました。

しかしリソースは大量で、すべてのテクスチャに対してSDでグラフ作成→テクスチャ出力なんてやってられない。
どうにか自動化出来ないだろうか?

はい、身につまされた人はいませんね?
え、いますか?
大変ですね。

すでにこんな経験をしている人ならすでに自動化してるとは思いますが、今後こういう事態に陥る人向けにこの記事を書きます。

まずはSDでテンプレートとなるグラフを作成します。
これにはまず適当なテクスチャ相手に問題なく動作するグラフを作成してください。
作成できたら入力しているテクスチャを入力ノードに変換します。
最終的に作成されるグラフはこんなものになるはずです。

f:id:monsho:20190307161025p:plain
テンプレートグラフ
今回はサンプルとしてB2M LiteでDiffuseテクスチャから各マップを出力するだけのものを作成しています。
もちろんこれは一例に過ぎないので、自分たちに必要な形でテンプレートグラフを作成してください。

次にSubstance Automation Toolkitですが、これを利用する方法は2種類あります。
1つはBatchtoolsを使用する方法で、インストールフォルダ内にコンソールアプリケーションがいくつか存在していますのでこれを使います。
直接使用できるので、ぶっちゃけバッチファイルを書くだけでも使えます。

もう1つはPythonを使う方法です。
といっても、実際にはPythonを通してBatchtoolsを使用するだけなので実はやってることは同じだったりします。
今回はこちらのPythonを通してやる方法と採用しています。

まず、Substance Automation ToolkitのPython APIをインストールします。
(インストールフォルダ)/Python API/Pysbs-2018.3.0_install.bat を叩いてPython APIをインストールしてください。
Pythonがインストールされてパスが通っていれば正常にインストールされるはずです。
MacLinuxについてはよくわからないですが、pip使えれば大丈夫じゃないかと。

次にテキストエディタでコードを書きます。
今回のコードは全文をここに書いておきますが、今後Substance系のコードが増えたらGitHubリポジトリ作るかもです。

import os
import sys
import subprocess
import pysbs
from pysbs import batchtools
from pysbs import context

if __name__ == "__main__":
    aContext = context.Context()
    template_path = 'MyDocument/Allegorithmic/Substance Automation Toolkit/specialize_test/B2MTest.sbs'

    # generate specialized sbs.
    output_path = 'MyDocument/Allegorithmic/Substance Automation Toolkit/specialize_test/'
    output_name = 'spec_test'
    base_color_image = 'MyDocument/Allegorithmic/Substance Automation Toolkit/specialize_test/Bricks_Test_basecolor.tga'
    metallic_image = 'MyDocument/Allegorithmic/Substance Automation Toolkit/specialize_test/Bricks_Test_metallic.tga'
    base_color_image_connect = 'input_diffuse@path@' + base_color_image + '@format@JPEG'
    metallic_image_connect = 'input_metallic@path@' + metallic_image + '@format@JPEG'
    proc = batchtools.sbsmutator_edit(
        input=template_path,
        presets_path=aContext.getDefaultPackagePath(),
        output_name=output_name,
        output_path=output_path,
        connect_image=(base_color_image_connect, metallic_image_connect),
        stderr=subprocess.PIPE)
    (out, err) = proc.communicate()
    proc.wait()
    if err:
        print(err)
        sys.exit(1)

    # cook sbsar.
    proc = batchtools.sbscooker(
        inputs=os.path.join(output_path, output_name) + '.sbs',
        includes=aContext.getDefaultPackagePath(),
        size_limit=13,
        output_path=output_path)
    (out, err) = proc.communicate()
    proc.wait()
    if err:
        print(err)
        sys.exit(1)

    # render textures.
    output_size = 11
    proc = batchtools.sbsrender_render(
        inputs=os.path.join(output_path, output_name) + '.sbsar',
        output_name='{inputName}_{outputNodeName}',
        output_path=output_path,
        output_format='tga',
        set_value=('$outputsize@%s,%s' % (output_size,output_size)))
    (out, err) = proc.communicate()
    proc.wait()
    if err:
        print(err)
        sys.exit(1)

フォルダ名やファイル名は適当ですので、必要に応じて変換してください。
また、今回のサンプルは1つの特殊化しかしていません。フォルダ内のすべてのテクスチャ、という感じにする場合は別途ファイル列挙などが必要になります。

簡単に解説していきます。
まず、インストールしたSubstancePython APIをインポートします。

import os
import sys
import subprocess
import pysbs
from pysbs import batchtools
from pysbs import context

今回はbatchtoolsとcontextだけ利用します。

その後はSubstanceのコンテキストを取得します。
コンテキストはSubstancePython APIを使用する上で基本となるものですが、今回はライブラリフォルダを取得するためだけに使用します。

if __name__ == "__main__":
    aContext = context.Context()

最初に実行するツールはsbsmutatorというツールです。
このツールは.sbsファイルの情報を取得したり、パラメータ等を変更して特殊化したりするためのツールです。
このツールを使うことで、テンプレートグラフの入力イメージを変更した特殊なグラフを作成し、.sbsファイルとして保存します。
そのためには batchtools.sbsmutator_edit() 命令を使用します。
なお、batchtools以下の命令はほぼ全てsubprocess.Popen命令の戻り値を返します。

    proc = batchtools.sbsmutator_edit(
        input=template_path,
        presets_path=aContext.getDefaultPackagePath(),
        output_name=output_name,
        output_path=output_path,
        connect_image=(base_color_image_connect, metallic_image_connect),
        stderr=subprocess.PIPE)
    (out, err) = proc.communicate()
    proc.wait()

コンソールアプリケーションであるsbsmutatorへの引数は、通常 "--" で始まるオプションを利用します。
例えば、入力イメージとしてファイルを指定する場合、"--connect-image" というオプションを利用します。
しかし、これはPythonの変数としては使用できない文字列ですので、上記の例の場合は "connect_image" を指定することになります。
先頭の "--" は削除、途中にある "-" は "_" に変更します。

また、複数の入力を指定する場合もあります。
"--connect-image" はオプション1つにおいて1つの入力を指定することになりますので、コンソールコマンドとしては以下のように指定することになります。

sbsmutator.exe --connect-image "input0@path@basecolor.png" --connect-image "input1@path@metallic.png"

Pythonの可変個引数で辞書を用いる場合、同じ引数名を複数指定はできませんので、1つの引数に対してタプルで複数指定すればOKです。

この命令で特殊化された.sbsファイルが出力されますので、今度はこれを.sbsarファイルに変換します。
このためにはやはりBatchtoolsのsbscookerを使用します。

    proc = batchtools.sbscooker(
        inputs=os.path.join(output_path, output_name) + '.sbs',
        includes=aContext.getDefaultPackagePath(),
        size_limit=13,
        output_path=output_path)

指定したパスに同名の.sbsarファイルが出力されます。
ちなみに、自分の環境ではちょっとした警告が出ていましたが、まあ特に問題はないんじゃないかなと思います。

最後に.sbsarファイルからテクスチャを出力します。
これにはsbsrendererを利用します。

    output_size = 11
    proc = batchtools.sbsrender_render(
        inputs=os.path.join(output_path, output_name) + '.sbsar',
        output_name='{inputName}_{outputNodeName}',
        output_path=output_path,
        output_format='tga',
        set_value=('$outputsize@%s,%s' % (output_size,output_size)))
    (out, err) = proc.communicate()
    proc.wait()

"--output-name" オプションにはSDで指定できるグラフ名、出力ノード名などが使用できます。
今回のサンプルでは使用する.sbsarファイルのファイル名と出力ノード名を利用していますが、もちろん固定の名前も可能です。
ただ、複数のイメージを出力する場合は出力ノード名は使うようにしましょう。名前がかぶります。

Substanceに渡す各種サイズですが、これは2のn乗のnの値を指定するようにしてください。
サンプルで使っている 11 なら2048になります。
この値に 2048 を直接指定したらひどい目にあいました。
画像ビューアで開けないサイズのテクスチャが出来たのですが、むしろレンダリングしてくれるんだなぁと感心してしまいました。

以上で一連の流れは完了です。
今回提示したソースコードはあくまでの一例でしかありませんし、実際の業務ではファイル名やフォルダ名を指定したり、列挙したファイルに対して処理をしたり、出力フォルダを作成したりといった処理が必要になります。
そのあたりも考慮に入れて参考にしていただけたらと思います。

Batchtoolsを使うと他にも.fbxからジオメトリ関連のマップ(ノーマルやAO、IDマップなど)をベイクし、IDマップに従って特定のマテリアルを割り当てるとかも可能です。
この手の自動化はうまくやれればかなりの効率化が可能ですので、是非いろいろ試してみてください。
そして、うちはこういう使い方したよ!みたいな情報があったらこっそり教えてくださいw