NieR Re[in]carnationにおける3Dアセット管理の話

皆さまはじめまして、
3Dアニメーター出身のテクニカルアーティストやっております堀井です。

本作ではデザイナー用Maya/Unity内エディタ拡張ツール開発やプログラマと連携・相談役やアセット仕様決め、アセットにまつわるワークフローやパイプラインの効率化・最適化や本業だったアニメーションに関わるリギング関連を担当しています。

今回は本作で行った3Dアセット管理・パイプライン構築と、
ちょっとしたTA視点ならではの小話を綴らせていただきます。

※本記事内には情報公開できない情報が含まれるため使用画像を加工しております。


目次

  1. 作業は極力はMayaだけで
  2. バリデーションはUnityで
  3. Unity内のアセットブラウザ
  4. mGearを使う

1. 作業は極力Mayaだけで

今回アセット管理ではPerforce4を利用しているのですが、
チェックアウトやサブミットの度にいちいちP4V(P4操作のためのGUIアプリケーション)を起動して状態を切り替える」などというちょっとした一手間がスピードが求められる本作においてはかなり致命的でした。

なぜ切り替えるかというとすでにP4管理のファイルはチェックアウト(編集状態に切り替え)しないと保存などができないからです。

そのためUnity管理側のワークスペースファイルなどを先にチェックアウトしておかないとエクスポーターがエラーになるなど障害が多く発生していました。

そこに同僚から「P4コマンドが便利」と聞きつけたので、独自に開発していたMaya内のブラウザ機能(現在は通称APB Browser)にP4操作機能を搭載しました。

APB_BrowserのP4操作の様子
ファイルオープンなどファイル操作全般を行うことができる

Mayaで使われているPyQt/PySideにはWindowsのエクスプローラー機能のようなものをもつQFileSystemModelというものがあり、それを継承したクラス内でデコレーションロールという装飾実装を行うことで目的にあわせてモデルに装飾を行うことが可能でした。

これによりP4の状態管理もMayaで行えるようになりました。

def data(self, index, role):
    col = index.column()
/-----------------< 省略 >----------------------/
    role == Qt.DecorationRole:
        # 1列目がファイル名
        if col == 0:
            fileIcon= QFileSystemModel.data(self, index, role)
            painter = QPainter(pixmap)
                painter.drawPixmap(
                    QRect(3, 1, 16, 16),
                    fileIcon.pixmap(QSize(16, 16))
                    )
            p4status = self.workSpace_PathDict[path] if path in self.workSpace_PathDict else None
            if p4status:
                if 'headRev' in p4status and 'haveRev' in p4status:
                    mapOverRay = QPixmap(":/apb_tools/icons/P4_mapped.png")
                    painter.drawPixmap(
                        QRect(24, 10, 6, 6),
                        mapOverRay
                        )

ファイルアイコンを別のものにできたり、TortoiseSVNのアイコンオーバーレイ機能のようにアイコンに付属マークをつけることができます。

ローカルが古い時のオーバーレイ、そして
他人がチェックアウトしている場合は青色になるなどP4Vと見た目は同じにしている

P4操作機能をモジュール化して、エクスポーター等でも利用することでMayaからチェンジリストを個々に作成したりが可能になりました。

モーション、モデル出力時には関連するアセットがまとまったチェンジリストを自動作成したりできるので便利です。
かなりの手間が省けました。


2. バリデーションはUnityで

本作では、Maya側のアセットバリデーションは汎用的なものにとどめていました。

仕様的にも複雑な設計になっていないためよくあるバグチェックに留め、Maya内では汎用的に使う自身の関数をすべて検証しエラーがあれば返す基底クラスを作成し、キャラ、背景、モーション、3Dスパインなどのアセット毎の検証内容を関数に追加したクラスを増やしていきました。

#Maya側のバリデーター基底クラス
class Validator(object):
    ErrorMessagelist = []

    def __init__(self):
        self.ErrorMessagelist = []

        for method in [ method[0] for method in inspect.getmembers(self, inspect.ismethod) ]:
            if method == '__init__' or method == 'get_Error':
                continue
            MethodObj = getattr(self, method)
            Condition = MethodObj()
            if Condition:
                self.ErrorMessagelist.append(Condition)

    def get_Error(self):
        if len(self.ErrorMessagelist) > 0:
            return self.ErrorMessagelist
        return None
Maya上でのバリデーションウインドウ

それよりもUnityエディタ上での整形・設定が多く、Unityの知識がない方が使用することも考慮してバリデーションはUnity内で極力行うという形で進めました。

アセットブラウザという、3D周りのアセットを閲覧・設定編集するツールにバリデーターを搭載することでデザイナーの確認・設定作業時にバリデーションできるようになっています。

public class AssetsValidator
    {
        /// <summary>
        /// Unityバリデーション基底クラス
        /// </summary>
        public class Validator
        {
            protected List<ErrorObjectsInfo> m_errorInfoList = new List<ErrorObjectsInfo>();
            /// <summary>
            /// こちらにバリデーション内容を記載する
            /// バリデーションしたら結果をm_errorInfoListに格納
            /// </summary>
            public void RunMain(){}

            /// <summary>
            /// バリデーションが持つ、エラー情報を吐き出すジェネレータ
            /// エラー受け取り側はこちらを受け取る
            /// </summary>
            /// <returns></returns>
            public IEnumerable<ErrorObjectsInfo> getErrors() {
                if(m_errorInfoList.Count > 0)
                {
                    for (int i = 0; i < m_errorInfoList.Count; i++)
                    {
                        yield return m_errorInfoList[i];
                    }
                }
                yield break;
            }
        }
Unity上でのバリデーションウインドウ

Unity内ではMayaと同じように汎用的に検証結果を同じフォーマットで表示できるように共通フレームワークを作成しました。
そのためにエラー情報クラスにはデリゲートを設けて、各エラーに対する対応処理をエラー側から投げれるようにしています。

対処処理はRepairボタンから実行します。
Repairボタンはこのデリゲート関数を実行していて、エラー対象のすべて問題を一括で対応します。

public delegate void Repair();

似たウインドウを実現し、ツール間の操作性の乖離を防いでいます。
Unity側はエラー詳細に加え、リペアボタンで一括でエラーを解消することで本作の大量のアセットチェックから修正までを素早く行えます。


3. Unity内のアセットブラウザ

アセットの検索やアセットの設定ファイルの編集を即座に行えます

開発においてアセットを見るのはデザイナだけではありません。
企画もエンジニアも見ます。
アセットブラウザではそれら必要な情報を全てアセット毎に拾い、
閲覧・編集が容易になるように設計しています。

色々なシーンでキャラを確認するために、閲覧中のキャラを現在のシーンに素早くロードしたり、コリジョン設定の面倒を解決するための補助の仕組みを搭載しました。
それがアセットブラウザです。

アセットブラウザではアセットをカテゴリごとに分類し、アセットの命名規則からフォルダやファイルの一覧を取得し3Dパートごとに必要なファイルだけをフィルタリングしてブラウジングが可能になっています。

ジオメトリの大きさから自動的にコリジョンの半径と高さを計算する

キャラ挙動専門のエンジニアと協力してデータベースを利用したキャラセットアップを本作では利用しています。

そのデータベースをアセット作成時に同時で作成できると良い、
ということでツールを用意しました。

こういうことがあるから社内だからといって
適当な説明文を書いてはいけないと後悔

バリデーションやデータベース登録をGUIツールで提供しています。


4. mGearを使う

本作では弊社3Dアニメーターが2Dカットシーンのようなパートのモーションを作成することになりました。(ADVパートでご覧になれるはず!)

一見2Dに見えますがカメラだけパースをつぶしていて、本当は3Dだったりするこのパートを支えるツールとしてmGearを採用しています。

http://www.mgear-framework.com/jp/

採用理由は主に4つ

  1. 自身がリグシステムを一から構築する余裕がないこと
  2. mGearの標準モジュールのリグが社内アニメーターから好評であったこと
  3. mGearのステップ機能が優秀で拡張性が高いこと
  4. ゲームでも採用事例が増えていたこと

様々な形状のモデルを取り扱う本作では見た目を重視するため、
骨の追加に柔軟なリグの開発が求められます。

mGearは基本フローがガイド=>リグという、トランスフォーム位置を骨位置に左右されずに決められるため(mGearは生成したリグへのWeightを想定している)骨の命名規則にも一旦はしばられずにリグのテストが行えました。

またモジュール実装テストとしてリグの構築を途中でとめることができるなどデバッグ機能も搭載されているのも魅力の一つです。

オープンソースなだけでなく、ほぼPythonで構築されているためMaya+Pythonの中級者ならかなり解析しやすい内容になっています。

細かいところも独自にリグモジュールを追加してカスタムできるのも魅力ではないでしょうか。

またピッカー作成・編集機能もあるのでとても便利です。
これも弊社内アニメーターに人気で追加開発しがいがあります。

Pythonが使える、mGearのフローやモジュラーリギングが理解できるなら、
使ってみて損はない一品です。

まとめ

開発は面倒なことだらけです。
でもこの面倒を発生からいち早く解決できると余裕が生まれ、
それになにより良いものが作れる。

弊社のTAは限られた時間のなかでデザイナがより良いものをありったけ突き詰めて作れるように、
日々面倒ごとを解決しております。

お読みいただき、ありがとうございました。


関連記事一覧

  1. この記事へのコメントはありません。