Anima2Dでキャラクターアニメーションを作ろう!編

■ 共闘ことばRPG 「コトダマン」

こんにちは。
セガゲームス 開発統括部 アート&デザイン部 TAセクション 廣田です。


今春リリースの「共闘ことばRPG コトダマン」では全てのキャラクターアニメーション(700キャラ以上!)をAnima2D で作成しました。タイトルリリース前ですが、一足先にコトダマンのキャラクターを使って、Anima2D のセットアップからアニメーション作成を解説していきます。

さらに次回、Anima2Dでの制作をサポートするスクリプトについても書いていく予定です。

公式

好評配信中です!
kotodaman.sega.jp

Twitter

この記事で紹介しているキャラクターは、全てコトダマンのAnima2Dキャラクターです。
Twitterでは他のキャラクターもたくさん登場していますので、Anima2Dのサンプルとして是非ご覧ください!
(フォローもお願いします!)

twitter.com


■Anima2D とは

f:id:sgtech:20180326002518p:plain
2017 年から Unity テクノロジーが提供しているアセットで、無料で利用できます。
将来的に Unity への組み込みを予定しているようです。
これを利用することで、Unity 内で 2D ボーンアニメーションができるようになります。
3DCGツールを使っている方なら、板ポリゴンにテクスチャーを貼り、ボーンを入れて2D イラストを動かすという工程がUnity内でできるようになったと思ってもらうと分かりやすいでしょう。

ドキュメント

上記画像にある、DocumentationというリンクからPDFをダウンロードできます(英語)

f:id:sgtech:20180326002249g:plain:w256

■Anima2D のメリット

  1. FCurve エディタが使用できる(MayaやSoftimageで慣れたアニメーターの作業・修正スピードが格段に違う)
  2. Unity のスクリプティングによる拡張ができる(セットアップの自動化や機能追加・作業支援ツールの作成など)
  3. Unity 内で作業できるので、他ツールからのエクスポート/インポートの手間が無い。
  4. メッシュをある程度自動で切ってくれる
  5. 学習コストが安い(Unityを触ったことがある+3DCGツールでアニメーションを作成した事があればすぐ)
  6. ゲーム画面内で見ながら作成・修正できる。(Shuriken のエフェクトも同時に作成可能)
  7. Unityの公式アセットになった。

コトダマンでは700キャラ以上のアニメーションを作るにあたり、Spine, Live2Dも検討したのですが、上記理由でAnima2Dを採用しました(数字は選択理由順)。
簡潔に言うと「作業スピード+スクリプティングによるカスタマイズ」が選択理由です。

f:id:sgtech:20180326002126g:plain:w200

■Anima2Dのデメリット

この場合は違うツールを選択した方が良いです。

  • Unity 以外で開発を行う可能性がある(Cocos2D など)
  • フリーフォームデフォーメーション(頂点を動かしての細かいアニメーション)を行う

フリーフォームでフォーメーションをやるかどうかが一番の判断基準ですが、作り方によっては、コトダマンのように魅力的に動かせますよ!

f:id:sgtech:20180326002111g:plain:w200

■Anima2D を準備する

では、さっそく始めてみましょう。
まずは、アセットストアから「Anima2D」で検索してダウンロードします。
後述する Skinned Mesh Combiner(複数のキャラパーツを1ドローにまとめてくれるスクリプト)が入っているので、Examples も含めてインポートを行っておきましょう。
(最低限でも、Examples/Scriptsのみはインポートしてください)


f:id:sgtech:20180326002151p:plain

■全体の流れ

イラスト → パーツ分け → パーツ配置 → ボーン配置 → ウエイト付け → アニメーション作成 → 描画最適化
という流れで進みます。


f:id:sgtech:20180326000954p:plain:w1000
f:id:sgtech:20180326002142g:plain:w200

■イラストの準備(パーツ分け)

Anima2Dで動かすために、キャラクターのパーツ分けを行います。

f:id:sgtech:20180326001438p:plain

コツ

  • まずはキャラクターがどう動くのか、アニメーションを決定しましょう。ここをきちんと計画しておかないと、アニメーションを作り始めてから、パーツが分かれていなくて動かせない!などの問題が出てきます。
  • それでも、どう動かすかが決めきれない場合は、基本的に関節単位で分割します。
  • Photoshop上でCtrl+Tでパーツを回転させて、パーツの割り具合と動きを確認してみましょう。隙間が見える場合は塗り足すか、それをカバーするような動きに変更します。
  • 上腕・下腕・手、足などはパーツを分けておいた方が破綻せず、動きも大胆に作れるようになるので、分けておくのをオススメします。

Photoshopデータの作り方

  • パーツを切り分ける。1 パーツ 1 レイヤー。
  • 英数字でレイヤー名を付ける
  • パーツの書き出し。1パーツ1画像(.png)

後でこのルールを元に、Unity取り込みまでを自動化するスクリプトを作成します。

f:id:sgtech:20180326002331g:plain

■Unityへの取り込み

1枚のテクスチャーに収める

TexturePackerやSpriteUVなどを使って、1枚のテクスチャーにパーツを配置します。


f:id:sgtech:20180326000951p:plain:w128

マルチスプライトに設定

  • テクスチャーをUnityに読み込んだら、SpriteModeをMultipleに設定します。
  • 必要があれば、Sprite Editor ボタンを押して、スプライトを切り直します。


f:id:sgtech:20180326001435j:plain

Anima2D SpriteMesh に変換

  • テクスチャーを右クリック → Create → Anima2D → SpriteMesh でAnima2DのSpriteMeshを作成します。
  • テクスチャーファイルを選んで実行すると、まとめてSpriteMeshが生成されます
  • マルチスプライトの各要素(青い四角アイコン)を選んで、同じようにCreate → Anima2D → SpriteMeshとすれば、1個単位でSpriteMeshを生成されます。SpriteEditorで後からスプライトを追加した場合など、単品でSpriteMeshを作る事ができます。


f:id:sgtech:20180326002323p:plain
f:id:sgtech:20180326002319j:plain:w256
グリーンのオブジェクトがAnima2D のスプライトです。


f:id:sgtech:20180326002840g:plain

■Unity階層の準備

まず、メッシュとボーンを配置するための階層構造を準備しましょう。
GameObject -> Create Emptyで画像のような階層を組みます。

character → キャラクタートップ階層(全体に関するスクリプトはここに付けます)
/mesh → メッシュを置きます
/bone → ボーンを置きます


f:id:sgtech:20180326001422j:plain
f:id:sgtech:20180326001812g:plain:w256

■パーツ(SpriteMesh)を並べる

配置テンプレート(下敷き)の準備

キャラクターの1枚画像を下敷きとして用意し、これをガイドにパーツを配置していきます。

  • キャラの全身画像を用意し、Sprite(2D)に設定します。


f:id:sgtech:20180326002244j:plain

  • Hierarchy上にtemplate画像を置き、InspectorでColorのAlphaを半透明に設定し、配置ガイドにします。


f:id:sgtech:20180326002431p:plain

  • そして・・・頑張ってSpriteMeshを並べていきます!


f:id:sgtech:20180326002415p:plain

パーツの前後関係の設定(Order in Layer)

Order in Layerでパーツの前後関係を設定します。数字が大きい方が前面に描画されます。

  • 分かりやすいように、Hierarchy上でPhotoshopと同じ順に並べます。
  • Order in Layerを下から順に番号を振っていきます。


f:id:sgtech:20180326002426p:plain

この項目の工程は非常に面倒なので、後でスクリプトで自動化します!


f:id:sgtech:20180326002945g:plain:w200


■ボーンの配置と設定

配置

計画したアニメーションに合わせて、配置したパーツの上にボーンを並べていきます。


f:id:sgtech:20180326000958j:plain:w256

  • Hierarchy上で右クリックし、CreateObject→2D Object → Bone でボーンを出します。(またはAlt+Shift+B)


f:id:sgtech:20180326001419p:plain

  • ボーンの根本を掴むと移動、先の方を選ぶと回転ができます
  • Transform, Rotationツールを切り替える必要はありません。


f:id:sgtech:20180326002903g:plain

親子階層の設定

配置が終わったらHierarchy上で親子関係を組み立てます。


f:id:sgtech:20180326002420j:plain

boneの設定

Hierarchy上での親子関係とは別に、bone自体にChildという設定項目があります。
Anima2Dが計算する時に使うボーンの親子情報です。

  • IKで動かす場合は、必ず設定します。
  • 子階層に向かって、ボーンの向きと長さが自動で設定されるようになります。腕・脚など、関節部分は見た目が分かりやすくなります。

f:id:sgtech:20180326002628p:plain

Colorの項目でボーンに色を設定できます。
自分で分かりやすい色に設定しましょう。
f:id:sgtech:20180326002438p:plain

Color ボーンカラー。見た目の変更。
Child 子ボーン。IKを使うなら設定必須。
Length ボーンの長さ。Childが設定されていると自動計算されます。

コツ

  • 階層構造は最後に組みましょう。
  • 骨の配置時に階層構造も組みたくなるのですが、先にやってしまうと親の骨を動かした時に、子の位置もずれてしまうので、手戻りが発生します。


f:id:sgtech:20180326003427g:plain

■メッシュにボーンを割り当てる

メッシュを選んで、対応するボーンを設定します。

  • メッシュを選択
  • Bonesの右下の「+」を、割り当てたい骨の数だけ押します。
  • 出てきた空欄に、ドラッグ&ドロップでボーンを割り当てます
  • (まだメッシュにバインドしていないため警告が出ますが、今は大丈夫です)

f:id:sgtech:20180326002524p:plain

  • 階層をまとめて割り当てたい場合は、SetBonesにドラッグ&ドロップすれば、子階層まで一気に設定してくれます。

f:id:sgtech:20180326002617p:plain


f:id:sgtech:20180326003037g:plain

■メッシュ割り

メッシュはボーンに合わせて割ると綺麗に曲がりますので、ボーン配置後にメッシュ割りをします。

SpriteMesh Editor

Anima2D用のメッシュ割り&ウエイト付を行うエディタです。
Unity上部メニュー → Window → Anima2D → SpriteMeshEditor で表示します。

Sliceツールの利用

左上のSliceボタンを押すと、メッシュを自動で割ってくれます。
f:id:sgtech:20180326002620j:plain

Outline Detail 画像形状をどれだけ正しく追跡するか。値を上げると画像に沿ってメッシュが切られますが、頂点数が多くなります。
Alpha cutout メッシュを切る時のアルファの許容値。値を上げるほど画像の輪郭に近いメッシュが切られます。OutlineDetailの値が高くないとあまり効果はありません。
Tessellation メッシュの中を分割するか。画像の輪郭だけでなく、内部にも頂点が生成されます
Detect Holes ドーナツのような穴の空いた画像部分のメッシュをくり抜くか。

画像は自動で生成されたメッシュです。
手間を掛けたくない場合は、決め打ちパラメーターでもある程度の結果にはなりますが、Sliceのスライダーを動かし、自動で割ってから手で調整するのが楽でしょう。


f:id:sgtech:20180326003159j:plain

手動でのメッシュの調整

頂点の追加 Shift+クリック
頂点の削除 Del


f:id:sgtech:20180326003405g:plain

■ウエイトの設定

Bindボタンを押すと、メッシュに骨がバインドされ、自動的にウエイトが割り振られます。
ウインドウ下にWeightTool, Inspectorが出てきますので、これらのツールを使ってウエイトを調整していきます。

  • 頂点を選択(Ctrlを押しながらで複数選択可)
  • ウエイトを割り振りたい骨を選択
  • Weight Toolのスライダーでウエイトの割合を設定する


f:id:sgtech:20180326001940g:plain

Weight tool(SpriteMeshEditor:右下)

f:id:sgtech:20180326002516j:plain

Overlay メッシュ上にウエイトカラーを表示します
Pies 各頂点上にウエイトの割合を表示します
Smooth ウエイト分配をスムーズ化します。選択した頂点だけにも掛けられます
Auto 自動でウエイトを割り振ります。選択した頂点だけにも掛けられます

Inspector(SpriteMeshEditor:左下)

SpriteMeshEditorで選択したものの情報が表示されます。
画像は頂点を選択した場合です。
選択頂点に割り当てられている、ボーンとウエイトの確認・調整ができます。
f:id:sgtech:20180326002513j:plain

コツ

  • ウエイトを付けた後に、バインドしたまま、頂点の追加と削除ができます。
  • 後から足した頂点にはウエイトが割り振られていませんので(黒い点)、Autoボタンなどで忘れずにウエイト設定しましょう。
  • Unbindすると、設定したウエイトが初期化されてしまいます! 骨位置が違うと思ったら、早めにUnbindして骨位置を調整しましょう。ここはAnima2Dで改善して欲しい所です。

f:id:sgtech:20180326003412p:plain:w300

f:id:sgtech:20180326003347g:plain:w300

■リギング

IKの設定

  • 先端の骨を右クリック→2D Object→IK Limb(IK CCD2D) を選択して、IKコントローラーを出します。
  • グリーンの円がIKコントローラーです。


f:id:sgtech:20180326002522j:plain

IK Limb(2ボーンIK)

腕・脚など2本の骨を制御する時に使用します。
f:id:sgtech:20180326002626j:plain

Record IKコントローラーを動かした時に、ボーン側にキーを打ってくれます。実機上でIKの計算コストを掛けたくない時はONにしてアニメーションを作成します。
Target IKでコントロールされる骨。自動設定されます。
Restore Default Pose 計算を実行する前に初期ポーズを設定する。基本ON(現状ON以外の使い道が分かりません)
Flip 関節の曲がり方を反転。IK設定後に関節が逆に曲がってしまうならONにします。
Orient Child IKでコントロールされているボーンより、下階層にあるボーンの角度をどうするか。ON:IKコントローラーと同じ角度にする。OFF:階層の角度を維持する。

IK CCD2D(多関節IK)

尻尾などの長い関節の制御に使用します。
f:id:sgtech:20180326002623j:plain

Num Bones 多関節IKでコントロールするボーン数。自動設定されます。
Iterations 計算回数。大きいほど多関節ボーンがIKコントローラーを正しく追跡しますが、計算負荷が大きくなります。モバイルではデフォルトの10を基準とし、様子を見て値を下げましょう。
Damping 多関節ボーンの柔らかさ。1に近いほど固くなります。

Limb, CCD2Dの挙動の違い

IKによくある現象として、IKコントローラーとボーンが離れた時に、伸び切りが発生します。
伸び切った所から、再びIKコントローラーに接触すると、カクッとした動きになり非常に目立ちますが、
イラストを大胆に動かすと、どうしても伸び切り・カクつきが回避できない場合があります。
その場合は、2ボーンでもCCD2Dを使ってコントロールすると緩和されます。
Limbに比べて計算コストは高くなりますが、Iterationsの値(デフォルト10)を上げなければ、実機上でも問題ありません。
f:id:sgtech:20180326002756g:plain:w256

コントローラーの追加

ボーンに直接アニメーションを付けず、別階層でリグを組む時はコントローラーを利用します。

  • ボーンを右クリック → 2D Object → Controlでコントローラーを出します。
  • ボーンはコントローラーのTransformに追従するようになります。



f:id:sgtech:20180326003123g:plain

■ポーズマネージャー

定型ポーズの保存・読み込みができます。
ポーズライブラリや、同じ骨構造で違うキャラを作る場合など、色々活用できます。

Poseファイルの準備

  • Projectウインドウで右クリック → Create → Anima2D → Pose でポーズファイルを作成

コンポーネントのアタッチ

  • bone階層にPoseManagerコンポーネントをアタッチ。
  • 「+」ボタンを押してPoseファイルを設定
  • 必要なポーズ数分繰り返します。

f:id:sgtech:20180326000948p:plain

保存/読込

  • Save/Loadボタンで、ポーズの保存・読込ができます。

コツ

  • PoseManagerは、付いている階層以下のゲームオブジェクトのTransformを記録します。キャラクターの一番トップ階層につけてしまうと、メッシュの位置も記録されてしまい、違うキャラクター間でアニメーションを使いまわせなくなります。
  • アニメーションエディタをレコードにした状態で、PoseManagerのLoadボタンを押せば、ポーズがロードされて、自動的にキーが打たれます。モーションの開始/終了時に基本待機ポーズに戻す時などに使えます。
  • セットアップ中やアニメーション中に、デフォルトポーズに戻せるように、初期ポーズを登録しておくと良いでしょう。

f:id:sgtech:20180326003302g:plain

■アニメーション作成

Animation, Mecanimを準備

ここはUnity通常のフローなので、簡単に書きます。

  • Character階層を選択し、Animatorコンポーネントを付ける
  • Projectウインドウで右クリック→Create→Animator Controller。Animatorコンポーネントに設定。
  • Projectウインドウで右クリック→Create→Animation。
  • Animatorウインドウで右クリック → Create State → Empty。出てきたステートに、上のAnimationを設定。
  • Animationウインドウを開き(Ctrl+6)、ボーンを動かしてアニメーションを作成します。

パターンチェンジ(目パチ・口パク・着せ替え)

Anima2Dではパターンチェンジアニメーションを作成する事ができます。

  • メッシュを選択
  • Sprite Mesh Animation コンポーネントをアタッチ
  • 「+」を押して出てきた欄に、SpriteMeshを設定
  • Frameのスライダーを操作してパターンを切り替えます(上から順番に0, 1, 2・・・の値に対応しています)

f:id:sgtech:20180326003103g:plainf:id:sgtech:20180326000945j:plain

コツ

  • パターンチェンジするSpriteMeshの位置は、最初のメッシュ配置時にピッタリ同じ位置に合わせておきます。
  • アニメーションに追従する必要があるので、パターンチェンジメッシュも忘れずに骨をバインドしておきましょう。
  • 今回は目なので、パターンチェンジメッシュを全て頭の骨にバインドしています。これで頭の動きに合わせてついてきます。

IKによるアニメーション

IKコントローラーは、そのままアニメーションを付けて、実機再生可能です。

  • IK計算のコストを省くには、最後にアニメーションベイクする方法もありますが、全フレームにキーが打たれるのでアニメーションデータが大きくなります。(IKコントローラーを選択 → Unity上部メニュー → Window → Anima2D → Bake Animation
  • IKコントローラーのRecordボタンをONにすることで、IKコントローラーを動かした時に、骨側にアニメーションキーを打つ事ができます。(最後にIK Limb, IK CCD2DのActiveをOFFにするのを忘れずに。IK計算がされたままになります。)
  • IKのままアニメーション付けできるのがやはり楽ですが、実機での計算負荷を減らしたい場合は、Recordボタンの使用が落とし所だと思います。


f:id:sgtech:20180326003120p:plain


その他

2Dアニメーションを作成していますが、UnityなのでZ軸(奥行き)が存在します。
アニメーションで上手く使えば、面白い表現ができるかもしれません。

f:id:sgtech:20180326003249g:plain

■次回:Anima2Dを自動化しよう!スクリプトの作成

作業で面倒だった部分をスクリプトで自動化しましょう!

  • Photoshopからのエクスポート(各レイヤー毎の画像と、位置情報の出力)
  • 位置情報を元にAniam2Dメッシュを自動配置
  • Photoshopレイヤー順にOrderInLayerを設定
  • ボーンの配置
  • ゲームに出すための最適化

について書く予定です。


f:id:sgtech:20180326002657g:plain


 TAセクションではこのようにアーティストの制作に役立つ環境を提供できるよう力を注いでおります。やりたいことを遠回りせずに行える環境でお仕事したい方はぜひ下記の弊社グループ採用サイトをご確認ください。現在いろいろな職種でアーティストを多数募集しております!
採用情報 | セガ企業情報サイト


(C)SEGA

MayaからUnityへのUVアニメーションエクスポート

はじめに

こんにちは、セガ・インタラクティブ 第三研究開発本部 開発一部
ソフトウェアエンジニアのサカイと申します。
最近はアーケードゲームの開発をしています。

本日は、「MayaからUnityへのUVアニメーションエクスポート」
というテーマで書いていきます。作業記録のような書き方ですが、私の好みなので許してください。
いつもの作業の流れはこんな感じなのです。

まず、動機、計画について簡単にまとめた後、
本題のMayaからのエクスポートとUnityのインポートについて書きます。
最後に、まとめと今後の拡張案で締めます。

なるべく内容に間違いのないように努めますが、
必ず自身で調べて、確認するようお願いします。

概要

動機

現在、私のチームのアーティストはMayaでモデリングとアニメーションつけを行っています。
アニメーション作業の中にはUVアニメーションによるキャラクターの表情変化が含まれます。
しかし、Unityの標準機能ではUVアニメーションをMayaからインポートする方法がなさそうです。

Unity上で表情アニメーションだけ後付けすることは手間がかかりますし、不具合の元にもなります。
アーティストの要望は以下のような感じでしょうか。

  • Maya上でUVアニメーションもつけたい
  • Unityにインポートした後で手を加える必要がない

計画

少し調べたところでは、既存の拡張はなさそうです。位置等の別チャネルに入れる方法は手違いが生じそうです。
自作するしかないでしょう。下記2つの機能を使えば要望を満たせそうです。これらを使ってできない場合は、
別ファイルにUVアニメーションを出力 ⇒ インポート時に統合で実現は可能でしょう。プランBは常に考えておきます。

作業見積もりは、1日4~5時間作業できたとして、4日ぐらいでしょうか。調査が必要ですので長めに見積もっています。
後で不具合や修正要望が出たら片手間でも対応できるでしょう。

Mayaのエクスポート

”FBX Extensions Software Development Kit”というものが Autodeskさんから提供されています。
これを使うと、FBXのエクスポートのタイミングでFBXファイルの加工ができます。
Maya上のユーザデータを汚すこともなさそうです。

Unityのインポート

Unityには、FBXのロード後にユーザデータと共にフックする関数が用意されています。
AssetPostprocessor.OnPostprocessGameObjectWithUserProperties(Unityマニュアル)

適当なボックスを作成してテキスト形式のFBXで出力してみます。
以下のようにFBXファイルを修正すると、Unityでキーが”my_property”、値が”test”で取得できます。

Model: 1074780944, "Model::pCube1", "Mesh" {
    Version: 232
    Properties70:  {
        P: "RotationActive", "bool", "", "",1
        P: "InheritType", "enum", "", "",1
        P: "ScalingMax", "Vector3D", "Vector", "",0,0,0
        P: "DefaultAttributeIndex", "int", "Integer", "",0
        P: "currentUVSet", "KString", "", "U", "map1"
        P: "my_property", "KString", "", "U", "test"
    }
    Shading: T
    Culling: "CullingOff"
}

Mayaのエクスポートプラグイン

SDK

Autodesk FBXから"FBX Extensions Software Development Kit"をダウンロードします。
プラグインは C++ で書きます。詳細はドキュメントとサンプルで確認していただきたいですが、
FBXSDK_DLLEXPORT void MayaExt_ExportEnd(FbxScene* pFbxScene)で出力FBXを加工できそうです。
FbxSceneを作成した後に呼ばれる関数ということですね。

また、DCCツールのプラグイン開発ではツールのバージョンアップに対応する機会が多いため、
ビルド環境を構築する環境をCMakeで作成しておきます。

作成したDLLは以下のディレクトリにコピーします。2016 Extension 2で異なっているので注意します。

2016用: C:\Program Files\Autodesk\Maya2016\bin\plug-ins\FBX
2016.5用: C:\Program Files\Autodesk\Maya2016.5\plug-ins\fbx\plug-ins\FBX

UVアニメーションの検索

まずは、UVアニメーションはどこから取得できるか、Maya上で確認します。
アトリビュートエディタを見るとplace2dTextureにセットされているようです。
作り方によるでしょうが、上手く動かない場合が出たらその都度対応でよいと思います。
アトリビュートエディタ

ハイパーグラフ上では以下のようになっています。メッシュ ⇒ シェーダ ⇒ place2dTextureの順に辿れそうです。
ハイパーグラフ

シェーダがついたメッシュノードを走査するコードは以下になります。
これが一番速い易しいと思います。

{
    for(MItDependencyNodes nodeIterator(MFn::kMesh); !nodeIterator.isDone(); nodeIterator.next()){
        MObject node = nodeIterator.thisNode();
        if(node.isNull()){
            continue;
        }
        if(!node.hasFn(MFn::kDagNode)){
            continue;
        }

        MFnMesh mesh(node);
        MFnDagNode parent(mesh.parent(0));
        parent.getPath(nodePath_);
#ifdef APPDEBUG
        log_ << Log::indent << "path " << nodePath_.fullPathName().asChar() << Log::endl;
#endif

        for(unsigned int i=0; i<mesh.instanceCount(true); ++i){
            MObjectArray shaders;
            MIntArray indices;
            mesh.getConnectedShaders(i, shaders, indices);
            for(unsigned int j=0; j<shaders.length(); ++j){
                MFnDependencyNode shaderNode(shaders[j]);
#ifdef APPDEBUG
                log_ << Log::indent << "traverse shader " << shaderNode.name().asChar() << Log::endl;
#endif
                uuids_.clear();
                traverseConnections(shaderNode);
            }
        }
    }// for(MItDependencyNodes
}

次にマテリアルの属性を辿ります。ハイパーグラフ上の矢印を辿っているイメージです。
OpenMayaでこの辺りの詳細説明を見つけることができませんでした。
参考になるかはわかりませんが、MPlugについては以下の説明をリンクしておきます。
AutoDesk Area Japan 第68回:AttributeとPlugについて語る

ハイパーグラフを見るとわかりますが巡回しているので注意が必要です。
非巡回(DAG)なのはメッシュやスキンのノードツリーのところで、シェーダのところは巡回します。
UUIDで比較していると不具合がでそうですが、現在は問題がでていないのでそのままにしています。

void traverseConnections(const MFnDependencyNode& dst)
{
    //同じuuidが出現したら巡回している
#if 201516<MAYA_API_VERSION
    std::string uuid(dst.uuid().asString().asChar());
#else
    std::string uuid(dst.name().asChar());
#endif
    if(uuids_.end() != uuids_.find(uuid)){
        return;
    }
    uuids_.insert(uuid);
#ifdef APPDEBUG
    log_ << uuid.c_str() <<  Log::endl;
#endif
    Log::ScopeIndent scoped(log_);

    //繋がっているMPlugを全検索
    MPlugArray plugs;
    dst.getConnections(plugs);
    for(unsigned int i=0; i<plugs.length(); ++i){
        MPlugArray connections;
        connections.clear();
        plugs[i].connectedTo(connections, true, false);
        for(unsigned int j=0; j<connections.length(); ++j){
            if(connections[j].node().hasFn(MFn::kDagNode)){
                continue;
            }
            //アニメーション付きのplace2dTextureならアニメーション取得
            if(connections[j].isCompound()){
                for(unsigned int k=0; k<connections[j].numChildren(); ++k){
                    MPlug childPlug = connections[j].child(k);
                    if(MAnimUtil::isAnimated(childPlug)){
#ifdef APPDEBUG
                        log_ << "animated compound: " << childPlug.name().asChar() << Log::endl;
#endif
                        if(connections[j].node().apiType() == MFn::kPlace2dTexture){
                            runPlug(childPlug);
                        }
                    }
                }
            }else if(MAnimUtil::isAnimated(connections[j])){
#ifdef APPDEBUG
                log_ << "animated: " << connections[j].node().apiTypeStr() << Log::endl;
#endif
                if(connections[j].node().apiType() == MFn::kPlace2dTexture){
                    runPlug(connections[j]);
                }
            }
            MFnDependencyNode src(connections[j].node());
            traverseConnections(src);
        }
    }
}

最後に、アニメーションを取得してFBXにカスタムのプロパティを追加します。
プロパティは確実にUnityに渡すことができる文字列にしています。
コードを貼っているだけで申し訳ないですが、 本編ではこれで最後です。

void runPlug(const MPlug& plug)
{
    Log::ScopeIndent scoped(log_);
    MObjectArray animations;
    if(!MAnimUtil::findAnimation(plug, animations)){
        return;
    }
    for(unsigned int i=0; i<animations.length(); ++i){
        MObject animCurveNode = animations[i];
        if(!animCurveNode.hasFn(MFn::kAnimCurve)){
            continue;
        }
        MFnAttribute attribute(plug.attribute());
        if(attribute.name() == "offsetU"){
            writeAnimationCurve(plug, MFnAnimCurve(animCurveNode), "xxx_decal_tu");
        }else if(attribute.name() == "offsetV"){
            writeAnimationCurve(plug, MFnAnimCurve(animCurveNode), "xxx_decal_tv");
        }
    }
}

void writeAnimationCurve(const MPlug& plug, const MFnAnimCurve& animCurve, const char* name)
{
    //----
    //中略
    //----
    // ノードのパス文字列でFbxSceneからFbxNodeを検索する
    FbxNode* node = findNode(nodePath_.fullPathName());
    if(NULL == node){
        return;
    }
    FbxProperty propCurve = FbxProperty::Create(node, FbxStringDT, animName.Buffer());
    propCurve.ModifyFlag(FbxPropertyFlags::eUserDefined, true);
    //----
    //中略
    //----
    propCurve.Set(FbxString(animString.c_str()));
}

Unityのインポートエディタ拡張

Unity側は特に悩むところはないです。Maya側で設定したキー、上の例では"xxx_decal_tu"、"xxx_decal_tv"で
アニメーションが文字列としてインポートできるため、アニメーションを復元し他スキン用のアニメーションと合成すれば完成です。
気を付ける点は、設定する以下4つのシェーダプロパティはセットで設定します。Repeatのアニメーションがなければ適当に追加します。

private const string PropertyNameDecalTexRepeatU = "material._DecalTex_ST.x";
private const string PropertyNameDecalTexRepeatV = "material._DecalTex_ST.y";
private const string PropertyNameDecalTexOffsetU = "material._DecalTex_ST.z";
private const string PropertyNameDecalTexOffsetV = "material._DecalTex_ST.w";

表情をUVアニメーションで付けたかったため、デカールテクスチャです。
何の証明にもなりませんが、以下が復元合成したアニメーションです。
Unityアニメーション

まとめ

駆け足かつコードを貼り付けただけですが、MayaのUVアニメーションをUnityへインポートする方法の説明をしました。
作業自体のまとめは以下です。

  • 良かった点
    • 要望を達成した。
    • 作業期間は概ね予定どおりで終了した。
  • 問題点
    • DLL方式のプラグインの場合、DLLを解放する仕組みがないと開発がつらい。
      • Mayaの再起動に時間がかかる。だから、1日4~5時間作業で、他の仕事と同時進行の見積もり。
  • 改善点
    • Pythonだけで解決できないか。

発展

この記事を書くにあたりコードなどを見返していて、スキニングに削れそうなキーフレームがいくつかあることに気づきました。
現在は頑張って削る必要はないのですが、将来必要に応じてインポート拡張に入れる準備もしておきましょう。
コードだけですが。

public static AnimationCurve decimate(AnimationCurve src, float tolerance=1.0e-4f, int resolution=1024)
{
    System.Diagnostics.Debug.Assert(null != src);
    System.Diagnostics.Debug.Assert(0.0f<=tolerance);
    System.Diagnostics.Debug.Assert(0<resolution);

    AnimationCurve dst = new AnimationCurve(src.keys);
    int srcIndex=1;
    int dstIndex=1;
    float invResolution = 1.0f/resolution;
    for(;;) {
        if(dst.length<3) {
            break;
        }
        if((src.length-1)<=srcIndex) {
            break;
        }
        //キーを削除しても、セクションが似ているかチェック
        Keyframe key = dst[dstIndex];
        dst.RemoveKey(dstIndex);

        float start = src[srcIndex-1].time;
        float end = src[srcIndex+1].time;
        float duration = end - start;
        bool equal = true;
        if(1.0e-5f<duration) {
            for(int i = 1; i<resolution; ++i) {
                float time = i*invResolution*duration + start;
                float v0 = dst.Evaluate(time);
                float v1 = src.Evaluate(time);
                if(tolerance<Mathf.Abs(v0-v1)) {
                    equal = false;
                    break;
                }
            }
        }
        //似ていなかったらキーを元に戻す
        if(!equal) {
            dst.AddKey(key);
            ++dstIndex;
        }
        ++srcIndex;
    }
    return dst;
}

おわりに

いかがでしたでしょうか、何かしら楽しげな仕事風景を、というつもりがただコードのコピーになってしまいました。
私のように、コードを示された方が理解が進む人がたくさんいることを願います。
プロジェクト初期はこのような作業環境準備の仕事がほとんどです。
将来チームが楽をするためにしっかりと土台を作っておきたいものです。

私達、第三研究開発本部では、このような下支えの仕事に積極的な方、もちろんゲーム作りが好きな方と
一緒に働きたいと考えています。もしご興味を持たれましたら、弊社グループ採用サイトをご確認ください。

採用情報|株式会社セガ・インタラクティブ
採用情報|株式会社セガゲームス -【SEGA Games Co., Ltd.】

そんなことよりHoudiniやろうぜ!

初めての方は初めまして、そうでない方はお久しぶりです。
ここ10年龍が如くシリーズ製作にどっぷり浸かってるVFXアーティストの伊地知と申します。

今日は最近巷で大人気のHoudiniで基本部分の理解から簡単なエフェクトをUE4に表示してみるまでを皆さんにお届けしようと思います。

とりあえずこれだけは知っておきたい基礎知識
●操作方法
選択モード:s→2(Point)、3(Edge)、4(Primitive)、5(Vertex)
編集モード:Enter
カメラモード:Esc(選択モードや編集モードの時は一時的にSpace又はAlt)
ノード生成:Tab→ノード名検索キーワード(例えばattribute wrangleならaw、Sphereならsph)



Houdiniではジオメトリを扱う際に各コンポーネントの種類毎にグループとアトリビュートという情報が持てます。
データの構造と流れを各ノードに把握しておくのが重要になってきます。

コンポーネントの種類
Point、Primitive、Vertex、Detailの4種類があります。
f:id:sgtech:20180120232427p:plain
それぞれのコンポーネントにランダムで色を付けるとこの様になります。
それぞれ点、面、面毎の点、全体と覚えておくと良いでしょう

アトリビュートの種類
各コンポーネントにアトリビュートが持てます
デフォルトのアトリビュートとしてはP(座標),N(法線),uv,Cd(色)などがあります。
また独自のアトリビュートを作成して情報を格納しておくことが出来ます。
後述しますがwrangleでの記述方法としては
f@hogehoge = rand(@ptnum);
// 上記はfloat型のhogehogeというアトリビュートを作る例です。@の前の1文字で型の指定が出来ます。vならベクター型、sなら文字列型です。
// ちなみに右項は頂点番号をシードにして0~1までの乱数を生成しています。
●アトリビュートの確認方法
ビューポート上の画面下半分でAlt+] → Alt+8 と押して下さい。GeometrySpreadSheetが表示されます。
GeometrySpreadSheetはデータの構造を確認する上で重要なものなので常に表示してのが良いでしょう。
f:id:sgtech:20180120232423p:plain

最も簡単なVOPとwrangleの例:
VEXでアトリビュートを扱うにはVOP(VEX Oprater)wrangleを使った二通りの方法があります。

SphereSOPを作りPrimitiveTypeをPolygonにしてFrequencyを10に上げます。
f:id:sgtech:20180120232146p:plain

次にpointVOPSOPを作りiやダブルクリックでpointVOPの中に潜ると

f:id:sgtech:20180120232139p:plain

こんな感じのインターフェイスがあります。
左側のgeometryvopglobal1というノードはpointVOPの第1入力の事です。
そして右側のgeometryvopoutput1はこのVOPノードネットワークの結果を返します。
試しにgeometryvopglobal1の出力のP(頂点の位置)とgeometryvopoutput1の入力のCd(頂点カラー)を繋いでみましょう。
すると球に頂点カラーが設定されます。

では同じ事をwrangeでやってみましょう。

先程のsphereSOPの下にpointwrangleSOPを作成し繋げます。
f:id:sgtech:20180120232132p:plain

pointwrangleSOPのパラメータにはコードを書き込む為のテキストエリアが存在し
そこに@Cd = @P;と入力しても先程のVOPと同じ結果が得られます。
f:id:sgtech:20180120232126p:plain

この様にVOPやwrangleを用いて頂点座標や頂点カラー、法線、UVなど様々なアトリビュートにアクセス出来るのがVEXの強みです。
またwrangleの場合、非常に高速に動作する(C/C++コンパイル相当)のも魅力的です。

ちなみにこの例の場合Cd(頂点カラー)にマイナスの値が入り込んでくるので
VOPであればfit rangeVOPを用いてwrangleであればfit01関数を用いて0~1の間に合わせてやると正しいデータになります。
f:id:sgtech:20180120232119p:plain
f:id:sgtech:20180120232431p:plain

Attributeのコピーや転送
同じトポロジー同士であればAttribute Copy、距離でアトリビュートを転写するのであればAttribute Transferを使うと良いです。
また違うコンポーネントにアトリビュートを移したい時はAttributePromoteを使うのが定石です。

グループ
各コンポーネントにグループを設定出来ます。
バウンディングオブジェクトでポイントのグループを指定する例
f:id:sgtech:20180120232418p:plain
法線でプリミティブのグループを指定をする例
f:id:sgtech:20180120232414p:plain

グループのコンポーネントの変換
ポイントグループをプリミティブグループに変換するのはGroup Promoteで出来ます。

駆け足でしたがここまでで基本的なジオメトリに関する操作は理解出来たでしょう。
ここまでの知識とSOPの機能を学んでいけばプロシージャルモデリングは可能です。
他のDCCツールと比較して無い機能もあったりしますがそうした際はポータルサイトで、英語で調べるとたいていは情報を得られますので、
あまり心配しなくとも良いでしょう。



ではここからは実際にループするパーティクルを作成してUE4に出力してみましょう。

ループするパーティクルの作成

フォースフィールドの作成
まずはフォースフィールドを生み出すガイドとなるカーブを作成します。
LineとCircleからSweepに繋いでねじれたチューブを作ってCarveで螺旋を取り出すという事をやります。
その際Lineの方に@pscaleと@orientというアトリビュートを持たせてSweepに渡す事で形状の調整をします。
そしてCopyStampでY軸回転に均等コピーしておきます。
f:id:sgtech:20180120232809j:plain
point wrangleには

float div = (@ptnum)/float(@numpt);
f@pscale = chramp("widthControl",div)*chf("widthBase");
@orient = quaternion( radians(chf("roll")+div*360)*chi("twistNum")*chramp("twistPow",div),@N );
の様に記述してパラメータを出し調整する事ですぼんだ様な形状にする事が出来ます。
ちなみにアトリビュートを使わない設定にすると
f:id:sgtech:20180120232822j:plain
この様に均等なねじれになります。
あとはDOPNetworkに渡す前に@forceアトリビュートを作っておけばフォースのガイドは完成です。

ワンショットパーティクルの作成
DOPNetworkにつないでパーティクルを作成する際popnetという名前で作成しておけば
自動的にPOP ObjectPOP SourcePOP Solverを作って繋げておいてくれるので楽です。
f:id:sgtech:20180120232804j:plain

なぜループのパーティクルを作るのにワンショットを作るのかといいますと
シミュレーションでノイズをかけるとパーティクルは周期的にはなりませんので
ワンショットパーティクルを作成してそれを連続再生させてループに見せる手法を取るためです。
(残念ながらGameDevelopmentToolSetのMakeLoopはパーティクルには効きません)
f:id:sgtech:20180120232816p:plain
図にするとこんな感じです
f:id:sgtech:20180120232806p:plain
ファイルキャッシュにした時点でトライ&エラーがやりづらいのであらかじめワンショットの時点で
動きや雰囲気などを皆さんの感覚で良い感じに見えるようにしておきます。
もっとうまい方法があれば良いんですが世の中そう都合の良い話は無いですね。
DOPNetowork内に話を戻すのですが先程作ったフォースのガイドを
あらかじめDOPNetworkの2番目に繋いでおいてSOPGeometryのSOP Pathに

`opinputpath("..",1)`
としてfieldforceDOPに繋いでPOPSolverとoutputの間に差し込みます。
f:id:sgtech:20180120232930j:plain
またPOPSourceの下にちょっとしたノイズを足す為にPOP Wind DOPを足しておきます。
すると動き的にはこんな感じになります。
f:id:sgtech:20180120232919g:plain

ループパーティクルの作成
これをfile cacheSOPでキャッシュを取っておいて前述の半分にして折り返す処理をします。
Alt+Shit+Gを押してGlobal Animation Optionsを出してシーンのフレーム数を半分にして
Time Shiftでフレーム数の半分を足してMergeします
f:id:sgtech:20180120232916j:plain

すると動きがループになります。
f:id:sgtech:20180120232905g:plain

ブラーが欲しくなるので気持ち的にはここでTrailSOPを使いたくなるのですが
スタートフレームとエンドフレームで破綻するのでTrailは使わずに一旦file cacheに動きを保存しておいて
Time ShiftSOPのエクスプレッションで対応します。
f:id:sgtech:20180120232901j:plain
こうすることでループさせてもスタートとエンドがキレイにつながります。

パーティクルのメッシュ化
Particle Fluid Surfaceにつなげてパーティクルをメッシュ化します。
物凄く重いので形がカッコよくなった段階で一度file cacheに取りLoad From Diskにチェックを付け
滑らかに再生出来る状態で動きを確認します。
f:id:sgtech:20180120233044j:plain

メッシュの削減
Poly Reduceなどを用いて最適なポリゴン数にまで落とし込みます。
CleanやRemesh、Dissolveなども使っても良いかもしれません。
f:id:sgtech:20180120233019g:plain

VertexAnimationTexturesノードを使ってデータを出力する
この状態をUE4に持っていくことにします。
/outモジュールに移って Vertex Animation Texturesノードを作ります。
f:id:sgtech:20180120233014p:plain
このノード名がメニューに出てこない方は
github.com
をダウンロードして入れて下さい。
入れ方が分からない人は同梱されてるREADMEを読んで下さい。
(英語ですけどそんなに難しく無いので大体分かると思います。)
で、
f:id:sgtech:20180120233010p:plain
MethodはここではFluid、ExportNodeを設定、Target Poly Countを決定しRenderボタンを押します。
※@Cd(頂点カラー)のアトリビュートを設定しておけばColorMapのチェックボックスを付ける事で色情報も出せます。

VertexAnimationTexturesのシェーダをUE4上で構築する
次にVertexAnimationTexturesのシェーダをUE4に持っていきます。
VertexAnimationTexturesノードの下の方にあるFluid VertexAnimation UE4 Codeの+ボタンを押してUE4のシェーダーコードを表示し
f:id:sgtech:20180120233006p:plain
全選択してコピーします。
f:id:sgtech:20180120233137p:plain
UE4上で新規マテリアルを作成して
[ff:id:sgtech:20180120233132p:plain]
ダブルクリックしてマテリアルエディタを開きます。
f:id:sgtech:20180120233124p:plain
そして先程コピーしておいたUE4のシェーダーコードをペーストします。
f:id:sgtech:20180120233119p:plain
するとこの様にマテリアルエディタ上にシェーダのノードネットワークが構築されます。
f:id:sgtech:20180120233115p:plain
マテリアルエディタの詳細タブの目玉マークのプルダウンメニューからアドバンスな詳細を表示にチェックを付けます。
f:id:sgtech:20180120233308p:plain
さらにカスタムUVの数3に設定します。
あとはノードネットワークのコメントに従って対応するパラメータを接続します。
f:id:sgtech:20180120233303p:plain
これでマテリアルは完成です。

マテリアルインスタンスの設定
先程Renderボタンで作成されたFBX(モデル)とEXR(テクスチャ)をUE4にドラッグ&ドロップしてインポートします。
f:id:sgtech:20180120233259p:plain
「全てインポート」で構いません。
f:id:sgtech:20180120233251p:plain
次にマテリアルインスタンスを作ります。
f:id:sgtech:20180120233248p:plain
ダブルクリックして設定ウィンドウを開き先程作ったマテリアルを親マテリアルにします。
f:id:sgtech:20180120233420p:plain
HoudiniのVertex AnimationノードのBBOX MAXとMINの値をコピーして
UE4にペーストします。あと該当するチェックやフレーム数も同様です。
f:id:sgtech:20180120233417p:plain
あとポジションのテクスチャも設定しておきます。
f:id:sgtech:20180120233412p:plain
これでマテリアルインスタンスの設定は出来ました。

モデルにマテリアルインスタンスをアサインする
先程インポートしたFBXモデルをダブルクリックして設定ウィンドウを開きます。
マテリアルスロットに作成したマテリアルインスタンスを設定します。
ビルド設定の「最大制度のUVを使用」にチェックを付けてモデルへのマテリアルインスタンスの設定は終了です。
f:id:sgtech:20180120233409p:plain

これでモデルをビューポートにドラッグ&ドロップすればUE4上で再生出来るループする流体エフェクトの完成です。

f:id:sgtech:20180120233345g:plain

いかがでしたでしょうか?
ゲームに出力する部分は意外と手続き的な処理が多く煩わしさはありますが、Houdiniで作った流体表現が
ゲームに表示出来るというのは感慨深いものがあるのではないでしょうか?
こういったエフェクトの手法は日々進化していきます。
現状ビルボードが主流のゲームエフェクトですがデータとシェーダを工夫する事でこの様な表現が可能になりました。
5年後10年後どういった表現が可能になるのかワクワクしますね。

という訳で恒例の求人タイムです。
弊社ではHoudiniに興味を持って取り組めるような人を募集しています!
私の作ったシーンを見放題にもなれますし社内でハンズオンセミナーなども開催していますので
興味のある方は以下のリンクをクリックしてみてください。



採用情報 | セガ企業情報サイト

Ruby on Railsでアセットライブラリを作ろう!

 年の瀬も押し迫り、すっかり寒くなりましたね。今年最後のお役目をつとめます、セガゲームス開発統括部アート&デザイン部TA(テクニカルアーティスト)セクションの宮下です。
 普段はUnityでアーティストとプログラマを繋いだり、Mayaプラグイン(C++)やスクリプトを書いたり、たまにデザインデータを作ったりしてます。今回は、TAの業務でも少し変わったネタとして、Webアプリ作成についてお伝えします。とくに最新のネタというわけでもないので、プログラマの方には退屈な記事かと思いますが、ご容赦ください。

そもそもWebアプリって?

 ブラウザ上で動く、サーバーを介したアプリケーション、みなさんも普段から使ってますよね。GoogleやFacebook、Twitter、Slackなど、インターネットを通して提供されているサービスは、ほぼWebアプリと呼んでいいでしょう。あのような有用なサービス、そこまでいかなくてもちょっとしたサービスをプロジェクトや部署の中で提供できたら楽しくないですか?もちろん会社内でのWebアプリなので、インターネットではなく、イントラネットを使ったサービスということです。

Docker

 その方法の1つは、オープンソースのWebアプリを探してインストールし運用することです。最近はDockerというものがあって、簡単にWebアプリをPCにインストールできます。

docker run --name mattermost-preview -d --publish 8065:8065 mattermost/mattermost-preview

 Dockerがインストールされていれば、このコマンドを実行するだけでSlackのようなチャットサービスMattermostを提供できます。
 もしかすると、これぐらい当たり前のように感じられるかもしれませんが、まともにWebアプリをインストールしようとすると面倒なことになるので、このDockerはなかなか画期的です。
 ニーズにマッチしたオープンソースのWebアプリが見つかればいいですが、探しても見つからない…。そんなときは、もう1つの方法、自分で作るしかない!TAだから!(昔はデザグラマって言われてました…。今はいい言葉があります。)
 今回は、アセットライブラリとボット管理ツールなど3例、紹介します。

アセットライブラリ

f:id:sgtech:20171224002340j:plain
f:id:sgtech:20171224002335j:plain
 2015年に作った部内用のWebアプリで、アーティストの作成したデータの共有を目的としています。具体的には、psdやtgaなどの画像ファイル、UnityのスクリプトやPrefabなどを格納できるUnitypackageファイルなどを共有しています。基本的に誰でもブラウザからアップロード、ダウンロード可能で、検索やタグ付け、フォルダのような階層構造も持っています。合わせてgifサムネイルを自動的に作成しUnity上からアップロードできるUnityスクリプトも用意しました。モーションやエフェクトは、gifアニメで視覚的にどのようなアセットかを確認できます。
 ファイルをアップロードする機能を作って公開した直後に、誰かがアップロードしていると、他の人がアクセス不能状態になることが判明し、あわてたことを覚えています。1人で開発とテストをしているときには分からなかった問題だったのですが、原因はアップロードした画像のサムネイルを生成するなどの後処理がメインプロセスを占有してしまうためで、後処理を非同期にすることで対応しました。
 

ボット管理ツール

f:id:sgtech:20171224002348j:plain
 私たちの組織では、Cisco SPARKというチャットツール(チャットワークやSlackのようなものです)を導入しています。それにはボットを組み込む機能があり、何らかの情報をボットから提供することができます。ただし、APIが公開されているだけで、使うにはハードルが少々高い。そこで、ボット管理ツールをWebアプリとして作りました。
 このWebアプリでは、ボットのスケジュールや発言内容、週替わりや日替わりの当番の指定ができます。たとえば、特定のメッセージを決まった時刻に発言させる、毎週水曜日のミーティングの議事録当番を知らせる…といったことが可能になります。
 もともとは今参加しているプロジェクトの朝会当番を、ボットに教えてもらうために作ったのですが、現在では他プロジェクトやセクションのミーティング告知など、幅広く活用してもらっています。
 面白かったのは、本来当番のメンバー名を入力すべき箇所(上のスクリーンショットでぼかしの入っているところですね)に、カレーやラーメンなどの食べ物名を入れて、ボットに今日食べる昼ごはんを日替わりでつぶやかせているプロジェクトがあったことです。遊び心のある思いもよらなかった使い方に驚かされました。

アーティストアサイン管理ツール

f:id:sgtech:20171224002344j:plain
 プロジェクトがいくつも平行している中、アーティストのアサインの調整がスピーディに求められていませんか?私たちの組織では求められています。エクセルでの管理もいいのですが、もっと効果的に管理はできないものかと突貫で作り上げたのが、このWebアプリです。リーダーがメンバーのスケジュールを入力し、どういったスキルのアーティストを求めているのか、また、いつアーティストがプロジェクトからリリースされるのかを視覚的に分かるようにして管理しています。まだまだ発展途上のWebアプリで現在も作成中です。
 
 それでは、次にこのようなWebアプリの作成方法を簡単に説明します。

Ruby on Rails

 私、個人的にRubyが大好きで処理の自動化などに使っています。でも、MayaなどのDCCツールのスクリプトに採用されている言語はもっぱらPythonなので、実はあまりアーティストには勧められません。
 Webアプリを作るためのフレームワークとして、とても有名なものがあります。それが、このRubyで作られたフレームワーク「Ruby on Rails」です。「Ruby on Rails」の思想は、ほかのフレームワークにも大きな影響を与えていて、Pythonなら「Django」、phpなら「CakePHP」というフレームワークがあります。「Ruby on Rails」は2004年の誕生でなかなか歴史があり日本でも人気です。それゆえ、Web上での日本語の情報も豊富でビギナーにもお勧めできるフレームワークです。大抵の問題や要望は、Webで検索すれば解決できます。

 「Ruby on Rails」はActive Recordが有名で、この機能により簡単にデータベースにアクセスできます。具体的には、オブジェクトのインスタンスが、そのデータベースの1つの行を表すような仕組みです。

a = Game.new 
a.name = "sonic the hedgehog"
a.price = 5000
a.save

このコードで、新しい行を追加できます。すばらしい!

 「10分で Rails」とWebで検索するとたくさんヒットするように、とても簡単にWebアプリのひな形を作ることができます。ここでは「セガ」というWebアプリを新しく作ってみますね。

rails new sega
cd sega
rails generate scaffold game name:string price:integer
rake db:migrate
rails s

 これらを実行したのちに

 http://localhost:3000/games

にブラウザでアクセスすると、
f:id:sgtech:20171224002332j:plain
のような画面が出てくると思います。
New Gameをクリックすると、
f:id:sgtech:20171224002411j:plain
このような画面に遷移して、ゲーム名と値段を入れることができます。先ほどActive Recordでの例をブラウザから入力して、「Create Game」ボタンを押してみます。
f:id:sgtech:20171224002407j:plain
どうですか?簡単ですね!
 上記のアセットライブラリやボット管理ツールは、この「Ruby on Rails」のバージョン4で作成し、このようなひな形から肉付けして発展させたものです。そして、その作業を助けてくれるのがRubyGemsです。

RubyGems

 Webアプリの機能を作る上で、すべてを1から書くのはとても時間がかかります。RubyGemsはRubyのパッケージ管理システムで、世界中のスペシャリストたちが作ってくれた便利機能を簡単にインストールできます。ここで、さきほど紹介したアセットライブラリを作る上で組み込んだパッケージを紹介します。

devise

アカウント管理のためのGemです。ユーザーアカウントとパスワードにまつわる部分を簡単に実装できます。
github.com

CarrierWave

ファイルのアップロード機能を組み込むためのGemです。
github.com

kaminari

ページ切り替え機能を提供します。こういう名前が日本語由来のGemを見ると、ちょっとうれしくなりますね。
github.com

rmagick

画像フォーマットを扱うのに便利です。psdやtgaのサムネイルをブラウザで表示させるためにjpegに変換するのに使っています。ただ、psdをうまく処理できないケースがあって、それに対応するため、「psd.rb」というGemも使っています。
github.com

psd.rb

psdファイルを読み込んだり、サムネイルを作成するためのGemです。
github.com

ransack

便利な検索機能を提供してくれます。
github.com

最後に

 アセットライブラリのWebアプリ(サービス)と言えば、このサイトが良くできてます。
sketchfab.com
 こんなの作ってみたいですね。ブラウザさえ動けば、もはや何もいらない時代は意外と近そうです。

 ここまで読んでくださった方、ありがとうございました。
 TAセクションではこのようにアーティストの制作に役立つ環境を提供できるよう力を注いでおります。Unityの登場でTAの活躍できるフィールドは格段に広がり、シェーダーやポストエフェクトなど面白い表現を生み出せないかと日々切磋琢磨しております。そんな中でお仕事したい方はぜひ下記の弊社グループ採用サイトをご確認ください。いっしょに働きましょう!

採用情報 | セガ企業情報サイト

TAが勉強を兼ねてモバイル向け電光掲示板シェーダー書いてみた

はじめまして、亀川と申します。


セガゲームス開発統括部アート&デザイン部TAセクションというところに所属しており普段はモバイル向けのゲーム開発に従事しております。


所属はTAセクションですが、業務内容はデザイン業務とTA業務が半々ぐらいです。
内容はモーション制作・管理、Mayaをはじめ各種DCCツールのツールやライブラリの開発、保守など日々TAセクションに寄せられる要望に応える形で作業を行っております。


弊部のゲーム開発は全てUnityで行われているのですが、ご存じの通りUnityはかなりオープンなゲームエンジンなので各種情報が公式ドキュメントやWEB上でも入手しやすく学習しやすいものとなっており、ゲームの実装に関わる部分でもTAが関われる部分が増えてきたというのが実感としてあります。


そういった中でもシェーダー開発は飛躍的に敷居が低くなったのではないかと思います。
少しプログラムをかじったことがあればレベルにもよりますが、すぐにでも画面に出るものが作って試せます。これは非常に楽しいです。


そういった事もあり、ここ1、2年は空前のシェーダープログラミングブームが来ていると思います。
私もご多分に漏れず、これまであまり触れる事のなかったゲーム中のビジュアル周りに関わるシェーダーの勉強を始めてみました。


前置きが長くなってしまいましたが、今回の記事は以前に部内のあるプロジェクト用に作った「電光広告看板」、「電光掲示板」用シェーダーについて作成のプロセスと内容について触れていきたいと思います。

電光広告看板とは?

電光広告看板という言葉は普段生活している中ではほぼ聞く事はないのですが、街中や施設の中など目にする機会は非常に多いと思います。


LED電球を並べて広告を映像として表示させることができるものです。
その中でも特に今回はサッカー場の横に置かれている電光広告看板を想定して作成しました。

制作物

結果から先に見せてしまうと、こういう物を制作しました。

f:id:sgtech:20171123211804j:plain:w120
f:id:sgtech:20171123211800j:plain:w120
f:id:sgtech:20171123211753j:plain:w120
f:id:sgtech:20171123211749j:plain:w120
f:id:sgtech:20171123211745j:plain:w120

電光広告看板に見えますでしょうか?実際はアニメーションしていますので本当は動画を置きたかったのですが”はてなblog”に動画を直接アップロードできなかったので静止画になっています。

それではこの看板シェーダーを作るプロセスと実際のコード内容を説明していきたいと思います。

制作前の仕様確認

制作前には仕様の確認が必要となりますので発注者であるアートリーダーと最終イメージと使い方などを含めた仕様のすり合わせを行いました。


具体的には広告の動かし方?テクスチャは何パターン用意するのか?メッシュのUVの持たせ方は?背景のライティングとの兼ね合いは?プログラムからの操作、アーティストがどこまで調整できるかなどなどをまずはざっくりで良いのでヒヤリングを行いシェーダーに持たせるパラメータや仕様を決めて行きます。


最初のヒヤリングで全ての要求仕様が出揃うことはないので、まずは最低限作成できるレベルの仕様はつめます。


ここでざっくり決まった仕様はこんな感じです。


・最近の電光掲示板のような広告
・サッカー場の回りにポリゴン板を立てて置くイメージ。何パターンか置きたい。
・ライティングは反映する必要はない。
・テクスチャスロットは2枚。プログラムで切り替える。
・2枚とも非表示にする事がある。その時は指定色が出るようにしたい。
・ブレンドで切り替えたい。
・広告テクスチャの移動はUVアニメーションで制御したい。

電光掲示板らしさの要素抽出

機能的な仕様が出そろったのでいよいよ今度はビジュアル面の方向性を固めていきます。


今回のゲームでは中継カメラを意識したような視点が想定されるので素直にTVを通して見た電光掲示板らしい表現を目指すことにします。


そのためは電光掲示板がそれらしく見えている特徴的な部分の抽出が必要です。この要素分解をするのが楽しいところですが、私はこう考えました。


・LEDならではのドット感。
・カメラのレンズを通す事による色収差、にじみ感。
・広告が切り替わる動き。スクロールする時のアニメーション。
・LED表示を描画しているスキャンラインの雰囲気。
・光のにじみのグロー表現


以上の要素があればだいたいそれらしく見えるのではないかと思いました。
今回は、広告の動きはUVアニメーション、光のにじみはポストエフェクトで適宜行うとして看板シェーダー自体の描画ではその前段階までを表現することにしました。

シェーダー実制作

先に完成したコードがこちらになります。

全体のシェーダーコードは以下のようになります。


インスペクターに表示されるシェーダープロパティはこんな風になっています。
f:id:sgtech:20171123211818p:plain:w120


それでは上で書いた要素がソース中でどのように実装されているのか中身を追って見ていきたいと思います。

LED球のドット感の表現

ドット感の表現はシンプルにLED球1つを表現したテクスチャ1枚をタイリングでしきつめて上から乗算で被せます。加えて、広告画像の方にも少し手を加えますがこちらは実装に少し迷ったので2つの表現を用意しました。

LED枠のイメージ

まずはLED枠のイメージですがこんな感じシンプルにタイリングするだけです。
この枠の下に広告画像が来る事になります。
f:id:sgtech:20171123211722p:plain

広告画像テクスチャの加工1 ”1ドット1色”

一つ目は広告画像をタイリング枠に合わせて1ドット1色という色の取り方をするものです。
こちらの方がLED球1つに対して1色なので原理的にはより本物に近いのですが、一方で相当細かくLED球をタイリングしないと絵が潰れてしまい判別できなくなる問題もあります。

f:id:sgtech:20171123211718p:plain

ソースはこうなっています。

                #ifdef _IS_REAL_REAL_LED
                    //こちらが1ドット1色
                    //Pixelate for MainTex
                    half2 steppedUV = i.uv.xy + _CellSizeXY*0.5;
                    steppedUV /= _CellSizeXY.xy;
                    steppedUV = round(steppedUV);
                    steppedUV *= _CellSizeXY.xy;
                    col = tex2D(_MainTex, steppedUV);

                    //Pixelate for MainTex2
                    steppedUV = i.uv2.xy + _CellSizeXY*0.5;
                    steppedUV /= _CellSizeXY.xy;
                    steppedUV = round(steppedUV);
                    steppedUV *= _CellSizeXY.xy;
                    col_mt2 = tex2D(_MainTex2, steppedUV);

この部分でポイントになってくるのは"_LedTex"で取得したLED枠テクスチャのタイリング単位でテクスチャの色を1色だけサンプリングしてくる部分です。

テクスチャのタイリング数は"_LedTex_ST.xy"でxyそれぞれのタイリング回数を得る事が出来ます。xを3回、yを3回繰り返すなら(3,3)という形で格納されています。


またテクスチャの色はUV空間で取得するのでこのタイリングのxy回数の1回分のサイズを0-1のUV空間の値で知る必要があります。それをコード中で事前に行っている部分がこちらです。

static const half2 _CellSizeXY = 1.0 / _LedTex_ST.xy;


UV値単位で1つのセルサイズを知ることが出来たら後はテクスチャのUVの値に対してセルサイズの半分を足しその値をセルサイズで割り、roundで端数を切り捨てる事で段階的なUV値を得ます。そのUV値を使ってテクスチャの色を取得することで段階的に取得することが出来ます。

広告画像テクスチャの加工2 ”画素ずらしによる疑似色収差”

1ドット1色は本物に近いのですがやはりLED枠タイリングを細かくしないとそもそも画像の認識が難しいです。
そのため、ドット化なしに通常通りテクスチャを張り付けるパターンも用意しました。これが2つ目です。

こちらはそのままテクスチャを表示するだけなのですが、そのままでは少し物足りなかったのでカメラのレンズによる色収差を表現する事にしました。
非常にお手軽な手法ですが色を取得する際にrgbの各チャンネルを1pxずつずらして取得することにしました。

それがこの画像になります。
f:id:sgtech:20171123211714p:plain


よりアップで見た画像がこちらです。LEDの枠はまたいで色はあるものの離れて見るときは1ドット1色とそう差異は感じられない印象です。

f:id:sgtech:20171123211710p:plain

こちらのソースはこうなっています。

                #elif _IS_REAL_FAKE_LED
                    //枠は関係なくテクスチャを読み込む
                    //Shift Texture color by 1px
                    col = tex2D(_MainTex, i.uv);
                    col.g = tex2D(_MainTex, i.uv.xy + _Texby1pt.x).g;
                    col.b = tex2D(_MainTex, i.uv.xy + _Texby1pt.x).b;

                    //Shift Texture color by 1px
                    col_mt2 = tex2D(_MainTex2, i.uv2);
                    col_mt2.g = tex2D(_MainTex2, i.uv2.xy + _Texby1pt.x).g;
                    col_mt2.b = tex2D(_MainTex2, i.uv2.xy + _Texby1pt.x).b;
                    
                #endif

テクスチャからrgbチャンネルごとに1pxずつずらして取得していることがわかります。
テクスチャの解像度の1px単位がUV値に換算するとどれぐらいになるのかは下に書いた行で事前に取得していました。

static const half2 _Texby1pt = half2(_MainTex_TexelSize.x , _MainTex_TexelSize.y );
マルチコンパイル

この2つの表現方法をUnityのマルチコンパイルという機能を使い1つのシェーダー内で2つのバリエーションのシェーダーを持てるように実装しています。


この機能の実装部分はCGPROGRAM~の少し下の行のスニペットと呼ばれる部分に記述されている以下の行となります。

#pragma multi_compile _IS_REAL_LED _IS_FAKE_LED

これはプロパティ部分を記述している部分に

[KeywordEnum(REAL_LED, FAKE_LED )]
_IS_REAL("IS Real LED", Float) = 0

このように記載することでアーティストがシェーダープロパティからどちらのモードを使用するか使い分ける事が出来ます。

f:id:sgtech:20171123211706p:plain


さらにフラグメントシェーダーの中もこれに対応している部分があります。上述のLED部分の中で#ifdefから始まり#endifまでの部分でプロパティのキーワードで条件として処理を分けています。

個人的にはピクセル化を行うREAL_LEDモードの方の方が力を入れて用意したのですが実際の現場では疑似モードの方でLEDの枠のわかりやすさ、テクスチャのわかりやすさから選択されているようです。

広告画像の切り替え

Lerp関数

1枚目と2枚目、そしてベースカラーの3つの色の切り替えは主にlerp関数を使って行っています。
動画が貼れないのでまた静止画ですみません。左から時系列でベースカラー、広告1、広告2そしてまたベースカラーへと変遷しています。

f:id:sgtech:20171123212418j:plain

                //Blend Texture Main and Main2farPower
                col.rgb = lerp(col.rgb, col_mt2.rgb, _MainTex2_BlendPower);
                //Blend Base color and Textures.
                col.rgb = lerp(_BaseColor.rgb, col.rgb, _BlendPower);


lerp関数を2回使う事で3つの色の切り替えを行っています。
最初の行で2枚の広告テクスチャの切り替えを、次のlerpで広告と指定カラーの切り替えを行います。


lerp関数はテクスチャのブレンドを行う際はよく使う関数でlerp(x, y, s)という形で使用しx,yの2枚のテクスチャをs(0~1)の値でブレンド度合を決めます。勿論用途はテクスチャに限らず2つの要素を0~1で割合でブレンド調整したい時には使いやすい関数だと思います。


広告の切り替えなので2つのテクスチャで十分ではないかと思われるかもしれませんがどちらの広告もオフにして何も表示しない状態にする時に第三の色が必要になってくるのでこの形式を取っています。また完全にどちらの広告が表示されないという状態の時にプログラムから広告テクスチャを入れかえる事も可能です。

LED描画更新による輝度ムラの表現

ここまでやってきましたがまだ何か物足りない気がしましたので輝度を時間軸で少しいじってみることにしました。

実際のLED電光掲示板は人間の目では意識できないほど高速に1ラインずつ描画を更新しています。それをカメラで写すとシャッタースピードのズレから輝度のムラのようなものを感じる事があります。そういうニュアンスを入れる事でさらに電光掲示板らしさが増します。

f:id:sgtech:20171123212531j:plain


左が何もしない時の映像で右が輝度の変化を加えたものになります。
どうでしょうか。上は静止画ですが実際は輝度ムラが上下左右でアニメーションします。
少し右側の方がそれらしく見えるのではないでしょうか?


その部分のソースがこちらになります。

                // 走査線1 uv基準でスキャンラインを描画
                half scanLineColor = sin(_Time.y * _LineSpeed + i.uv.y * _LineSpacing);// / 2 + 0.5;

                // 走査線2 uv基準でスキャンラインを描画
                half scanLineColor2 = sin(_Time.y * _LineSpeed*0.5 + i.uv.y * _LineSpacing*0.1);/// 2 + 0.5;
                
                // 走査線3 uv基準で横スキャンラインを描画
                half scanLineColor3 = sin(_Time.y * _LineSpeed*0.25 + i.uv.x * _LineSpacing2); //; / 2 + 0.5;

                col += (saturate(scanLineColor) + saturate(scanLineColor2) + saturate(scanLineColor3)) * lerp(_LineBrightness, 0.0, farPower) * _BlendPower;


やっていることは簡単です。sin関数を用いて現在の表示色に白を加算する事で輝度ムラを加えています。
それを波形のリズムを変えて3つ重ねています。こうする事で1つの時より自然な感じが出ます。

カメラからの距離に応じた対策

上述の輝度の変化ですがこれは看板に近い時は効果的ですが離れたところから見た時はやや不自然に感じました。


そこでカメラの距離に応じてその影響を加減することにしました。実は上で書いたコードにはそれはすでに含まれています。計算式の中のこの部分になります。

 (_LineBrightness * (1 - farPower)) * _BlendPower;

事前にカメラからの距離をfarPowerという変数に格納してあり距離に応じて光の強度が乗算する割合を変えています。

シェーダー高速化について

シェーダーを書く上で常に意識しないといけないのはやはり処理負荷の部分です。

この高速化について少し触れてみたいと思います。これに関しては月並みですがUnityドキュメントの”シェーダーを書く場合のパフォーマンスのヒント”という項目を忠実に行うにつきるかと思います。
https://docs.unity3d.com/jp/560/Manual/SL-ShaderPerformance.html


できるだけ頂点シェーダー内で済ませる

頂点シェーダー部分で取得した情報はフラグメントシェーダー内では”補間された値”で受け取ることができるので、ライティング情報など頂点で受け取っても問題ない場合は頂点シェーダー内で受け取ります。例えば3頂点で囲まれたポリゴンの場合、頂点シェーダーでの計算は3回ですがフラグメントはピクセル数に応じて行われますのでその計算量の差は圧倒的です。

できるだけ計算の精度を下げる。

floatよりhalf、halfよりfixedの順で計算が低精度になっていくので処理負荷が低くなります。
fixedで問題のないケースは積極的にfixedを使うとよいでしょう。

ただし

現代の多くの GPU(OpenGL ES 3 や Metal を実行することができるもの)は固定小数点数と
半精度浮動小数点数を、内部的にはまったく同じものとして取り扱います。

とUnityのドキュメントにもありますのでhalfで書いていても実質は問題ないというケースは多いのではないかと思います。

テクスチャへのフェッチ(読み込み)回数を減らす

テクスチャは通常フラグメント関数の部分でフェッチしますがピクセル数に応じて行われるのでパフォーマンスに影響します。例えば上述のシェーダーソース内の”画素ずらし”の部分でテクスチャのフェッチを4回やってますがこれはなかなかの負荷だと思われますので画像がこの用途以外に使い道がないのであれば事前にPhotoshop等で画素ずらしを行っておいてこの処理を省いてもよいでしょう。


まとめ


以上、電光掲示板シェーダーの制作事例の説明となります。今回はライティング、影や半透明ソートなどのややこしい部分は考えなくてよいのでシェーダーとしては比較的簡単な部類には入るのではないかと思いますが、それでも実機で表示されて実際のゲーム中で動作するとなかなか達成感があります。



今回は右も左もわからない、それこそセマンティクスって何ですか?というようなところからシェーダー学習を始めたのですが学習法は完全に我流でいろんな情報を並行して学んでいきました。


Unity公式ドキュメントやネット上の制作事例を参考にした他、Unityシェーダーの書籍、nVidiaのCgマニュアルなども参考にしました。

nVidiaのドキュメントはこのあたりから読む事が出来ます。


Cg Toolkit | NVIDIA Developer


ただ上記URL内のドキュメントは全て英語なので正直言ってざっと斜め読みするのも大変です。私を含めてそういった方には日本語に訳された物もあります。

http://developer.download.nvidia.com/cg/Cg_3.0/Cg_Users_Manual_JP.pdf


こちらはリリース日がかなり古いのが気になりますが、Cgの基本や数学関数の説明などは今でも使えるものですので一読されることをお薦め致します。


またゲーム会社ということもありシェーダーを始め描画周りの知識が豊富な者がたくさんおりますからアドバイスをもらったり、コードレビューしてもらったりしたことも大きな助けになりました。


ただ何にでも言えることだとは思うのですが、一番学習効果が高い勉強法は”何か作りたいものがあって実際に作ってみる”。これにつきると思います。


これが一番学習意欲が上がりますし、質問がより具体的になり、望む答えも得やすくなりますので是非皆さんも何か題材を見つけて取り組んでみてください。


次回はリグのネタでも載せられればと考えています。それでは次回またお会いしましょう。


 TAセクションではこのようにアーティストの制作に役立つ環境を提供できるよう力を注いでおります。やりたいことを遠回りせずに行える環境でお仕事したい方はぜひ下記の弊社グループ採用サイトをご確認ください。いっしょに働きましょう!現在いろいろな職種でアーティストを多数募集しております!

採用情報 | セガ企業情報サイト

(C)SEGA

Powered by はてなブログ