Quantcast
Channel: DF TALK
Viewing all 131 articles
Browse latest View live

crowdツールチュートリアル ~Miarmyで遊ぼう!~

$
0
0

[あいさつ]

お久しぶりです。
なんだか知らないうちに一年経ってしまった
crowdTDの 護守(ごのかみ) です。

本当はもっと早くに投稿する予定でしたが、
忙殺されましてね…
その忙殺されたお仕事は
もう少し先ですが公開されますのでお楽しみに。
 

さて、今回は前回ご紹介したMiarmyを使用してみたいと思います。
題して、
『Miarmyで遊ぼう!』
 

前回の記事:
crowdツールチュートリアル ~Miarmy入門!~

 
 

[introduction]

まずは、こちらをご覧ください。

一時期界隈で話題に上がってたので
もしかしたら見たことあるかもしれません。

動画の説明文にあるようにMiarmyで作られています。
 
 

今回はこれを作ってみようと思います。
 
 

いきなりハードル高いって思いますか?
実はそうでもないんです。
ちょちょいっと設定してあげればできちゃいます。
 
 
 
 

1:セットアップ

前提条件として今回は

・Maya2014
・Miarmy version 3.5.21

を使っています。
ちなみに、Miarmyの最新バージョンは3.6.17です。(記事執筆時)

すぐできるみたいなこと書きましたが
Agentは準備している前提で進めます。
「説明されないとわかんないよ!」って方は
公式のチュートリアル動画の1~3、21を参考に。
https://www.youtube.com/playlist?list=PLalQe7BG7XcL8YXywnLD6QSnIN-dV2VZe

もしくは、公式で配布しているサンプルデータを使用してください。
https://onedrive.live.com/?cid=50E5893406EEF5F7&id=50e5893406eef5f7!821
 

Agentのアクションで必要なのは走るモーションだけです。
いきなりハードル上がってますね…すいません…
しかし、こっからは本当に簡単です。

まずは、セットアップしたAgentデータを読み込みます。
読み込んだら、Agentにdynamicsを仕込みます。

dummyShape(cubeで表示されているもの)を選択して、
Collide Feelをonにします。
これを足、腕、胴体にやります。

このCollide Feelをonにすることで、MiarmyがこのdummyShapeを
コリジョン判定してくれるわけです。
 
 
 
 

2:brain

brainを作ります。
本来ダイナミクスを使うとAgentはいわゆるラグドール状態になってしまいます。
体が脱力した感じでだらーんとなってしまうんですよね。
しかし、動画で見ていただいたようにダイナミクス判定をしてもラグドールにならず、
しっかりとモーションを再生しています。
これは、Miarmyに付属している機能で“servo”と呼ばれるものです。

ここのbrainでその“servo”を設定します。

まずは、走るモーション用のbrainを作ります。
Miarmy > Logic and Decision > Make Decision

defaultと名前を入力してcreateを押します。
設定は画像を参照してください。


ちなみに公式のサンプルデータにはこれは設定されているはずですので
新たに作る必要はないです。
 

次に、dynamics判定用のbrainを作ります。
dynamicsという名前でdecisonを作成します。
設定は画像を参照してください。

この中でやっているのは
「この範囲の中で、コリジョン判定があったらdynamics(ラグドール)になるよ!」
ってことです。
「この範囲」というのは後々設定します。

さて次に“servo”を使用します。
servoという名前でdecisonを作成します。
内容はこちら。

「dynamics(ラグドール)になったらservo機能使うよ!」
ってことです。
ちなみにここでrunって記入している部分がservoで使用するアクションになります。
 
 
 
 

3:プレイスメント(配置)

Agentの配置とギミックの配置を行います。

Agentは適当な位置に列を作らせます。
PlacementのPlace Typeをformationにして、
あとはColumnとNumOfAgentをいじるだけです。
隣り合う距離が気になるようでしたらDistanceをいじってください。

次はギミックにいきます。
最初にbrainで記述した「この範囲」というものを指定します。
Miarmy > Knowledge Perception > Create Bound

このBoxの中が「この範囲」ということですね。
このBoundですが、Miarmyではbrainのトリガーとして非常によく使います。

さて、次に真ん中で回転していた棒を作ります。
Miarmy > Physics > Kinematic Primitives > Create Box Kine Prim

これでできました。

上記で作った二つの位置やスケールを調整します。

こんな感じですかね。

棒の回転アニメーションもあらかじめつけておきます。
 
 
 
 

4:シミュレーション

さてさて、お待ちかねシミュレーションです。
その前に、simple Transitionを外します。
外し方は以下。
Miarmy Globalを起動します。
Miarmy > Miarmy Global…から、もしくは、
前回の記事で配布しましたMiarmy GUI Collectionから起動できます。
Use Simple Transitionというところのチェックを外します。

ここは何かというと、ちょっと長くなりそうなので端折ります。
簡単にいうと、ここにチェックが入っていると
brainがうまく作動しないことがあるのでそれを防いでいるのです。
 
 

今度こそシミュレーションです。
シミュレーションの仕方は前回書いたので割愛します。

が、この方法ぶっちゃけめんどくさいです。
なので私は自作ツールを使用してます。

何が簡単って、cacheとmeshDrive二回ボタンを押さないといけないのが
ひとつのボタンにまとまってること!
超絶便利ですね。
cacheのパスもこのツールで設定できるので、前回の記事で書いたみたいに
windowを乱立させなくてもいいんですよね。

Miarmy触り始めてめんどくさすぎて一番最初に作ったツールですね。
 

とにはかくにも、これでシミュレーション完了です。

シミュレーションしてみて配置が気に入らなければ
いろいろと調整して再シミュレーションしてください。

ここの工程が一番楽しいです。
楽しすぎるので私は職場でげらげら笑ってました。完全に変な人です。

みなさまどうか周りに人がいないところでお試しください。
 
 
 
 

5:レンダリング

meshDriveをenableにします。
meshDriveをまだはき出していない場合ははき出してください。
やり方は前回の記事を参照で。

真ん中の棒ですが、このままだとレンダリングされませんので、
cubeを、作成したKinematic Primitiveと同じ位置・大きさにします。
もちろんアニメーションもコンストレインで持ってくるのを忘れずに。
お好みでカメラ、レンダー設定してLet’s Renderです。
 
 
 
 
 
 

出来上がったのがこちら。
気持ち悪いのが苦手な方は閲覧注意です。

_人人人人人人人人人_
> すごい気色悪い <
 ̄Y^Y^Y^Y^Y^Y^Y ̄

oh…もう…

気色悪い以前に画質があれすぎですね…
youtubeにアップするときに画質劣化したっぽいです。
室長に「はよせい」と怒られたので、すんませんがこれで我慢してください。

 
 
 
 

[おまけ]

さて、今回記事中で紹介した便利なsimulationツールを読者プレゼント!
応募はDFの採用情報から…
そんなわけないですね。ちゃんと公開します。

=> Miarmy Simulation Tool ダウンロード

※免責事項※
本記事内で公開している全ての手法及びファイルの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらの手法及びファイルを使用したことによって引き起こる直接的、間接的な損害に対し、
当方は一切責任を負うものではありません。
自己責任でご活用ください

※注意事項※
・使用方法は同封のreadmeをご参照ください。
使用する際はMiarmyのプラグインを読んだ状態でお願いします。
 

すでに、前回配布したMiarmy GUI Collection(以下まいこれ)を設定している方はスクリプト上書きだけでいけます。

ちなみにまいこれには他にもカスタムでスクリプトを登録できます。

登録の仕方?

じ、時間がないのでまた今度…
 
 
 
 

[おわりに]

そんなわけで検証という名目でいろいろ遊べるMiarmyです。
crowdでdynamics使うと楽しいですね。
中々実際のショットには使いにくいところもありますが、
雰囲気だけ出すならもってこいですね。

Miarmyあればもう何時間でも楽しめますね。
それはもうゲーム並みです。

あ、すいません…こんな盛り上げてますけど、
別に私Miarmyの会社の回しものじゃないです…
Miarmyにも弱点は多々ありますから…
こんなこと書いてるから室長に怒られるんですね。
仕方ないですね。
 
 

要するにこの記事で何が書きたかったというと

「楽しいよ!」

ってだけです。
 
 

それでは今回はこのへんで(:3ノシ )ヘ
 
 
 
 


≪セットアップ室≫角度でRigを制御する方法

$
0
0


初めまして!セットアップチームの田中です!
まさに五月晴れの日が続いていましたが、時期的にもうすぐ梅雨入りでしょうか。
天然パーマの僕にとっては梅雨は少し憂鬱です。

さてさて、セットアップチームのTALKをみてると、どうもCloth Simulationについての記事が多いような・・・
ということで、今回はRiggingについての記事を書いていきたいと思います。
題して【 角度でRigを制御する方法 】です!


Rigging作業をしてると、
「肩や足周りを曲げた時、特定のこの角度からこの角度の間にだけ反応する補助骨をつくりたい!」
という場合がぼちぼちあるかと思います。
例えば弊社でも、肩を回転させたとき、胸と腕が触れ合ったタイミングからめり込みを回避するような補助骨を入れたりします。
ですが、肩・手首・足首など複雑に3軸の数値が入るところは、X軸など限定した1つの軸から数値を取得して補助骨を作ろうとするとフリップを起こしたり、予期せぬ動きをすることが多々あります。
基本的にこのようなRiggingは望んだ動きにならないので、DFセットアップチームではご法度です。
ですので、うちでは別の方法を使ってRiggingを行っております。

まぁまず、ごたごた言うよりどういうものなのかを見てもらおうと思います。
GIF画像ですので、クリックしていただくと動きます。
posereader

どうでしょうか?
回転しているJointがある一定の角度に進入したタイミングで補助骨が動き始めたのがわかってもらえたでしょうか?
実は、弊社には『poseReader』というプラグインがありまして、GIFアニメーション上で見えるコーンがそうです。
(poseReaderは社外で作られたプラグインです。)
回転しているJointがコーンに進入してから真ん中に至るまで、補助骨は動いています。
そうです!『poseReader』を使うと上記のようなことができるのです!!






「・・・は?プラグイン無いとできないとか、無理じゃん!ふざけんなっ!!」






はい、みなさんの声が聞こえてくるようです・・・。
さすがにそんな状態で記事を書くほど僕も肝が据わっておりませんので、
そのプラグインと同等のことが出来るrigging方法を紹介しようかと思います。

さて、ここでやっとタイトルに戻ります。角度でRigを制御する!!
Mayaには『angleBetween』 というNodeがありまして、これを使うことによって角度を算出することができます。
(angleBetweenがわからない人は、≪セットアップ室≫Maya Utilityノードのセットアップでの使い方 第1回へどうぞ!)
まずは、この方の動画を3分くらいまで観てください。

MAYA TUTORIAL : build a poseReader with maya nodes from Marco Giordano on Vimeo.

上記動画を観ていただいたという前提で3分くらいまでの内容を掻い摘んでお話しすると、
●ConeAngle(動画上は90度)内にいるときだけ、resultという名前のロケータのアトリビュートが0~1で変化する。
●動かしているロケータが中心のロケータ(コーンの中心軸にあるもの)に近づけば近づくほど数値が1に近づき、コーンの外周(ConeAngleの数値で外周が変動)に近づくほど数値が0になる。コーンの外周より外に出てからは数値がずっと0。


動画を最後まで見ていただければ組み方はわかるかと思いますが、英語で何言ってるのかわからないという方に図で構造を説明します。
この画像内でWEIGHTと表記されているのが動画内でresultという名前のロケータで確認できた変動している数値です。
rig構造解説
connection
こんな感じの計算がNodeで組まれています。
画像上のBに関しては変動する角度になりますので、ここをangleBetweenで取得してきます。
上の図では単純な説明にするために無視しましたが、rigの途中にConditionNodeが挟まります。
これが、Coneの外に出てからの数値をすべて0に限定するために使われます。(Cが1以上にならないようにする。)

このRigの使用例としてはこういう感じです。
単純にWEIGHTをJointのtranslateXにコネクションしただけです。
GIF画像ですので、クリックしていただくと動きます。
Angleweight
poseReaderと同じことが出来ましたね!!
回転しているJointと一緒に動いているロケータがtargetで、poseロケータに近づいていくと反応します。
ちなみに、設定はCONE ANGLE が90です。






さて、構造の説明は終わりました。
最後まで読んでいただいた皆さんに、上記で説明したRigが一発で仕込めるScriptを配布したいと思います。
なんせ、コネクション周りの話を詳しくはできていないので、気が引けまして・・・
最初に見ていただいた動画から少々構造が変わってはいますが、やっていることはまったく同じです。

【ダウンロード先】
http://www.dfx.co.jp/freestuff/scripts/AngleWeight.py
txtに貼り付けて、ファイル名を『AngleWeight.py』で保存してください。

【実行方法】
Scriptファイルを読み込める場所に配置。
仕込みたいJoint等を選択して実行。

【実行コマンド】
import AngleWeight as AW
AW.AngleWeight()

【作成されるものの説明】
AngleWeight・・・ロケータを内包するグループ。CONE ANGLEとWEIGHTというAttributeを持つ。
 ●CONE ANGLE・・・WEIGHTの数値を計上する際に使用する最大角度。動画上でのConeの角度です。
 ●WEIGHT・・・Rigging時に使うAngleから計上される数値。0~1で変動。
target・・・poseと近づけたり離したりすることで、WEIGHTの数値が変動する。
base・・・動かさない。計算の起点になるロケータ。
pose・・・基本的には動かさない。poseに対してtargetを近づけていくと数値が変動する。

【使用方法】
選択したものの子として『(実行時選択したものの名前)_AngleWeight』というロケータ3つを含むグループが作られていると思います。
任意の角度に向けて、後はtargetロケータを動かせばAngleWeightのAttributeが変動しているのがわかるかと思います。
『(実行時選択したものの名前)_AngleWeight』のAttributeにWEIGHTがあるかと思います。
これがロケータ間のAngleを計算して出される数値(0~1の変動値)になりますので、riggingの際にお使いください。


以上です!
次回はもう少し簡単なRiggingの説明をできたらな~と思います。







DFセットアップ室では、リギングアーティストを大募集中です。
一緒に魅力的な作品を作っていきましょう!
詳しくは、下記の募集要項をご覧ください。

http://www.dfx.co.jp/recruit/application/cg/#recruit_1240




※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

MotionBuilderのPythonスクリプトを始めたい方へ2(UI初級編)

$
0
0

こんにちは、TD室の森本です。

前回から結構期間が経っていますが
MotionBuilder(以下MB)でPythonスクリプトをやってみたいという方に少しは役に立っているでしょうか?
簡単な処理のPythonスクリプトが使えるようになった次のステップとして
今回はMBで実装されているUIモジュールを使って簡単なUIを作ろうと思います

今回の紹介しているPythonスクリプトについてはMB2015で動作を確認しています
MBのバージョンによって若干クラス名が違うこともありますので
別バージョンで動作しない場合はエラー箇所のクラス名を確認して書き換えてください

まずは簡単なウィンドウをつくる

それでは始めますがまずは最初にMBのUIモジュールを読み込みます
[python]
from pyfbsdk_additions import*
[/python]

ただし実際にはUIでボタンを押しりしたときにMB上でなにか動作させるために
通常のPythonモジュールも必要になってくるので

[python]
from pyfbsdk import*
from pyfbsdk_additions import*
[/python]
となります

まずは単純にWindowを1つ出しましょう

[python]
from pyfbsdk import*
from pyfbsdk_additions import*

TestWindow=FBCreateUniqueTool(“TestWindow”)
ShowTool(TestWindow)
[/python]

これをMB上のPythonEditorで実行するとTestWindowという名前のウィンドウができあがりました。
次はウィンドウにボタンを追加しますがここでUIの配置のやり方について以下のようなやり方があります。

①Regionを設定して配置していく方法

UIに対してRegion(領域)を設定してそのRegionに対して新たにUIを配置していく方法です

[python]
# *- coding: utf-8 -*-
from pyfbsdk import*
from pyfbsdk_additions import*

# Topウィンドウを作る
TestWindow=FBCreateUniqueTool(“TestWindow”)

# TopウィンドウにBtnLytという名前の領域を設定する
x = FBAddRegionParam(10, FBAttachType.kFBAttachNone, “”)
y = FBAddRegionParam(20, FBAttachType.kFBAttachNone, “”)
w = FBAddRegionParam(50,FBAttachType.kFBAttachNone,”")
h = FBAddRegionParam(30,FBAttachType.kFBAttachNone,”")
TestWindow.AddRegion(“BtnLyt”,”BtnLyt”, x, y, w, h)

# ボタンを作ってTopウィンドウのBtnLyt領域にボタンのUIを設定する
TestBtn=FBButton()
TestWindow.SetControl(“BtnLyt”,TestBtn)

# Topウィンドウ表示時のウィンドウサイズの指定
TestWindow.StartSizeX = 200
TestWindow.StartSizeY = 200

# Topウィンドウを表示
ShowTool(TestWindow)
[/python]

以上のスクリプトを実行するとボタンが配置されたウィンドウが立ち上がります。
Topウィンドウに縦、横、高さ、幅を指定してRegionを設定してボタンを登録します。

RegionLayout 1

ここで縦、横などを指定するときにFBAttachType.kFBAttachNoneという記述がありますが
このAttachTypeを切り替えることで色々なRegionの指定ができます。
FBAttachType.kFBAttachNone 指定なし、数値がそのまま縦、横などの数値になる
FBAttachType.kFBAttachBottom 底からのアタッチ、数値が底からみた数値として配置される
FBAttachType.kFBAttachRight 右側からのアタッチ、数値が右側からみた数値として配置される
などがあります。
これらのFBAttachTypeを切り替えることでウィンドウのサイズを変更したときに変更に合わせて
UIの配置も変わる配置を作れます。

RegionLayout AttachType

②Layoutクラス(例:BoxLayout)を使用して配置していく方法

複数のUIを配置する際に一つ一つRegionを指定して配置するのが面倒な場合に
Layoutクラスを使用して簡単にUIを整列させた状態で並べることが出来ます。
ここでは縦方向に並べるLayoutであるFBVBoxLayoutについて説明します。

[python]
# *- coding: utf-8 -*-
from pyfbsdk import*
from pyfbsdk_additions import*

# Topウィンドウを作る
TestWindow=FBCreateUniqueTool(“TestWindow”)

# VBoxLayoutをつくってTopウィンドウに配置する
VBoxLyt=FBVBoxLayout()

x = FBAddRegionParam(0, FBAttachType.kFBAttachLeft, “”)
y = FBAddRegionParam(0, FBAttachType.kFBAttachTop, “”)
w = FBAddRegionParam(0,FBAttachType.kFBAttachRight,”")
h = FBAddRegionParam(0,FBAttachType.kFBAttachBottom,”")
TestWindow.AddRegion(“VBoxLyt”,”VBoxLyt”, x, y, w, h)
TestWindow.SetControl(“VBoxLyt”,VBoxLyt)

# ボタン(A)をつくってVBoxLayoutに登録する
ABtn=FBButton()
ABtn.Caption=”A”
VBoxLyt.Add(ABtn,30)

# ボタン(B)をつくってVBoxLayoutに登録する
BBtn=FBButton()
BBtn.Caption=”B”
VBoxLyt.Add(BBtn,50)

# Topレイアウトを表示
TestWindow.StartSizeX = 200
TestWindow.StartSizeY = 200
ShowTool(TestWindow)
[/python]

上記スクリプトの記述では幅を指定して配置していますが
AddRelativeメソッドを使用することでBoxLayout内で設定された幅に合わせて
追加したUIの幅が調整されます
BoxLayout

今回は例としてFBVBoxLayoutを使用しましたが他にも横方向のFBHBoxLayoutやグリッド配置のFBGridLayout等があり
Layout関係のクラスの使い方はMB内のSampleスクリプトがあるのでそちらも参考にしてください

ボタンにイベントを追加

これでUIにボタンを配置することが出来ましたがこのままではボタンを押しても何も反応しません。
そこでボタンにイベントを追加していきましょう

最初のRegionを使って一つのボタンを配置したPythonスクリプトに
ボタンを押したときにメッセージボックスを出すイベントを追加します
[python]
# *- coding: utf-8 -*-
from pyfbsdk import*
from pyfbsdk_additions import*

# ボタンを押したときのイベント時に実行される関数の設定
def BtnOnClick(control,event):
FBMessageBox(“BtnClick!”,control.Caption,”OK”)

# Topウィンドウを作る
TestWindow=FBCreateUniqueTool(“TestWindow”)

# TopウィンドウにBtnLytという名前の領域を設定する
x = FBAddRegionParam(10, FBAttachType.kFBAttachNone, “”)
y = FBAddRegionParam(20, FBAttachType.kFBAttachNone, “”)
w = FBAddRegionParam(50,FBAttachType.kFBAttachNone,”")
h = FBAddRegionParam(30,FBAttachType.kFBAttachNone,”")
TestWindow.AddRegion(“BtnLyt”,”BtnLyt”, x, y, w, h)

# ボタンを作ってTopウィンドウのBtnLyt領域にボタンのUIを設定する
TestBtn=FBButton()
TestBtn.Caption=”Test”
TestWindow.SetControl(“BtnLyt”,TestBtn)

# ボタンを押したときにイベント(BtnOnClickを実行)を設定
TestBtn.OnClick.Add(BtnOnClick)

# Topウィンドウ表示時のウィンドウサイズの指定
TestWindow.StartSizeX = 200
TestWindow.StartSizeY = 200

# Topウィンドウを表示
ShowTool(TestWindow)

[/python]
上記スクリプトを実行して出来たUIでボタン押すとメッセージボックスがでてきます

25行目の
TestBtn.OnClick.Add(BtnOnClick)
でクリックした際にBtnOnClick関数が実行されるように設定しています

BtnOnClick関数に関しては6行目の
def BtnOnClick(control,event):
で設定していますが
UIのイベントに設定した関数はcontrolとeventの引数をもっています

controlに関してはイベントを起こすキーになったUIのオブジェクト
(ここではボタンを押すというイベントを設定した元のUIである「TestBtn(FBButtonクラス)」が格納されています)
eventにはイベント自体の内容が格納されています
イベントに関しては今回のボタンだと押すだけなので大して意味はないですが
例えばFBSpread(スプレッドシートのUI)を使ったイベントの場合はイベントの発生源の行や列の値をとったりできます)

今回はイベント発生時の内容として7行目の
FBMessageBox(“BtnClick!”,control.Caption,”OK”)
においてボタンを作ったときに付けたボタンの表示名(TestBtn.Caption=”Test”)を
ボタンを押した際にcontrol.Captionとして取り出して表示しています

実践的なスクリプトサンプル

ということで一通りUIでボタンをつくってイベントを設定するところまで説明しましたが
これらを使ってもう少し実践で使えるようなスクリプトを作ってみます

[python]

# *- coding: utf-8 -*-
from pyfbsdk import*
from pyfbsdk_additions import*

import os

# =========================================================
# スクリプト置いてあるフォルダー
ScriptFolder=r”C:\MBScript”
# =========================================================

# スクリプトリストの辞書をつくる(Key:スクリプトファイル名、Value:スクリプトファイルのPath)
dScriptList={}
for oFile in os.listdir(ScriptFolder):
if oFile.endswith(“.py”):
dScriptList[oFile.replace(".py","")]=os.path.join(ScriptFolder,oFile)

# Event ===================================================
def ScriptBtnOnClick(control,event):
# ボタンの表示名(スクリプト名)からスクリプトリストの辞書をつかってスクリプトファイルのパスを取得
if control.Caption in dScriptList.keys():
oScriptPath=dScriptList[control.Caption]
# 取得したスクリプトファイルを実行
FBApplication().ExecuteScript(oScriptPath)

# UI ======================================================

# Topウィンドウを作る
RootWindow=FBCreateUniqueTool(“ScriptWindow”)

# VBoxLayoutをつくってTopウィンドウに配置する
VBoxLyt=FBVBoxLayout()

x = FBAddRegionParam(0, FBAttachType.kFBAttachLeft, “”)
y = FBAddRegionParam(0, FBAttachType.kFBAttachTop, “”)
w = FBAddRegionParam(0,FBAttachType.kFBAttachRight,”")
h = FBAddRegionParam(0,FBAttachType.kFBAttachBottom,”")
RootWindow.AddRegion(“VBoxLyt”,”VBoxLyt”, x, y, w, h)
RootWindow.SetControl(“VBoxLyt”,VBoxLyt)

# スクリプト名のリストを辞書からつくってスクリプト名のボタンを並べる
lScriptList=dScriptList.keys()
lScriptList.sort()
for oScript in lScriptList:
oUIBtn=FBButton()
oUIBtn.Caption=oScript
# 作ったボタンに押したときのイベントの設定を追加
oUIBtn.OnClick.Add(ScriptBtnOnClick)
VBoxLyt.AddRelative(oUIBtn)

# Topレイアウトを表示
RootWindow.StartSizeX = 100
RootWindow.StartSizeY = 300
ShowTool(RootWindow)

[/python]

このスクリプトを実行すると
9行目に設定したアドレスの中にあるpythonファイルをボタン名にした
UIウィンドウが立ち上がってボタンを押すとPythonファイルが実行されます

スクリプトの中身としてはフォルダの中からPythonファイルのリストをつくって
先ほどの説明のFBVBoxLayout()を使用して縦にリストのボタンを並べています
また、ボタンを押した際にボタンの名前からスクリプトのパスを取得して実行するイベントが設定されています

自分用のスクリプト一覧のランチャー等の使い方ができるので使ってみてください

まとめ

今回は前回の初心者向けの続きとしてMotionBuilderでのUI初級編として簡単なUIの作り方を説明しましたが
単純に少しボタンを配置したUIがつくれるだけでも組み合わせてスクリプトの利便性があがります

MotionBuilderにも様々なサンプルUIが用意されているので中身を見てもらって
参考にすればより様々なタイプのUIが組めるようになると思います

また、PySide等を使用するとより高度なUIがつくれますので
さらに興味が出た方はそちらを調べるのもいいかもしれません

以前にでたPyQTのMotionBuilderの記事です
*MotionBuilder2012のGUIはQtで作られてるのにスクリプトでPyQtが使えないっておかしくね?
http://www.dfx.co.jp/dftalk/?p=4018

UI以外の処理部分についても以前の記事も参考になるので是非どうぞ
*モーションビルダースクリプトTips
http://www.dfx.co.jp/dftalk/?p=4700

*MotionBuilderでツールをより使ってもらうために
http://www.dfx.co.jp/dftalk/?p=6539

*MotionBuilderをカスタマイズしよう
http://www.dfx.co.jp/dftalk/?p=7339

*MotionBuilderでScript!!便利なコンストレインの活用術!!(初級編)
http://www.dfx.co.jp/dftalk/?p=9135

*MotionBuilderのPythonスクリプトを始めたい方へ
http://www.dfx.co.jp/dftalk/?p=12696

今回はこの辺りで、ではまた。

.

※免責事項※
本記事内で公開している全ての手法の有用性、安全性について、当方は一切の保証を与えるものではありません。
これらの手法を実施したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください

Arnoldのちょいといい話!? part.2 -User dataについて-

$
0
0

ど~もお久しぶりです。
開発部ゴトー[@jackybian]です。
今年の5月は異常な暑さが続き夏みたいでしたね~。でもようやく東京も梅雨入りしましたね。
と書き始めて前々回の「Arnoldのちょっといい話!?」を見返したところ
ちょうどあの時も梅雨時期だったんですね~1年前か。。。早っ年々時間が経つのが早く感じますわ(゚д゚)!

この1年間もArnoldは着実にversionアップが繰り返され、細かな点も含めて色々進化してくれています。
HtoAも正式にリリースになりましたしね。という訳で今回もpart.2と題して、Arnold(MtoA)関連のお話をしようと思います。

実際にプロジェクトをやっていると通常shaderから取得できるデータ(normal,positionなど)以外にも色々なデータを取得してレンダリングしたい場合が出てきます。
例えば、deformerなどで計算されたsurfaceのtension情報などもその1つです。これをAOVとしてレンダリングするとか。
これ以外にも何か特殊なindexだったり、textureのパスだったりと何かしらの情報を埋め込んでレンダラーに渡したい場合があったりします。そこで今回は”MtoAにおけるUser-data”についてお話します。

Webで調べてみると、SolidAngle社など幾つかヒットするページはObject単位のdata付与の例が殆どでそれ以外はあまり載っていないようなのでまとめてお話できたらと思います。

MtoAでUser-dataを付与するにはMeshのShapeノードにUserAttributeを追加する必要があります。
こうすることでMtoAがこのAttributeを解釈してUser-dataがAss(Arnold Scene Source:scene description file)ファイルに埋め込まれるしくみです。
この埋め込まれたUser-dataは専用のshader(User-data Shader)で情報を取得することが可能になります。
では実際にどのような型や種類がサポートされ、どのようにAttributeを追加するのか?についてお話します。

Dataの型

こちらは標準でサポートされているUser-data Shaderをみると現在は以下の型が取得できます。

●string
●bool
●Int
●float
●vector
●point2(2次元ベクトル)
●color
※APIではこの他にもByte,Ptr,Matrixなどもサポートしているようです

Dataの種類

ここでいう種類というのは何に対するデータを付与できるのか?ということです。
APIリファレンスを見ると、
●constant — constant parameters are data that are defined on a per-object basis and do not vary across the surface of that object.
●uniform — uniform parameters are data that are defined on a “per-face” basis. During subdivision (if appropriate) values are not interpolated. Instead, the newly subdivided faces will contain the value of their “parent” face.
●varying — varying parameters are data that are defined on a per-vertex basis. During subdivision (if appropriate), the values at the new vertices are interpolated from the values at the old vertices. The user should only create parameters of “interpolatable” variable types (such as floats, colors, etc.)
●indexed — indexed parameters are data that are defined on a per-face-vertex basis. During subdivision (if appropriate), the values at the new vertices are interpolated from the values at the old vertices, preserving edges where values were not shared. The user should only create parameters of “interpolatable” variable types (such as floats, colors, etc.)
ということでObject単位、Face単位、Vertex単位、FaceVertex単位でデータが格納できるようです。
※現在のところMtoAではindexedはサポートされていないようです

User-dataの付与

User-dataの付与は、
Shapeノードに対して”mtoa_”+Dataの種類+”_”+”parameter名”という規則に従いAttributeを追加します。
例えばObjectに対してvector型でparameter名:myVectorとしてAttribute追加する場合は下図のようになります。
追加されたAttributeに値(User-data)をセットすることでdataが付与されます。

ShaderによるData取得

①MeshにSurfaceShaderをアサイン。aiUserDataVectorノード作成。Vector Attr Nameに追加Attributeの”parameter名”を指定
②aiUserDataVector.outValure -> surfaceShader.outColorへコネクト
レンダーしてみるとAttributeに設定された値(User-data)でオブジェクトがレンダリングがされているかと思います。

ここまでが ”User-dataの付与~ShaderでのData取得~レンダリング” までの基本的な流れになります。

では次にObject単位ではなく、Face単位、Vertex単位のUser-dataの付与の場合はどうなるか?を紹介したいと思います。
Dataの種類としては前述の通り”uniform”, “varying”を使ってAttribute追加を行うことになりますが、この場合Data対象がFaceやVertexになるので格納するDataも配列型になります。
この場合の”Attributeの追加~User-dataのセット”を見ていこうと思います。
※mayaの配列型としては、stringArray, Int32Array, doubleArray, vectorArray, pointArrayあたりを使用します。
例えばFace単位(uniform)のvectorデータを付与する場合は、以下のpythonスクリプトを実行してvectorArrayタイプでuniformアトリビュートを作成し、値をセットします。
[python]
import pymel.core as pm
pm.addAttr(‘pPlaneShape1′,longName=’mtoa_uniform_faceVector’, dt=’vectorArray’)
pm.setAttr(‘pPlaneShape1.mtoa_uniform_faceVector’, [[0.3,0.2,0.1],[0.5,0.4,0.3],[0.7,0.6,0.5],[0.9,0.8,0.7],[1,1,1]])
[/python]
先程同様aiUserDataVectorノードのVector Attr Nameに”faceVector”を指定してレンダリングしてみると

Face毎のUser-dataが取得されてレンダリング出来ていると思います。実際にこのMeshをASSファイルとして出力してみると

[python]
polymesh
{
name pPlaneShape1
・・・・・・・・・・・・
・・途中省略・・・・・・
・・・・・・・・・・・・
declare mtoa_shading_groups constant ARRAY NODE
mtoa_shading_groups “aiUtility2SG”
declare faceVector uniform VECTOR
faceVector 5 1 VECTOR
0.300000012 0.200000003 0.100000001 0.5 0.400000006 0.300000012 0.699999988 0.600000024 0.5 0.899999976 0.800000012 0.699999988 1 1 1
}
[/python]
Mesh情報の中にVECTOR型のUser-dataが正しく付与されていることが確認出来ると思います。
前述の通りVector以外にも色々な型のUser-dataが付与できるので、Data型, Dataの種類などを指定してAttributeを追加する関数をつくりテストしてみました。※numpy使ってますのでご注意を!
[python]
import pymel.core as pm
import numpy.random as nr, random, string

def addMtoaAttr(target, attrName, attrType, dataType):
shape = pm.listRelatives(target, children=True, shapes=True)[0]
dAtype = {‘obj’:'constant’, ‘face’:'uniform’,'vertex’:'varying’}
dDtype = {‘str’:'string’, ‘strArray’:'stringArray’,
‘int’:'long’,'intArray’:'Int32Array’,
‘float’:'double’,'floatArray’:'doubleArray’,
‘vector’:'double3′,’vectorArray’:'pointArray’,
‘color’:'float3′,
‘float2′:’double2′}
dAsize = {‘obj’:1}
dAsize.update(pm.polyEvaluate(target, v=True, f=True))
comp_size = dAsize[attrType]
attrType = dAtype[attrType]
dataType = dDtype[dataType]
attrName = ‘mtoa_’ + attrType + ‘_’ + attrName

if pm.attributeQuery(attrName, node=shape, exists=True) == False:
if dataType in ['long','double']:
pm.addAttr(shape,longName=attrName, at=dataType)
elif dataType == ‘double3′:
pm.addAttr(shape,longName=attrName, at=dataType)
for elm in ['x','y','z']:
pm.addAttr(shape,longName=elm,attributeType=’double’,parent=attrName)
elif dataType == ‘float3′:
pm.addAttr(shape,longName=attrName, uac=True, at=dataType)
for elm in ['r','g','b']:
pm.addAttr(shape,longName=elm,attributeType=’float’,parent=attrName)
elif dataType == ‘double2′:
pm.addAttr(shape,longName=attrName, at=dataType)
for elm in ['X','Y']:
pm.addAttr(shape,longName=elm,attributeType=’double’,parent=attrName)
else:
pm.addAttr(shape,longName=attrName, dt=dataType)

return [shape, attrName, dataType, comp_size]

def createData(dataType, dataSize):
if ‘Array’ in dataType: vData = []
for i in xrange(dataSize):
if dataType == ‘string’:
vData = ”.join([random.choice(string.ascii_letters + string.digits) for i in range(8)])
elif dataType == ‘stringArray’:
vData.append( ”.join([random.choice(string.ascii_letters + string.digits) for i in range(8)]) )
elif dataType == ‘long’:
vData = nr.randint(0,100)
elif dataType == ‘Int32Array’:
vData.append( nr.randint(0,100) )
elif dataType == ‘double’:
vData = nr.rand(1)[0]
elif dataType == ‘doubleArray’:
vData.append( nr.rand(1)[0] )
elif dataType in ['double3','float3']:
vData = nr.rand(3)
elif dataType == ‘double2′:
vData = nr.rand(2)
elif dataType == ‘pointArray’:
vData.append( nr.rand(3) )

return vData

## obj:string
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’objStr’,'obj’,'str’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(pm.getAttr(‘pPlaneShape1.mtoa_constant_objStr’))

## face:stringArray
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’faceStr’,'face’,'strArray’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(map(str,pm.getAttr(‘pPlaneShape1.mtoa_uniform_faceStr’)))

## vertex:stringArray
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’vertexStr’,'vertex’,'strArray’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(map(str,pm.getAttr(‘pPlaneShape1.mtoa_varying_vertexStr’)))

## obj:int
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’objInt’,'obj’,'int’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(pm.getAttr(‘pPlaneShape1.mtoa_constant_objInt’))

## face:intArray
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’faceInt’,'face’,'intArray’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(map(str,pm.getAttr(‘pPlaneShape1.mtoa_uniform_faceInt’)))

## vertex:intArray
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’vertexInt’,'vertex’,'intArray’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(map(str,pm.getAttr(‘pPlaneShape1.mtoa_varying_vertexInt’)))

## obj:float
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’objFloat’,'obj’,'float’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(pm.getAttr(‘pPlaneShape1.mtoa_constant_objFloat’))

## face:floatArray
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’faceFloat’,'face’,'floatArray’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(map(str,pm.getAttr(‘pPlaneShape1.mtoa_uniform_faceFloat’)))

## vertex:floatArray
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’vertexFloat’,'vertex’,'floatArray’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(map(str,pm.getAttr(‘pPlaneShape1.mtoa_varying_vertexFloat’)))

## obj:vector
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’objVector’,'obj’,'vector’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(pm.getAttr(‘pPlaneShape1.mtoa_constant_objVector’))

## face:vectorArray
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’faceVector’,'face’,'vectorArray’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(map(str,pm.getAttr(‘pPlaneShape1.mtoa_uniform_faceVector’)))

## vertex:vectorArray
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’vertexVector’,'vertex’,'vectorArray’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(map(str,pm.getAttr(‘pPlaneShape1.mtoa_varying_vertexVector’)))

## obj:color
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’objColor’,'obj’,'color’)
dVal = createData(dType, dSize)
pm.setAttr(tName + ‘.’ + aName, dVal)
print(pm.getAttr(‘pPlaneShape1.mtoa_constant_objColor’))

## obj:pnt2
tName,aName,dType,dSize = addMtoaAttr(‘pPlane1′,’objUv’,'obj’,'float2′)
dVal = createData(dType, dSize)
print dVal
pm.setAttr(tName + ‘.’ + aName, dVal)
print(pm.getAttr(‘pPlaneShape1.mtoa_constant_objUv’))
[/python]
以下はVertex単位のVectorデータを付与してレンダリングした例

この結果、pointArray型はASSファイルではVECTOR型として付与されるようです。

こんな感じでいろんな型のUser-dataを埋め込み、Shaderでピックアップすることが出来るので考え方次第ではとても役立つ場面があるのではと思います。興味のある方はぜひ利用してみてください。
次回はこの派生型利用法が紹介できたらと思っています。
という訳でお時間がきたようなので今回はこのあたりで。では(´∀`*)ノ マタ~

※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください

≪セットアップ室≫エクスプレッションを用いた車のセットアップ

$
0
0
初めまして。デジタルフロンティアCG部セットアップ室の神田です。

さて、弊社デジタルフロンティア(以下DF)のセットアップ室の主な業務は、Autodesk Mayaおよび
MotionBuilderでのキャラクターセットアップになります。弊社のセットアップ室はクロス、ヘアーの
仕込みからシミュレーションも担当します。地味に思われがちなセットアップ業務だけでなく、
最終的な絵作りにも関わることができるのはDFのメリットかなと感じます。
その分覚えることも多いですが、やりがいのある仕事です!



今回はエクスプレッションを用いた車のセットアップについて紹介したいと思います。
エクスプレッションとは、キャラクターセットアップやアニメーションの反復動作の効率化する為に使用されるMayaの機能です。DFのセットアップ室でも、特に人体セットアップ作業では使用頻度が高くなっています。そのほとんどは社内のツールで自動化されているので、全てを1から書くということはありませんが、セットアップでは必須のスキルですね。
パーティクルの制御にも用いられますが、今回はどちらかというとセットアップ寄りなので、ご了承ください。
エクスプレッションのような「文字」で作業することは始めはハードルが高く感じるかも。MelやPythonといったスクリプトの習得をしたいなーと、考えている方は、エクスプレッションから入ってみるのもいいかもしれませんよ。
簡単な数式で、アニメーションが作れたりとMayaの世界をより快適なものにしてくれるはずです。
 
 

エクスプレッションを用いた車のセットアップ:アジェンダ

1.タイヤの制御その1:コントローラーとの関連付け
2.タイヤの制御その2:距離に応じたタイヤの回転エクスプレッション
3.インタラクティブピボット:バンク(傾き)のコントロール
4.エクスプレッションの制御テクニック:WeightとOffset
5.おまけ:旋回の中心点を取得しよう
6.エクスプレッションのメリット/デメリット
7.まとめ


※動画のMayaのバージョンは2014を使用しています。
 

1.タイヤの制御その1:コントローラーとの関連付け

expression editor

expression editor

■エクスプレッション エディタ

エクプレッションを作成する時はメニューのウィンドウ>アニメーション エディタ>エクスプレッション エディタから起動します。
各項目は画像の通りです。エクスプレッションをメインで作業するときはフィルタの選択をエクプレッション名にしておくと現在の選択したノードに関わらずエクスプレッションが切り替わらないので、作業しやすいと思います。
 
 
 
 

■エクプレッションの使い方

エクスプレッションの一番基本的な使い方から始めましょう。一般的に↓この形から派生していきます。

ノード名.アトリビュート名 = ノード名.アトリビュート名;
 

car wheel expression : コントローラー名

car wheel expression : コントローラー名

実際に書いてみると…
 
// —- front wheel
L_front_wheel_rot.rotateY = front_wheel_rot_ctrl.rotateY;
R_front_wheel_rot.rotateY = front_wheel_rot_ctrl.rotateY;

// —- wheel rotation
L_front_wheelJT.rotateX = L_front_wheel_ctrl.rotateX;
R_front_wheelJT.rotateX = R_front_wheel_ctrl.rotateX;
L_rear_wheelJT.rotateX = L_rear_wheel_ctrl.rotateX;
R_rear_wheelJT.rotateX = R_rear_wheel_ctrl.rotateX;

という感じです。
 

ここで既に頭が痛い方いませんか?アルファベットや数字、演算子(=や+ーの記号)に惑わされないようにしましょう。
右辺の内容(計算結果)が左辺に代入されるといった意味合いになります。きちんと意味を理解すれば大して難しいことは
書いていないことがすぐにわかると思います。行の最後は ; (セミコロン)で締めるのを忘れずに!

エクスプレッション内では // でコメントを書くことができます。計算には無視される文章ですね。
慣れないうちは説明文を残しておいて、後で確認できるようにしておくといいでしょう。
他人の方が引き継いだ時にも分かりやすくしておくと尚良いです。
 
 

2.タイヤの制御その2:距離に応じたタイヤの回転エクスプレッション

車の進んだ距離に応じてタイヤが自動で回転するエクスプレッションを書いていきます。ちょっと頭を使うところです。
エクスプレッションを書き始める前に計算式を考え、扱う情報を整理しましょう。

タイヤの円周と回転量について

タイヤの円周と回転量について

タイヤの円周 × タイヤの回転数 = 車の前進した距離

とします。タイヤの円周を右辺に持って来て、

タイヤの回転数 = 車の前進した距離 / タイヤの円周

となります。円の円周の公式は直径×円周率なので、

タイヤの回転数 = 車の前進した距離 / (タイヤの直径 × 3.14)

となります。
 

タイヤの回転を制御するのですが、Mayaの中では「タイヤが何回転するか」というよりも
「タイヤのRotate値はいくつになるか」というように考えますね。1回転は360°なので、
rotateXの1°当たりに進む距離を導き出すには、

タイヤの回転量(rotateX) = (車の前進した距離 / (タイヤの直径 × 3.14)) × 360

となります。実際のエクスプレッションで用いるアトリビュート名と記号で書いてみると、

// —- wheel rotation by distance
L_front_wheel_exp.rotateX = (all_ctrl.translateZ / (2*3.14))*360;
R_front_wheel_exp.rotateX = (all_ctrl.translateZ / (2*3.14))*360;
L_rear_wheel_exp.rotateX = (all_ctrl.translateZ / (2*3.14))*360;
R_rear_wheel_exp.rotateX = (all_ctrl.translateZ / (2*3.14))*360;

となります。all_ctrlを前後に動かしてみるとタイヤが自動で回転していますね。後退すると逆回転になります。
 
 

3.インタラクティブピボット:バンク(傾き)のコントロール

どんどんいきましょう。続いて車の傾きを制御してみましょう。
まずは、車がどの方向に傾くか監視するbank_pivot_directionというロケータに、bank_ctrlのrotate値を代入します。

// —- bank pivot direction
bank_pivot_direction.translateX = bank_ctrl.rotateZ * -1;
bank_pivot_direction.translateZ = bank_ctrl.rotateX;

傾いた方向にロケータが移動するようになりました。

次にnearestPointOnCurveノードを使用します。nearestPointOnCurveノードはある一つのオブジェクトの位置からそのカーブの最も近いポイントの位置情報を取得できるノードです。pivot_curveShapeのworldSpace[0]とnearestPointOnCurve1のinputCurveにコネクションします。
 
 
続いて下記の様にエクプレッションを作成します。
vector $vはベクター型と呼ばれる変数です。三つの値をセットにして扱える型ですね。
.x.y.zでリストと同じように各要素を取り出すことができます。

// —– Nearest Point On Curve
vector $v = <<bank_pivot_direction.translateX, 0, bank_pivot_direction.translateZ>>;
nearestPointOnCurve.inPositionX = $v.x;
nearestPointOnCurve.inPositionY = $v.y;
nearestPointOnCurve.inPositionZ = $v.z;

nearestPointOnCurve.result.positionをbank_rotのrotatePivotに繋ぎます。
※bank_pivot.translateにも繋いでおくと位置を確認できます。

バンク(傾き)コントローラー名

バンク(傾き)コントローラー名

nearestPointOnCurve:ノードコネクション

nearestPointOnCurve:ノードコネクション

 
 
 

4.エクスプレッションの制御テクニック:WeightとOffset

エクスプレッションはいろいろ自動化できて非常に便利です。しかし、常に自動化された結果が正しいとは限りません。
演出で、エクスプレッションをOFFにして手付けでアニメーションを行いたいといった場面もでてきます。
そんな時に便利なので、ウェイトとオフセットという追加のアトリビュートです。
ウェイトはエクスプレッションの影響をどれだけ反映させるか調整できるアトリビュートで、
オフセットはエクスプレッションの結果に任意で上乗せ(または差し引く)ことができるアトリビュートになります。

2.の距離に応じたタイヤの回転エクスプレッションを例にとってみます。
各タイヤのコントローラーに Rot OffsetExp Weight というアトリビュートを追加しました。
エクスプレッションを修正します。

// —- wheel rotation by distance : declare variables
float $LFOffset = L_front_wheel_ctrl.rotOffset;
float $RFOffset = R_front_wheel_ctrl.rotOffset;
float $LROffset = L_rear_wheel_ctrl.rotOffset;
float $RROffset = R_rear_wheel_ctrl.rotOffset;

float $LFWeight = L_front_wheel_ctrl.expWeight;
float $RFWeight = R_front_wheel_ctrl.expWeight;
float $LRWeight = L_rear_wheel_ctrl.expWeight;
float $RRWeight = R_rear_wheel_ctrl.expWeight;

// —- wheel rotation by distance
L_front_wheel_exp.rotateX = ((all_ctrl.translateZ / (2*3.14))*360) * $LFWeight + $LFOffset;
R_front_wheel_exp.rotateX = ((all_ctrl.translateZ / (2*3.14))*360) * $RFWeight + $RFOffset;
L_rear_wheel_exp.rotateX = ((all_ctrl.translateZ / (2*3.14))*360) * $LRWeight + $LROffset;
R_rear_wheel_exp.rotateX = ((all_ctrl.translateZ / (2*3.14))*360) * $RRWeight + $RROffset;
 
 
始めに追加した8つのfloat型の変数は各コントローラーからのアトリビュート値を代入しているだけです。
続くエクスプレッションが長く読みづらくなってしまうのを避けるためですが、気にならない方は

L_front_wheel_exp.rotateX = ((all_ctrl.translateZ / (2*3.14))*360) * L_front_wheel_ctrl.expWeight + L_front_wheel_ctrl.rotOffset;
R_front_wheel_exp.rotateX = ((all_ctrl.translateZ / (2*3.14))*360) * R_front_wheel_ctrl.expWeight + L_front_wheel_ctrl.rotOffset;
L_rear_wheel_exp.rotateX = ((all_ctrl.translateZ / (2*3.14))*360) * L_rear_wheel_ctrl.expWeight + L_rear_wheel_ctrl.rotOffset;
R_rear_wheel_exp.rotateX = ((all_ctrl.translateZ / (2*3.14))*360) * R_rear_wheel_ctrl.expWeight + R_rear_wheel_ctrl.rotOffset;

 
の4行でもOKです。
 
 

進行方向とタイヤの自動回転の向き

進行方向とタイヤの自動回転の向き


■all_ctrlの移動値と回転値の分割

all_ctrlのtranslateZの値をエクスプレッションで取得していると、all_ctrlが回転したとき進行方向がZ方向でない場合、計算結果異なり、予期せぬ方向にタイヤが回転を始めてしまいます。そこで回転だけのTransformノード(all_rot_cnst)と移動だけのTransformノード(all_pos_cnst)を用意し、ペアレント化を行います。それぞれにall_ctrlからOrient Constrain、Point Constrainを設定し、all_pos_cnstから移動値を取得すると回転にも対応したセットアップになります。
 
 
// —- wheel rotation by distance
L_front_wheel_exp.rotateX = ((all_pos_cnst.translateZ / (2*3.14))*360) * $LFWeight + $LFOffset;
R_front_wheel_exp.rotateX = ((all_pos_cnst.translateZ / (2*3.14))*360) * $RFWeight + $RFOffset;
L_rear_wheel_exp.rotateX = ((all_pos_cnst.translateZ / (2*3.14))*360) * $LRWeight + $LROffset;
R_rear_wheel_exp.rotateX = ((all_pos_cnst.translateZ / (2*3.14))*360) * $RRWeight + $RROffset;

 
 
 

5.おまけ:旋回の中心点を取得しよう

今回時間の関係でリグに組み込めなかったのですが、車の旋回の中心点を取得してみたいと思います。
一般的な乗用車であればハンドルを切ると前輪が傾き、車が左右に曲がることが可能です。そのタイヤの角度によって回転運動の基準軸を取得したいと思います。タイヤの角度と進む向きのギャップ(スリップアングル等というらしいですが。)などの要素は今回は考慮に入れず、単純に後輪軸の延長したラインと、前輪の中心から傾きに直行したラインの延長が交わる点、且つ、車体の中心(重心)から同じ距離横方向に伸ばした位置を旋回の軸としました。文字での説明よりも詳しくは図を参照してみてください。

前輪の角度と旋回の中心点

前輪の角度と旋回の中心点

エクスプレッションを書く前にここでも先に情報の整理です。
前輪の傾きによって、前輪の中心、後輪の中心、そして上で定義したラインが交わる点で直角三角形ができます。
ここで不変の距離は、前輪の中心から後輪の中心までの距離なので、三角関数を用いて旋回の中心点を導きだすことができます。
 
 
←はGIFアニメーションです。クリックして下さい。
 
 
 

旋回の中心点までの距離 = 前輪の中心と後輪の中心の距離 × tan(※前輪の傾き)
※前輪の傾きはラジアンで指定する必要があります。

となります。実際のエクスプレッションでは

turn_pivot.translateX = 8 * tan(deg_to_rad(90 – front_wheel_rot_ctrl.rotateY));

となり、90 – front_wheel_rot_ctrl.rotateYで三角関数で用いられるθの部分の角度になります。
エクスプレッションのtan()関数では角度の単位にラジアンを用いる必要があるので、deg_to_rad()という
°(度)をラジアンに変換する関数があるので、こちらを使用しました。

※°(度)で計算できるtand()という関数もあります。その場合は
turn_pivot.translateX = 8 * tand(90 – front_wheel_rot_ctrl.rotateY);

となります。

ここで、括弧内が0°だった場合ですが、tan()関数は傾きなしになってしまい、turn_pivotは無限に遠くにいってしまいます。if文の条件式を使用して、傾きなしの場合は元の初期値に戻ってくるようにしましょう。
 
 
// —- Turn pivot position
if(front_wheel_rot_ctrl.rotateY == 0){
    turn_pivot.translateX = 0;
}else{
    turn_pivot.translateX = 8*tan(deg_to_rad(90-front_wheel_rot_ctrl.rotateY));
}
 
 
front_wheel_rot_ctrl.rotateY == 0 の場合 turn_pivot.translateX = 0 になります。
それ以外の場合は先に定義した式になります。
turn_pivot.translateX = 8*tan(deg_to_rad(90-front_wheel_rot_ctrl.rotateY));
 
 

6.エクスプレッションのメリット/デメリット

■エクスプレッションのメリット

 
・時間変化を利用したアニメーションの作成に便利
・反復、分岐の処理か容易
・テキストデータの為、作業の蓄積・共有が容易
・多くのアトリビュートにアクセスする場合や、複雑な処理もノード一つで可能
・アドリブが利きやすい

ドリブンキーやコンストレイン、ノードコネクションによる操作をエクスプレッションに置き換えることも可能です。
複数のノードを一つのエクスプレッションにまとめ、結果的にシーンを軽くすることもできるかもしれません。
また、文字での操作なので、作成したエクスプレッションは資産として残して再利用することも可能です。
不自由なく使える様になれば、アーティストの考えたものを直感的に作成できるパワフルな方法の一つになります。

個人的には最後のアドリブが利きやすいというのが大事ですね。仮でセットアップする際にエクスプレッションで
書いてみて確認する時に使いやすく感じています。思った方向に行かなかったときには-1を掛けてしまえとか。
スピーディーに対応できるところなどはメリットだと思います。
 
 

■エクスプレッションのデメリット

MotionBuilderに持っていく事ができません。MotionBuilderの中ではRelationという機能を用いて代用します。
DF内ではMotionBuilderを使用する機会が多いので、自動化されていない部分(自分で追加したエクスプレッションなど)はMotionBuilder内で再現する為の作業が発生してしまう場合があります。

アトリビュートをいくつでも纏めて管理できるので、逆にシーンを重くしてしまうことも。Alwaysの評価の方法は毎フレーム、エクスプレッションを計算します。その結果、必要の無い計算がボトルネックになってしまい、再生パフォーマンスが落ちてしまうことがあります。その場合はエクスプレッションノードを複数分けたり、代替の機能を検討する必要があるかもしれません。場合によっては評価の頻度をOn demandにしてアトリビュートにアクセスした場合のみ反映されるようにするのもいいかもしれません。
 
 

■エクスプレッションで記述する形

アトリビュートの値へのアクセスは = を使い、アトリビュートに直接代入することが推奨されています。
 
float $x = `getAttr pSphere1.translateX`;   // getAttr コマンドでpSphere1のtranslateXの値を取得
setAttr pCube.translateX $x;   // setAttr コマンドでpCubeのtranslateXの値に代入
 
のように、エクスプレッション内でMelコマンドを使用して値を取得代入が可能ですが、上記の内容なら
= を使って↓のように書くほうが処理が速くなります。
 
pCube.translateX = pSphere1.translateX;
 
 
Melコマンドを使う場合:単純なアトリビュート値の操作では難しい表現をする時に使用します。
時間によってオブジェクトが軌跡を追従する場合など、Melコマンド独自の機能を使って制御できます。
 
// pCube0のアニメーションをpCube1~pCube9が1フレームずつ遅れて追従するエクスプレッション
for($i=1; $i<9; $i++){
    float $valT[] = `getAttr -t (frame - $i) pCube0.translate`;
    float $valR[] = `getAttr -t (frame - $i) pCube0.rotate`;
    float $valS[] = `getAttr -t (frame - $i) pCube0.scale`;
    setAttr ("pCube" + $i + ".translate") $valT[0] $valT[1] $valT[2];
    setAttr ("pCube" + $i + ".rotate") $valR[0] $valR[1] $valR[2];
    setAttr ("pCube" + $i + ".scale") $valS[0] $valS[1] $valS[2];
}
 
 
また、=で記述したオブジェクトとそのエクスプレッションノードの間にはノードコネクションが作成されます。
ノードコネクションが作成されてしまうこと避けたい場合(他のコネクションと競合してしまう場合など)、
代わりの方法としてMelコマンドを使用することができます。Melコマンドで制御する場合はエクスプレッションノードとの間にノードコネクションは作成されません。

 
 

■まとめ

車というモデルを使ってエクスプレッションの紹介をしてきました。本当に自由度の高い機能なので、使えば使うだけ、効率的な使い方ができるようになります。さらに、学校で習ったような数学や物理の知識があるとより一層高度な使い方ができるはずです。今回の車のセットアップはテキストだけでは理解しづらいかと思いますので、動画も是非見てみてください。皆さんの日々の制作の一助になれば幸いです。
 
 
 
 
デジタルフロンティア CG部セットアップ室は渋谷の桜丘町のオフィスにあります。飲食店が多く、近くにはさくら通りや西郷山公園といった桜が楽しめるスポットがあります。社内見学も随時受け付けていますので、興味をお持ちの方は是非ご連絡ください。

西郷山公園:会社でお花見

西郷山公園:会社でお花見

さくら通り

さくら通り

ミニ四駆部が発足!

ミニ四駆部が発足!

ミニ四駆部の活動

ミニ四駆部の活動


 
 
 
 
 
 
 
 
 
 
DFセットアップ室では、リギングアーティストを大募集中です。
一緒に魅力的な作品を作っていきましょう!詳しくは、下記の募集要項をご覧ください。

【 -セットアップ室-  リギングアーティスト】募集要項はこちら

 デジタルフロンティア お問い合わせフォームはこちら
 

※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

複数のシーンファイルを一括で処理する方法

$
0
0

どうもこんにちは、TD室の石田です。

今回の内容はMayaで複数のシーンファイルに対して一括処理を行う方法について書いてみました。
例えばシーンファイルが大量にある状況で全てのシーンファイルに対して
アトリビュートを一箇所変更したい。
いらないノードを消したい。追加したい。
レンダリングの設定をしたい。
などなどあると思います。
これを一つ一つ手動でやるには時間も掛かるし苦行ですよね。

弊社ではこういう自動処理を行うツールはいくつか存在しますが、今回はツールというほどのものではなく簡易的なものを作ってみました。
今回の内容ではPythonとbatファイルで構成されています。
サンプルもあるのであとでダウンロードしてみてください。

今回の手順

1、create.batに複数シーンファイルの入ったフォルダをDrag&Drop ※図ではrunFilesフォルダ
2、create.batが複数シーンファイルのパスをcreateBat.pyに渡してrun.batを作成
3、run.batを実行すれば自動処理が動き出す。
※ついでにログも出すようにしています。

1、のDrag&Dropした時点で自動処理が走ってもスムーズでいい気がしますが
run.batを作っておけば中身の確認と処理が編集出来るので今回はこの形で作ってみました。

create.batでrun.batを作る

ここからメインとなるDrag&Drop先のbatファイルとpyファイルの解説をしていきます。
create.bat    Drag&Dropしたパスを受け取りcreateBat.pyに渡す
createBat.py  create.batからパスを受け取りrun.batを作る。

—- create.bat —-

[cpp]
@echo off
set PYPATH=%~d0%~p0createBat.py
echo %PYPATH%

“C:\Program Files\Autodesk\Maya2014\bin\mayapy.exe” %PYPATH% %1

pause
[/cpp]

create.bat解説

[cpp]
set PYPATH=%~d0%~p0%PYTHONFILE%
[/cpp]
この記述はcreate.batと同じ階層のパスを取得してPYTHONFILEとくっ付けています。
例えばcreate.batがC:\Users\Documents\create.batにある場合
PYPATHはC:\Users\Documents\createBat.pyになります。

[cpp]
“C:\Program Files\Autodesk\Maya2014\bin\mayapy.exe” %PYPATH% %1
[/cpp]
mayapy.exeがなんなのかって感じだと思いますがmayaが用意しているpython.exeだと思ってください。
もちろんpython.exeでも大丈夫です。python.exeを用意するのが面倒なときはmayapyを使いましょう。

%1はDrag&Dropしたフォルダのパスが入ります。

この文を直訳してみます。
例えば
%1 = C:\Users\Documents\runFiles
PYPATH = C:\Users\Documents\createBat.py
とします。

訳すとこんな感じ。
“C:\Program Files\Autodesk\Maya2014\bin\mayapy.exe” C:\Users\Documents\createBat.py C:\Users\Documents\runFiles
runFilesをcreateBat.py渡して実行、と言うことになります。

—- createBat.py —-

[python]
# -*- coding: utf-8 -*-
import sys
import os

mayaexe = r’C:\Program Files\Autodesk\Maya2015\bin\maya.exe’
mayabatchexe = r’C:\Program Files\Autodesk\Maya2015\bin\mayabatch.exe’
pythonPath = r’C:\Users\Documents\python’ #pythonファイルが置かれているパス

def main():
srcDir = sys.argv[1]
file_list = listDirFiles(srcDir)

cmd = ‘SET MAYA_UI_LANGUAGE=en_US’+'\n’
#cmd += ‘SET MAYA_PLUG_IN_PATH=C:\dir’+'\n’
#cmd += ‘SET MAYA_MODULE_PATH=C:\dir’+'\n’
#cmd += ‘SET MAYA_SCRIPT_PATH=C:\dir’+'\n’
cmd += ‘SET PYTHONPATH=%s’%pythonPath+’\n’

# logを入れるフォルダを作成
check_dir(os.path.join(srcDir,’log’))

for fileName in file_list:
filePath = os.path.join(srcDir,fileName).replace(‘\\’,'/’)
root,ext = os.path.splitext(filePath)
logPath = os.path.join(srcDir,’log’,fileName).replace(ext,’.log’)

cmd += ‘\n’
cmd += ‘SET MAYA_CMD_FILE_OUTPUT=%s’%logPath + ‘\n’
cmd += ‘”%s” -command “python(\\”import runCmd;runCmd.run(\’%s\’)\\”)”‘%(mayaexe, filePath)+’\n’
cmd += ‘\n’

print cmd

dirPath, curPathName = os.path.split(srcDir)
path = os.path.join(dirPath,’run.bat’)
saveBat(path, cmd)

# コマンドの内容を書き込む
def saveBat(path,cmd):
with open(path, “w”) as file:
file.write(cmd)

# ディレクトリがなければ作成
def check_dir(path):
if os.path.isdir(path) == False:
os.makedirs(path)

# 指定されたフォルダからファイルのみのリストを取得
def listDirFiles(dirPath):
file_list = []
for fileName in os.listdir(dirPath):
filePath = os.path.join(dirPath,fileName)
if os.path.isfile(filePath):
file_list.append(fileName)
return file_list

if __name__ == ‘__main__’:
main()
[/python]

createBat.py解説

run.batを作成する処理です。
sys.argv[1]がDrag&Dropで渡されたパスが入ります。
ちなみに現状の設定だとmaya.exeで処理が走るようになっているのでUIがいらない処理であればmayabatch.exeにしてください
あとはほとんどがディレクトリ操作なので省略します。

—- runBat.py —-

[cpp]
SET MAYA_UI_LANGUAGE=en_US
SET PYTHONPATH=C:\Users\Documents\python

SET MAYA_CMD_FILE_OUTPUT=C:\project\autoBat\runFiles\log\cube.log
“C:\Program Files\Autodesk\Maya2014\bin\maya.exe” -command “python(\”import runCmd;runCmd.run(‘C:/project/autoBat/runFiles/cube.mb’)\”)”

SET MAYA_CMD_FILE_OUTPUT=C:\project\autoBat\runFiles\log\plane.log
“C:\Program Files\Autodesk\Maya2014\bin\maya.exe” -command “python(\”import runCmd;runCmd.run(‘C:/project/autoBat/runFiles/plane.mb’)\”)”

SET MAYA_CMD_FILE_OUTPUT=C:\project\autoBat\runFiles\log\sphere.log
“C:\Program Files\Autodesk\Maya2014\bin\maya.exe” -command “python(\”import runCmd;runCmd.run(‘C:/project/autoBat/runFiles/sphere.mb’)\”)”
[/cpp]

runBat.py解説

createBat.pyで作られたbatファイルです。
このbatファイルを実行すれば自動処理が動き出します。
ここでやっていることは順番にmaya.exeを立ち上げてscriptを実行。
これのみです。
途中で終了したい場合はコマンドプロンプトを閉じてから、mayaを閉じてください。

[cpp]
python(\”import runCmd;runCmd.run(‘C:/project/autoBat/runFiles/sphere.mb’)\”)
[/cpp]
ここの部分はPYTHONPATHに通っているパスのpythonが走ります。
pythonにしていますがもちろんmelでも大丈夫です。

今回の例では

—- runCmd.py —-

[python]
# -*- coding: utf-8 -*-

from maya import cmds

def run(filePath):

#maya シーンファイルのオープン
print ‘## Scene File Open >> ‘ + filePath
cmds.file(filePath, f=1, o=1)

#
# なにか適当に自動処理を書いてください。
#

#maya.exeで処理していた場合にmayaを終了させる。
if not cmds.about(batch=1):
cmds.evalDeferred(‘from maya import cmds;cmds.quit(f=1)’)
[/python]

こんな感じで書いています。

別名保存したいなどいろいろあるかと思いますが挑戦してみてください。
今回はMayaを例にしましたが中身を書き替えればMayaでなくても関係なく動きます。
これで時間がかかる単純処理は全部マシンに任せて時間を有効活用できますね!
それでは、また

ソースコードはこちら。→ダウンロード

※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください

Facial Delta Mush

$
0
0

Alô pessoas! 皆さん、こんにちは。
開発部のVladimirです。

今回は最近取り組んだ開発案件についてお話させて頂こうかと思います。
それは少し前に話題になっていたDelta Mushです。

初めて聞く人もいるかもしれないので簡単にご説明すると、Delta Mushは2010年にRythm&Hues社によって開発された
アルゴリズムで去年のSIGGRAPHで発表されたdeformer技術です。
そのシンプルかつパワフルな点からも世界中で多くの人々から注目されていたように思います。
ご存じの通り今年Maya2016にも搭載されましたね。
web上にはソース付きで公開されているものがあったりと独自実装をしている方もいるようです。
弊社では今回Facialアニメーション用に開発されました。なぜこれが必要になったのか?というと
blendshapeベースのFacialアニメーションをつけた際に表情によっては望まない凹凸が表れる場合があります。この不正な形状を修正する目的でDelta Mushが使用されました。
Delta Mushを説明するには基礎的な3D幾何学の知識が必要ですが出来るだけわかり易く進めていきたいと思います。

これぞ!Delta Mush!

キャラクターなどのリギングをされたことがあればskinningにおけるskin weightの重要性はご存知だと思います。
多様なポーズ変化に対して適切なデフォーム結果を得るためには非常に重要なデータになります。
一般的にはweightをPaintしていくことになりますが最近のDCCツールではbind時にある程度最適化されたweightが付くような
機能が盛り込まれています。しかし多くの場合それだけでは不十分でやはりpaintでの修正が必要となります。
この面倒なPaint作業をせずにsmoothなdeform結果を得るために使用されるのは代表的な例の1つと言えるでしょう。

Delta Mush Demo

ご覧の通りweightがあまり最適化されていない状態でもディテールを保持しつつ不要な凹凸、めり込みのないsmoothな結果が得られています。ではなぜ?これが可能なのか見てみいきましょう。

The Algorithm

アルゴリズムについてはSIGGRAPHのTalkにて公開されていますが
ここでは図を使いながら説明していこうと思います。

どのようにsmoothなdeform結果を得るか?を簡潔に言うならば
表面をスムーズ化することで不要な凹凸、めり込みをなくした後、元々のディテールを復元します。
処理の工程としては下記の二段階に分けられます。

事前計算
  1. undeformedポーズのメッシュがあるとします。それはReference poseと呼びます。
  2. Reference poseをスムーズ化したものが、 Smoothed Reference poseになります。
  3. 頂点毎の差分をローカル座標で計算し、結果を格納します。
実際の変形
  1. 変形されたメッシュのポーズがあるとします。それはDeformed poseと呼びます。
  2. Deformed poseをスムーズ化したものが, Smoothed Deformed pose になります。
  3. Smoothed Deformed poseに頂点毎の事前計算された差分をローカル座標で与えます

Delta Mushのアルゴリズムは以上です。まず”スムーズ化”、次に”差分の適応”という順で説明していきたいと思います。

Smoothing

スムーズ化によって不要な凹凸を減らして滑らかな表面に変化させることができます。 ただし全てのスムーズ手法がDelta Mushに有効という訳ではありません。 Delta Mushの論文を見ると、”modified discrete Laplacian”という手法が挙げられています。 一見難しそうですが、 実はそうでもないです。Laplacian Smoothingはvertexの近傍を平均してそこに移動するという方法です。 Iso-surfacesや実数の関数を使うなら積分を取るのですが、メッシュの場合は離散データを利用します。あるvertexに隣接するvertexの平均を取ってそこに移動します。これを全てのvertexに与えるとスムーズなメッシュが得られます。 下のコードはそのスムーズ化の実装例です。Smoothnessはスムーズ化の強さを表すパラメータとして挙げています。
[cpp]
void averageSmooth(MFloatPointArray &vertices, vector &neighbors, float smoothness)
{
int nPoints = vertices.length();
MFloatPointArray temporaryVertices;
temporaryVertices.setLength(nPoints);
for (int i = 0; i < nPoints; i++){
int nNeighbors = neighbors[i].size();
if (nNeighbors == 0)
temporaryVertices[i] = vertices[i];
else {
MFloatPoint ponto;
for (int j = 0; j < nNeighbors; j++)
ponto = ponto + vertices[neighbors[i][j]];
temporaryVertices[i] = ponto / nNeighbors;
}
}
for (int i = 0; i < nPoints; i++)
vertices[i] = vertices[i]*(1-smoothness) + smoothness*temporaryVertices[i];
}
[/cpp]

全てのLaplacian Smoothing手法を見て行くときりがないので1つ例を挙げるとすると、先程の平均化の段階で、edgeの長さの逆数をweightとして使い加重平均を取ることが出来ます。
そうするとmeshを全体的にスムーズ化しながらもedgeの長さを保持出来ます。

どの手法を用いるかはuser次第です。 Maya 2016のDelta Mushは普通の平均を使っていますがLaplacian Smoothing以外の手法を使うことも考えられます。
例えば弾性relaxationやmodified catmull subdivisionなども使えるでしょう。ここで重要なことはスムーズ化されたメッシュはオリジナルメッシュより内側に縮んだ形状でないといけません。Volumeを保持する手法を使うと期待するdeform結果が得られない場合があります。下の図ではいくつかのスムーズ手法を示しています。

私が試した感じではシンプルな手法であればあるほどいい結果をもたらすように思います。

Displacements(差分の適応)

次はスムーズ化されたメッシュに対してオリジナルメッシュとの差分の適応です。
任意の頂点P1における差分はローカル座標空間で定義します。ローカル座標はこのように求めます:

まずはP1の法線をXとした時、座標の基底vectorの長さは1なので、Xをnormalizeします。

X’ = normalize(X)

P1に隣接する頂点を1個選択し、それをP2とします。※Reference poseだけでなくDeformed poseの場合も同じP2を使います
そして次にP1からP2へ向かうvectorを

Y = P2 – P1

とします。
YはXに垂直とは限らないので次式にて垂直なvectorを求めてnormalizeします。

Y’ = normalize(Y – dot(X’,Y)X’)

これで垂直な2軸が決まったのであとは外積を使って3軸目を求めます。

Z = cross(Y’,X’)

これによって頂点のローカル座標が定義できます。差分データ:displacement vector(D)はこの座標系で定義しますので

Δ = Preference- Psmoothed

D = ( dot(Δ, X’),  dot(Δ,Y’), dot(Δ,Z))

となり、これを格納します。最後にDeformedされたメッシュにこの差分を与えます。

Final Position = D.x * X’new + D.y * Y’new + D.z * Znew

ここでのX’new, Y’new, ZnewはDeformed Smoothedメッシュの頂点毎のローカル座標となります。

問題点

FacialメッシュがHighレゾな場合、単純にDelta Mushを使用しても不要な凹凸が除去できないケースがあります。

①ノイジーで不要な凹み

下の図のようにSmoothing stepでiteration数を増やすと口の周りなどにノイズが出る場合があります。

先程、”スムーズ化されたメッシュはオリジナルメッシュより内側に縮んだ形状”であるべきという話をしましたが形状によっては
Smoothed PoseがオリジナルなPoseよりも外側に押し出されるエリアがあったりします。アニメーション中、差分の方向は内側と外側の両方が混ざったエリアもあります。
これに対して実際の変形時、Smoothed Deformed Poseが全てのエリアでDeformed Poseよりも内側に縮んだ形状になる場合、

内側への差分が適応されるエリアでは望まない凹みができることになります。またメッシュのレゾが高くHighディテールな程、より高周波でノイジーな凹みが出てくる可能性が高まります。
また同じ形状でもSmoothing iterationが増えるとよりスムーズ化されるため差分量が増えてより大きな凹みになります。結果として望まない凹凸が発生する原因になっていると考えています。

現状でこの問題に対する根本的な解決法は不十分ですがDelta Mushをマルチパス化して反復計算することでかなり軽減できます。

②Deformed Pose自体の不要な凹凸

下の図では口と鼻の間、口の横(黄色枠)におかしな凹みが残っています。このケースではDelta Mushパスを増やしても問題は解決されないだけでなく一部の形状(唇)が変わってしまいます。

この問題の原因はスムーズ化されても凹みが残ることです。下の図はSmoothed Deformed Poseを示していますがスムーズ化されたメッシュでも凹みがあります。これはDelta Mushというよりむしろ状況に応じたスムーズ化が出来ていないと言えるでしょう。

多くのtargetを使ったblendshapeベースのfacialではblend具合によってはこのような予期しない凹凸が表れることがあります。
本来であればblendshape側で適切に修正することが望ましいですがポスト処理として解決できないか挑戦した結果、
弊社では独自のsmoothing手法が使われています。Average smoothingより事前計算のデータが必要ですが、スピード的には2回のSmoothing(Average smoothingと独自smoothing)を行うのと同じくらいの処理時間でこの様な結果になります。

Facial Delta Mushの紹介は以上です。基本的なDelta Mushアルゴリズムは単純なので簡単に実装できると思います。ぜひ独自なDelta Mushを作ってみてください。
皆さんの自由な発想によるよりスマートなDelta Mushに出逢えることを楽しみにしています。

それでは、また今度!

オマケ

Maya 2016の標準 Delta Mush deformerのattributeを説明してみたいと思います。

Mayaでは先ほど説明した Laplacian smoothingのシンプルなAverage方法を使われています。

Smoothing Iterationsは、Displacementsをapplyする前のsmoothingの回数を設定します。ここが増えれば増えるほどSceneは重くなってノイズがひどくなります。

Smoothing Stepはsmoothingの強さを設定できます。先程説明したアルゴリズムでは、vertexのcurrent positionと近傍の平均を補間するsmoothness値です。 0はcurrent positionのままになります(smoothing無し),1は近傍の平均(full smoothing)
またこれはDF Delta Mushとの比較で分かったことですが、Mayaでは少しでもノイズ問題を軽減するためかsmoothness値に0.75を乗算するような補正がかかっているようです。

Pin Border Verticesはラベル名の通りです。Border verticesを動かさないように設定できます。

Displacementは事前計算された差分にかける数値です。

そして、Scaleは各方向のdisplacementにscaleを与えます。


※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

≪セットアップ室≫Paint Skin Weightの調整方法(初心者向け)

$
0
0

始めまして、デジタルフロンティアCG部セットアップ室の林です。

何事も基礎が大事です。今回のDF_talkはセットアップ初心者や未経験者に向けた基礎内容です。

セットアップと言えばアニメーションをやりやすくするために、キャラクターにrigを入れて、体や装飾品など便利に動かせるのが一般的なイメージですが、この中に実はもう一つ大事な手順があります。それはSkin weightの調整です。

Skin weight調整はRigとModelをSmooth Bindした後、jointの影響範囲を調整することです。この調整によって関節などを曲げる時の形が制御でき、テクスチャーも綺麗に見えます。
Rigの仕込みと違って、かなり単純かつ地味な作業になります。この手順で飽きてセットアップを諦める人もいます。ですがこれはかなり大事な手順です。
実際の調整方法は人それぞれですが、これから自分の経験を生かし、weight調整のやり方やコツを紹介します。セットアップに興味がある方はこの記事を見て、諦めずにチャレンジしていただければ幸いです。

画像のように今Smooth Bindされた腕のモデリングを用意しました。単純にBindしただけなので、腕を曲げるだけで形が壊れます。ここはPaint Skin Weights Tool使って調整しましょう。

Paint Skin Weights Toolの使い方覚えましょう

これは見た目の感覚で自由にSikn weightを調整できるToolです。Paintなので、もしペンタブを持ちなら筆圧でもっと繊細な調整ができ、効率はかなり上がるのでお勧めです。
ここでよく使う機能を下の画像にまとめました。

①ペイント操作(Paint Operation)
ここはかなり大事な部分です。ちゃんと四つのモードの意味を理解できればさまざまの状況に対応できます。
●置き換え(Replace)
このモードは対象インフルエンス現在のウェイト値をValueで設定されたウェイト値に置き換えます。
●加算(Add)
このモードは対象インフルエンスのウェイト値を上限まで増やすことができます。(通常上限は1です)
●スケール(Scale)
このモードは対象インフルエンスのウェイト値を0まで減らすことができます。
●スムーズ(Smooth)
このモードは対象インフルエンスと隣接のインフルエンスのウェイトをスムーズさせることができます。

②値(Value)
paintしたいウェイト値の調整を行います。

③不透明度(Opacity)
Valueと合わせてpaintの加減を細かく調整できます。
例えば、Valueが0.6、Opacityが0.3を設定し、Replaceモードでpiantします。ウェイトが30%のスピードで0.6のウェイトまで徐々に増えます。この二つをうまく組み合わせてpaintの加減を調整します。

④インフルエンスのLock
この機能もかなり重要です。
Lockは対象jointのウェイト値をロックできます。この機能をうまく使えば、ロックされてないインフルエンスの間でウェイトの調整ができ、ほかのロックされたインフルエンスに余計なウェイトも入りません。

重要な機能説明はここまでです。ここから操作しながら応用します。

Smoothモードの使い方

まずSmoothモードの使い方です、一見便利な機能だと思いますが、実際全部をきれいにスムースできない場合もあります。Smoothモードで塗る時、盲目的に塗るのではなく、少しコツがあります。


画像では曲げた腕の肘部分に注目してください。簡単にshoulderJT とelbowJTのウェイトを振ってありますが、あまりきれいではないです。Smoothを使ってきれいにしましょう。

このモデルには大量にインフルエンスされたジョイントがあります。実際に調整したいのはshoulderJTとelbowJT二つだけなので、ほかのインフルエンスはロックしましょう。

適当にSmoothモードで塗ると画像のように、必要以上な凹みなどの問題が発生します。

画像の説明のように、二つのjointそれぞれのSmoothがかける方向は画像中の矢印で表示してあります。この方向を沿って、大体青い枠の範囲内で、適度に塗れば綺麗な形ができます。

三つ以上のインフルエンスの調整方法

今回の例はTwistRigを使って説明します。今のRigでは腕や足などねじる時細くなり、根元も一緒に回転します。これを防ぐために、下の画像のように補助Rigを入れます。今から追加したRigを使って腕が細くならないようにウェイトを調整します。

画像の説明のよ うに、L_shoulderIKJTは根元の回転を防ぐために作ったIKです。IKハンドルを回転しないと、IKJT自身は回転しません。今IKハンドル はelbowJTとpointConstraintされました。IKハンドルはelbowJTのtranslateの影響しか受けないので、 shoulderJTの回転には影響受けません、ゆえに回転しません。

twistJT は画像説明のように、rotateXのみ回転します。回転値はshoulderJTの0.5倍です。たとえばshoulderJTのrotateXの回転値は 10の時twistJTのrotateXは5になります。こうやって両端のjointの回転の中間値を取って、腕が細くなるのを緩和します。

shoulder周りは、shoulderJTと先ほど説明したIKJT・twistJTを合わせて三つの インフル エンスを使います。ですが、同時に塗るのではなく、まずニつに絞ってきれいに調整してから三つ目のウェイトを調節しましょう。
まずL_shoulderIKJTとL_shoulderJTから始めてください。

L_shoulderJT根元のウェイトをL_shoulderIKJTに振ってあげましょう。ここはScaleモードでL_shoulderJTのウェイトを減らして、IKJTのほうに移す方法を勧めます。理由はScaleは減るだけなので、操作ミスでほかの所にウェイが塗れないからです。

まずこの二つ以外のインフルエンスをロックして、根元から真ん中まで思いっきり0までウェイトを減らしましょう。

画像のように腕をねじるとすぐ壊れますが、ここは先説明したスムースの塗り方で直しましょう。

直したら、twistJTのロックを解除しましょう。ねじった状態のままでAddモードを使って、Value値を0.1~0.3くらいでかなり弱めにtwistJTのウェイトを増やしてください。jointの位置を中心に両端まで塗ってください、若干やりすぎても大丈夫です。

程よくできたら、スムースをかけましょう。twistJTは両方同時に影響があるので、中央から両端までスムースをかけてください。

これで基本は完成しましたが、たまにスムースでは対処できないところもあります。ここにWeight Hammerという便利な機能ですぐ直せます。

Weight Hammerの使い方

これは望ましくないウェイト値の頂点を修正できるツールです。修正したいVertex、Edge、Faceを選んで(複数選択可能)クリックすると、隣接する頂点と同じウェイト値が割り当てられます。

※ここで注意点があります。

①一気に選らび過ぎないようにしてください。こうすると画像のように結果がかなり変わります。
できれば部分的細かく修正するのが望ましいです。
②このツールは万能ではありません。もし隣接する頂点はおかしいなウェイトが入った場合は、選んだ頂点もおかしくなります。つまり、これはあくまである程度きれいにウェイトが調整できた後で使うツールです。頼りすぎは禁物です。

Weight Hammerをうまく使って、腕の荒いウェイトを直しましょう。

これで完成しました。腕の一部しかないですが、このやり方を応用すれば全身までいけます。

まとめると、ウェイトをうまく塗るポイントは:
①Rigの構造と役割をよく理解しましょう。
②Paint Skin Weights Toolなどの機能を理解しうまく使いましょう。
③経験をつみましょう。ウェイト調整は一回で覚えるスキルではありません。他人から学ぶのはもちろんですが、自分でさまざまのキャラのウェイト調整し、他人のやり方をを融合し、自分なりのやり方は見つけてください。

DFセットアップ室では、リギングアーティストを大募集中です。
一緒に魅力的な作品を作っていきましょう!
詳しくは、下記の募集要項をご覧ください。
http://www.dfx.co.jp/recruit/application/cg/#recruit_1240

※免責事項※

本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。


≪エフェクト室≫ FumeFXあるあるトラブル解決術!(初心者向け)

$
0
0

ごぶさたしております。エフェクト室の小宮です。
久しぶりの投稿になりますが宜しくお願いします!

さて、今回はFumeFXを使い始めたばかりの方がつまずきがちなトラブルを解決していこうと思います。
今回の記事で学生さんや業界に入りたての新人さんのエフェクトのクオリティーが一段あげられたら幸いです。
それではさっそくはじめてみましょう!

炎の質感がベタッとしてしまう

デフォルトの設定ですと炎の質感がベタッと潰れがちです。こういう場合はFireOpacity のカーブを調整するとよいです。
下の動画の左がデフォルトの設定のままので、右がFireOpacity のカーブを調整した炎です。
このようなカーブにすることでベタッとしていた部分がなくなり自然な階調が出てきました。

FireOpacity のカーブを編集したい時はRendering タブのFire ロールアウトで下の画像のアイコンをクリックしてください。

縞模様を消したい

レンダリングすると縞模様が目立つ時があります。こんな時はJittering % の値を大きくしてみてください。
縞模様を目立たなくすることができます。

下の画像は上の動画を拡大したものです。縞模様が目立たなくなったのがわかるかと思います。
※画像をクリックすると拡大します↓

Jittering % の調整はRendering タブのRendering Parameters ロールアウトでできます。
値を上げすぎると全体にボケてしまうのでやりすぎに注意です。
また、Jittering % をあげた時にノイズがでる時がありますが、その場合はStep Size % の値を小さくしてみてください。
(Step Size % の値を小さくしていくとレンダリング時間が長くなっていきます)

発生時にキノコが発生してしまう

下の画像のように発生時にキノコが発生してしまうことがあります。
キノコを消す方法は色々ありますが今回は煙の発生時にTurbulence をあたえてみました。
この方法でお手軽にキノコを消す事ができます。

このようにTurbulence にアニメーションキーを打ちました。発生してから40フレームの間、Turbulence をあたえています。
Turbulence は段々弱めていきますが最後は0ではなく少しだけ値が入っています(今回は0.05)

未処理のものと比べてキノコの印象が弱まったのが確認できます。

まとめ

さて、簡単ですが今回はここまです。いかがだったでしょうか?
単純なことかもしれませんが、こういった部分をひとつひとつ丁寧に調整することでエフェクトの見栄えがぐっと良くなります!
今回はFumeFXのあるあるトラブルを解決してきましたが、FumeFX以外の流体ツールでも同じことが言えるかと思います。

エフェクターを大募集!

現在、弊社エフェクト室では一緒にエフェクトをつくる仲間を絶賛募集中です。
経験者の方はもちろん、初心者の方でもエフェクトが大好きな方や興味がある方はどんどん応募してください!
募集要項はこちらになります。よろしくお願いします!!
http://www.dfx.co.jp/recruit/application/cg/#recruit_1237

≪セットアップ室≫キャラクターセットアップに大切なこと

$
0
0

みなさん、こんにちは。野澤です。
今回はセットアップ室の野澤として、キャラクターセットアップに対して思うことを書いてみようと思っています。
今回は細かい技術的な内容ではなく、コンセプト的な内容です。

キャラクターセットアップにおけるプライオリティ

みなさんは、リギングされたキャラクターを扱う時にどの様なプライオリティを意識するでしょうか?
・アニメーターにとっての扱いやすさ?
・操作の軽さ?
・見た目の自然さ?
・複雑なギミックの連動性?
・異なるツール間を跨ぐためのハンドリングしやすさ?

などなどいろんな観点で判断しなければいけないと思います。
自分はその中でも、見た目の自然さや美しさに一番気を遣う様に周りに伝えています。
レンダリングされるモデルの変形が不自然に見えてしまったら、
いくら仕組み的に高尚なセットアップになっていても全てが台無しとなってしまいます。
セットアップをする人は、とかく頭でっかちになりがちなところがありますが、
根本的な話をすると最終的な見た目が良ければ全て良しだと自分は思っています。
CGで描かれたキャラクターは、1コマずつ切り抜いてみるとパラパラ漫画の様なものですね。
フレーム単位で好きに形状を編集できるセットアップは、それだけ見た目を美しく出来ると思います。
とはいっても何でもかんでも自由に変形出来てしまうとカットごとでキャラクターの見た目にバラつきが出てしまいキャラ崩れに繋がる事もありますね。
こういう場合あらかじめ動かす事を想定した場所にコントローラを仕込んでおいてアニメーターに制御してもらう事もありますが、
やはりそれだけでは万能とは言えません。

↓個人的にすごく好きなリグ

SergiCaballer – Troglodita RIG from Sergi Caballer on Vimeo.

↓この人のリールはいろいろすごいです。

Rigging TD Reel 2015 from Webber Huang on Vimeo.

弊社では、この見た目をブラッシュアップしたり、めり込みを修正する作業を、ショットワークの中でキャラクターを選択し、編集したい部位にのみ
影響を与えるブレンドシェイプターゲットで編集するツールがあります。

上記の動画の様に、直したい箇所だけを選んで、なるべく直感的に見た目のブラッシュアップを行えるようにしています。

DCCツールの垣根を越えるセットアップ

今度はもう少し範囲の広い話をします。
CGを制作する上で、全て同じDCCツールを使っていれば悩まなくてもいいのですが、世の中には
たくさんのDCCツールが存在します。
Maya, 3dsmax, Softimage, Houdini, C4D, MotionBuilder, Blender….などなど。
それぞれに長所短所がありますが、表現力を手軽に上げるためには、複数のDCCツールを使って
それぞれのいいとこ取りをした方が効率が良いです。
しかし、それぞれのソフトの中で同じように動作するセットアップを用意するのは非常に難しいです。
単純なスキニングであれば、殆どのDCCツールがサポートしているfbxフォーマットを用いる事である程度解決出来ますが、
それだけではかなり妥協した表現になってしまいます。
これらの問題を解決するために、Alembicというジオメトリキャッシュの共通フォーマットが誕生し、
今日では様々なDCCツールがサポートしてくれるようになりました。
しかし、全てをポイントキャッシュとして扱う方法は確実ではありますが、膨大なデータ量との戦いを強いられます。
ソフトやレンダラーによってSubdivide(スムース化)の計算が変わってしまうと
見た目に差が生じてしまうために、あらかじめSubdivideかけたモデルをキャッシュしたりすると、
たった数十フレームのショットのポイントキャッシュが数十GBを超えてしまう事もざらです。

日本の環境ではサーバやPCのOSにWindowsを使っている会社が殆どで、ファイルのIOがプロジェクトのボトルネックになる事はよくあります。
また、ジオメトリキャッシュを使ってしまうと後で編集が容易に出来ないため融通が利かないという問題もあります。
そんな中で、自分が前から非常に注目しているのが、Fabric EngineのRigging FrameworkのKrakenです。

Kraken – Open Source Rigging Framework from Fabric Engine on Vimeo.

Fabric Engineについてはtaikomatsu大先生が紹介しているのでこちらも是非ご覧ください。
このKrakenの機能を使用したRigを組むとMayaと3dsmaxとHoudiniとSoftimageなどで同じRigを扱うことが出来ます。
ただ単に同じリグを扱えるというだけでなく、GPUを活用した非常に高いパフォーマンスを期待できてリグの高速化も見込める夢の様な話です。
しかし一番のボトルネックは、実現したいデフォームの表現は自分たちでコーディングしなければいけない事でしょうか。
標準で用意されているskinClusterやシンプルなデフォーマー以外は、自分たちで実装しなければいけません。
プロジェクトで使用するためには、相当な開発コストがかかってしまいます。
しかし、このKrakenはOpenSourceなので、世界中の開発者の方がどんどん新しい機能を実装していけば、プロジェクトで活用する事もそう遠くないかもしれません。
ちなみに、このFabric Engineは、有償のFrameworkですが、Fabric Fiftyというキャンペーンで1 studio 50シートまで無料で使うことが出来ます。
興味のある方は是非試してみてください。そして情報共有していきましょう。

モジュラーリギング

キャラクターセットアップをしていると、パーツ替え、衣装替え、IK/FK切り替え、
コントローラの変更などアニメーターやショットアーティストから様々な要求をされます。
しかし全ての要求を一つのファイルで網羅できるセットアップは、中身が複雑になりオペレーションも重たくなりがちです。
しかも多くの機能を兼ね備えたリグが完成するまでには非常に多くの時間がかかり、その間アニメーターがモーションを
チェックする事ができないと作業効率が著しく低下してしまいます。
この問題を解消するために、Modular Rigging System(モジュラーリギング)という物が提唱されました。
この仕組みは、一つ一つのリグをモジュールという細かい単位で区別し、
ベースとなる骨格に後から接続する形でリグを機能させます。
こうする事で必要な機能は後から追加する事も出来るし、不要であれば取り除く事も容易になります。
と言葉で説明すると簡単なように感じるかも知れませんが、接続先の名前が変わってしまうだけでリグが破綻してしまったり
思わぬトラブルを引き起こす可能性が高いため、事前に綿密な仕様を立てる必要があります。

Modular Rigging System – Overview from CreatureRigs on Vimeo.

例えば上記で紹介されているようなフリーのスクリプトを試用してリグを構築すると、
手作業で構築するよりもヒューマンエラーを防止する事が出来ますが
ツールでサポートしていない範囲をどうするかなど、やはり考えなければいけない事はたくさんあります。
少し古い情報ですがHaloシリーズの開発で有名なBungieスタジオのHPでは、
このModular Riggingの仕組みを詳しく解説したスライドとビデオをダウンロードする事が出来ます。
http://halo.bungie.net/inside/publications.aspx
リグモジュールを接続する先をノードの名前を参考にするのではなく、ノードの上流にmetaNode(情報を格納)を接続し、
必要な情報をそちらから取得するという方法でトラブルを回避しています。
これらを活用するには、リグを構築するためにスクリプトを書く必要が出てきますが、リギングにかかる工数を必要最小限に
抑えるためには無視できない部分だと思います。
と、この記事を書いている間にCEDEC2015の講演内容が発表されました。
http://cedec.cesa.or.jp/2015/session/VA/5052.html
スクウェアエニックスの佐々木さんがモジュラーリグについて発表されるみたいですね。
これはリガー必見の内容になりそうですね。
CEDECといえば、私も僭越ながらこちらで講演させて頂くことになりました。
http://cedec.cesa.or.jp/2015/session/VA/2667.html

私の方では、リグではなくプロジェクトマネジメントの効率化について今話題の「Shotgun」の活用方法を紹介させて頂く予定です。
興味のある方は是非会場まで足を運んで頂けると嬉しいです。

資料集め

これからリグの勉強をしていきたくても、どこから情報を集めて良いか分からないという人も多いと思います。
Google先生に”Rig tutorial”などで検索しても色々と情報が揃えられますが、弊社ではさらにPinterestというサービスを利用しています。
ご存知の方も多いかも知れませんが、Pinterestは資料探しにとても便利なウェブサービスです。自分で一つ一つ調べなくても誰かが調べた情報をPinしておく事や、あらかじめ登録してあった情報が自然と集まってきます。
カテゴリ毎にボードを作り整理をすることが出来てとても便利です。

Pinterest

Pinterest

まとめ

ちょっと長くなってきたので、今回はこの辺で〆ようと思います。
キャラクターセットアップは、たくさんのピースを巧みに組み合わせてキャラクターに命を吹き込む非常にやりがいのある仕事です。
ロジカルに物事を考える訓練にもなりますので、もし興味がある方は是非トライしてみてください。
弊社では、枠の中でがんじがらめに縛られた仕様のリギングを強要することはありません。
リギングアーティスト一人一人で考えたベストだと思う方法をみなで共有しあいながら日々様々な改善に取り組んでいます。
そうしてリグは日々進化していくものです。


そんなDFセットアップ室では、リギングアーティストを大募集中です。
一緒に魅力的な作品を作っていきましょう!
詳しくは、下記の募集要項をご覧ください。

http://www.dfx.co.jp/recruit/application/cg/#recruit_1240




※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

画質評価アラカルト ~SSIMってすごい!~

$
0
0

こんにちは。開発部の高山です。
明日(8月11日)は山の日ですね!と書こうと思いましたが、実際に施行されるのは2016年からなんですね。最近知りました。

今回も業務とはあまり関係ない画像処理系のネタをやっていきますよ。

画質評価とは?

画像を適当な形式で保存したり、拡大縮小等の画像処理を繰り返していると元の画像とは異なる、劣化したようなものになってしまうことがあると思います。
では、実際どの程度異なるのか?というのを数値化するのが今回のテーマです。
画質評価や画像評価などと呼ばれています。

これが使われる例としてはjpgなどで画像圧縮をした場合、圧縮前の画像からどの程度劣化しているかを計りたい時ですね。
画像や動画は圧縮して容量を減らす場合も多いと思われますが、圧縮すればするほど基本的には劣化が激しくなってしまいます。
ではどこまでの劣化を許容するのか、といった時に「なんとなく見て劣化が分からないように」といった曖昧な答えではなく、
「PSNRが30以上になるように!」といったはっきりとした答えを出すことができます。
これによってクオリティは一定以上に保ったまま、データ容量の削減ができるわけです。

他にはノイズ除去や超解像などのアルゴリズムの精度比較の指標としてもよく使われます。

具体的な手法としてはMSE、PSNR、SSIMといったものがよく用いられています。
とりあえずはそれらの説明から。


手法ごとの説明

・MSE(平均二乗誤差)

MSEはMean Square Errorの略で、画素値の差分を2乗して足していき、その合計の平均を求めるといったものです。
名前は知らなくても、とりあえず思いつく方法の1つだと思います。

MSEの平方根を取ったものをRMSEといい、出てくる値としてはこちらの方が分かりやすいでしょう。
例えばRMSEが12になったとすれば、2つの画像の画素値が平均して12ずつ違う!といったことに(大体)なります。

※余談
テンプレートマッチングの分野では画素値の差分を2乗したものの合計をSSD(Sum of Squared Difference)とよびます。
MSEと意味的にほとんど変わらないのに全然名前が違っていて正直ややこしいです。


・PSNR(ピーク信号対雑音比)

PSNRはPeak Signal-to-Noise Ratioの略で、以下の式で求められます。

ここでいうMAXとは画素値の取りうる最大値を意味します。よく用いる8ビット画像ならMAX = 255となります。
単位はdB(デシベル)です。値が少ないほど劣化が激しく、高いほど劣化していないことを示します。
同一画像2枚で行った場合は途中式で0除算が起こるため、求められません。
使いにくい部分もありますが、よく用いられている評価方法の1つです。


・SSIM(Structural Similarity)

MSEやPSNRはそれなりの結果が出てきますが、これらによって求まる値は人間が見た時に感じる違いと必ずしも一致しません。
結果の方で詳しくその辺りをお見せしますが、MSEやPSNRでは「画像全体で少しずつ違う」「局所的に大きく違う」といった時にほぼ同じ結果が返ってきます。
一方人間は前者よりも後者により強い違いを感じる傾向があります。
その人間が感じる違いをより正確に指標化するためにできたのがSSIM(Structural Similarity)です。

具体的な計算方法は以下の通りになります。

ここで、μx, μyは平均、σx, σyは標準偏差、σxyは共分散を意味します。
c1やc2は自由に決めることができますが、8ビット画像の場合 c1=(0.01 * 255)^2, c2=(0.03 * 255)^2 を用いることが多いです。

…とだけ説明されている場合が多いのですが、これだとさっぱり分からないのでもう少し詳しく説明します。間違っていたらごめんなさい。

まず、原画像と比較画像の同じ位置のピクセルを中心とする局所領域の画素値を全て取得します。
下の画像で言うと、緑の四角で囲んだ領域が局所領域で、それらをそれぞれx, yとしています。

つまり、上で言う所のμx, μyはそれぞれの局所領域の画素値の平均を表し、σx, σyはそれぞれの局所領域の画素値の標準偏差を表しているということですね。
これらは先程それぞれの局所領域の画素値を取得しているので、求めることができます。σxyもこれらの情報から同様に計算できます。

そして求まったμx, μy, σx, σy, σxyを最初の式に代入すれば、そのピクセルにおけるSSIM値が定まります。
これを全てのピクセルで行い、その平均を取ることで最終的なSSIM値の結果が求まります。

まだいまいち分からない!といった方のために最後にサンプルコードを載せておいたので、そちらも参考にして下さい。
さっぱり分からない!といった方は「なんとなくだけどSSIMすごそう」ということを感じ取って頂ければと思います。

ちなみに、実際の運用ではガウシアンフィルタをかけるなど事前処理的なものをすることが多いそうです。今回はその辺りの説明は省いています。


実験結果

MSE, PSNR, SSIMを実装したうえで、様々な場合にどういった結果になるのか実験してみました。

画像圧縮の場合

Photoshopを使って画質を変えてjpg保存した場合に、どの位画質が劣化するのか、それぞれの評価値がどうなるのかを調べてみました。(画像はクリックで拡大)

原画像
サイズ 193KB
画質100
サイズ 74KB
画質50
サイズ 15KB
画質0
サイズ 6KB
MSE: 0.00
PSNR: ―
SSIM: 1.000
MSE: 0.34
PSNR: 52.82
SSIM: 0.998
MSE: 9.91
PSNR: 38.17
SSIM: 0.964
MSE: 59.96
PSNR: 30.35
SSIM: 0.864

画質が低いほどMSEが大きく、PSNRやSSIMが小さくなっている事が分かります。
MSEが相違度を、PSNRやSSIMが類似度を評価するものなので正しく実装できてそうです。
Photoshopでは画質0にしても思ったほどは劣化しないのですね。画像の種類にもよるでしょうが、もっとブロックノイズとかでると思っていました。


縮小拡大を行った場合

画像を1/4倍に縮小した後、4倍に拡大した時にどの程度画像が劣化するのか調べてみました。

縮小や拡大に使う画像補間アルゴリズムについての詳細は↓の記事をご覧ください。
分かる!画像補間 ~基本から応用まで~
自然な流れで過去記事を宣伝

Nearest Neighbor
ニアレストネイバー法
BiLinear
バイリニア法
BiCubic
バイキュービック法
Lanczos-3
ランツォシュ-3法
MSE: 423.15
PSNR: 21.87
SSIM: 0.671
MSE: 243.75
PSNR: 24.26
SSIM: 0.748
MSE: 246.24
PSNR: 24.22
SSIM: 0.753
MSE: 262.36
PSNR: 23.94
SSIM: 0.737

ニアレストネイバー法は劣化具合が激しいですが、他は大体似たり寄ったりな結果ですね。
これらは画像の種類や拡大縮小の割合によって変わってくる部分だと思います。


画像処理を行った場合

様々な画像処理を加えた場合に、それぞれPSNRやSSIM値がどうなるのかを調べてみました。

明度上昇 ノイズ追加 ぼかし処理 1ピクセル右に移動
MSE: 204.41
PSNR: 25.03
SSIM: 0.991
MSE: 212.07
PSNR: 24.87
SSIM: 0.516
MSE: 237.61
PSNR: 24.37
SSIM: 0.717
MSE: 288.34
PSNR: 23.53
SSIM: 0.767

これらは明らかにPSNRとSSIMで違いが出ています。
PSNRは23から25におさまっている一方で、SSIMはそれぞれで全然異なる結果になっています。
例えば、明度の変化は人間はそこまで強く意識しない(勝手に補正する)ため、SSIM値が高くなっています。
ノイズなどは逆にはっきり意識するため、SSIM値が低い結果になっています。
完全ではないにせよ、ある程度人間が感じる違いに近いものがSSIM値で判断できることが分かります。

一方SSIMでも苦手なものが微小な移動や回転で、人間はほとんど変化ないと判断しますが、上の結果の通り1ピクセル右に動いただけでかなりSSIM値が下がってしまっています。
ただ、実際のSSIMではフィルタなどをかけることが多いので、このあたりもある程度カバーできているのかもしれません。


まとめ

画質評価のアルゴリズムとしてMSE、PSNR、SSIMといった代表的なものを紹介しました。
画質評価としてだけでなく、割と様々な分野で使えそうなものだと個人的には思っています。

MSEやPSNRよりもSSIMの方が複雑な分、より人間が感じる違いに近いものを得ることができます。すごい。
ただし、SSIMは式やパラメータが一意に決まっているものではないため、そこは十分に注意して下さい。

では、今回はこの辺で~。


サンプルコード

今回実装したMSE, PSNR, SSIMのプログラムを載せておきます。
上でも述べた通りSSIMは一意に求まるものではない、実際の運用ではガウシアンフィルタなどをかけることが多いため、あくまで参考までに。
より正確な方法が知りたい!という方はコチラ(本家)へ。

[cpp]
#include

int mirror(int x, int min, int max){
while(x < min || x >= max){
if(x < min) x = min + (min - x - 1);
if(x >= max) x = max + (max – x – 1);
}

return x;
}

float SQR(float x){
return x * x;
}

float MSE(float *data1, float *data2, int width, int height){

float Sum = 0.0f;

for(int y=0;y for(int x=0;x Sum += SQR(data2[x + y * width] - data1[x + y * width]);
}
}
Sum /= (width * height);

return Sum;
}

float PSNR(float *data1, float *data2, int width, int height){
return 10.0f * log10(255.0f * 255.0f / MSE(data1, data2, width, height));
}

float SSIM(float *data1, float *data2, int width, int height){

float c1 = SQR(0.01f * 255.0f);
float c2 = SQR(0.03f * 255.0f);

int m = 2;

float Sum = 0.0f;

int mx, my;

for(int y=0;y for(int x=0;x

float ave1 = 0.0f, ave2 = 0.0f; // 平均
float var1 = 0.0f, var2 = 0.0f; // 分散
float cov = 0.0f; // 共分散

for(int dy=-m;dy<=m;dy++){
for(int dx=-m;dx<=m;dx++){
mx = mirror(x + dx, 0, width);
my = mirror(y + dy, 0, height);

ave1 += data1[mx + my * width];
ave2 += data2[mx + my * width];
}
}
ave1 /= SQR(m * 2.0f + 1.0f);
ave2 /= SQR(m * 2.0f + 1.0f);

for(int dy=-m;dy<=m;dy++){
for(int dx=-m;dx<=m;dx++){
mx = mirror(x + dx, 0, width);
my = mirror(y + dy, 0, height);

var1 += SQR(data1[mx + my * width] - ave1);
var2 += SQR(data2[mx + my * width] - ave2);
cov += (data1[mx + my * width] - ave1) * (data2[mx + my * width] - ave2);
}
}
var1 /= SQR(m * 2.0f + 1.0f);
var2 /= SQR(m * 2.0f + 1.0f);
cov /= SQR(m * 2.0f + 1.0f);

Sum += ((2.0f * ave1 * ave2 + c1) * (2.0f * cov + c2)) / ((SQR(ave1) + SQR(ave2) + c1) * (var1 + var2 + c2));
}
}

return Sum / (width * height);
}

[/cpp]


※免責事項※
本記事内で公開している全ての手法の有用性、安全性について、当方は一切の保証を与えるものではありません。
これらの手法を使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご活用ください

【来場御礼】CEDEC 2015セッション

$
0
0

みなさん、こんにちは。野澤です。
先日、パシフィコ横浜で行われたCEDEC2015にて、
プラチナゲームズ株式会社 久禮さん、 スマイルテクノロジーユナイテッド株式会社 高橋さん、 株式会社セガゲームス 樋口さん、
株式会社セガゲームス 麓さんと、「Technical Artist Bootcamp 2015」というタイトルで登壇させて頂きました。
非常にたくさんの方にご来席頂き、誠にありがとうございました。
Technical Artistという職種が最近ではゲーム業界、CG業界でもある程度認知されてきましたが、
私がTDになった2007年の頃は、そういう仕事を専門にする人は本当に少なかったと思います。
デザイナーのクリエイティブワークを支援するために、繰り返し作業の効率化やワークフローの見直しなどを中心に様々なことをさせて頂きました。
今回のCEDECでは、そのTD室の活動の一部を紹介させて頂くと共に、最近DFで全てのプロジェクトのベースとなりつつある「Shotgun」との付き合い方をお話しさせてもらいました。
この記事では、そのセッションでご紹介したスライドと動画を公開したいと思います。
これらのツールは、TD室で開発をした極一部のツールですが、参考になれば幸いです。

セッションスライド

StartIt

このツールは誰でも共通のプロジェクトフォーマットで始められる様にする為に開発しました。
開発環境: JavaScript, Twitter Bootstrap, JQuery

MissionRoom

このツールは、Shotgunに登録されたタスクに用意に情報を記入するために開発しました。
開発環境: Python, PySide

WorkRoom

このツールは、デザイナーが行う作業をShotgunのタスクベースに統一し、進捗やバージョン、担当者などを把握し易くする為に開発しました。
開発環境: Python, PySide

Check Movie Up Tool

このツールは、チェックに出すムービーをサーバ、Shotgunへアップを、共通のフォーマットで容易に行う為に開発しました。
開発環境: Python

まとめ

Shotgunは、海外ではものすごく普及しているそうですが、国内ではまだまだと聞きます。
実際に使ってみて感じたのは、完全分業化されたワークフローで、それぞれのタスクを細分化していく文化が根付いている所では親和性が高いと思います。
ですが、日本では一人のアーティストが複数の作業を同時に担当する事の方が普通だと思います。
また、その作業の切れ目が明確ではない為、進捗率を把握するのが難しいと思います。
我々は、社内の文化やワークフローをなるべく壊さずに、Shotgunを使いこなす為に、ツールを用いて橋渡しする道を選びました。
ここまで来るのに、中々厳しい道のりでしたが、ウチのTD達がものすごいスピードでShotgunを習得してくれたのでホントに助かりました。
ですが、スライドにも書いた通り、まだまだ進化の過程でありやらなければいけない事がたくさんあります。
Shotgunを既にお使いの会社やこれからShotgunを使おうと思っている会社と共に日本の制作現場にあった使い方を模索していければ幸いです。

最後まで、お付き合い頂きありがとうございます。

※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください

LightStage 解体新書 Part2

$
0
0

こんにちはー。開発部のひらく[@horonig]です。

今回、SIGGRAPHついでにICTの内覧会に参加してきました。
そこでLightStageに関する最新情報を諸々ゲット出来たので、シェアさせて頂きます。

LightStageとは?

そもそもLightStageを知らないという方は、まず前回の記事をご覧下さい(話はそれからだw)。
LightStage 解体新書

ちなみに前回の記事で「世界に三台だけ」と書きましたが、現在はActivisionにもあるそうです。

ICT

ICTとはなんぞや?と思われる方も多いと思うので、一応説明しておきます。

ICTは Institute for Creative Technologies の略で、
Paul Devebec氏が率いる USC(University of Southern California) の研究室です。

主にLightStageを始めとするCapturing技術を研究してる研究室として非常に有名です(間違ってたらすみません)。
とにかくこの分野での最先端技術とノウハウを持っており、ハリウッドの有名スタジオも利用しています。
Wetaとの共同研究の話もありましたし(あったよな・・)、産学連携も盛んに行っている模様です。

ICTの歴史に興味のある方は、こちらのfxguideの記事を読むといいかもしれません(けど長いw)。
The Art of Digital Faces at ICT – Digital Emily to Digital Ira

 
ICTの概観はこんな感じ。立派ですなー。

LightStageの古いバージョンが飾ってある・・!歴史を感じますね。

そしてこれが最新のLightStage!!キラッキラ!!w

あとはお約束の記念撮影(笑)

イラっとして貰えましたかね。
さて、本題に戻りましょうか。

LightStage比較表

ICTにある最新版と、前回報告した台湾のNMAのものとの比較表を以下に書いてみました。
よろしくご査収下さいませ。

ICT NMA
Version LightStageXⅢ (最新) LightStageX (旧)
撮影用Camera Canon EOS-1D X Canon EOS-1D Mark Ⅳ
撮影用Cameraの解像度 3456×5184 3264×4896
プロジェクタ使用 なし あり
撮影時間 1~2秒 5~6秒
一般的な納品Map diffuse map (albedo)
specular map
specular normal map
displacement map
single scattering map
diffuse map (albedo)
specular map
specular normal map
diffuse normal map [R]
diffuse normal map [G]
diffuse normal map [B]
納品Mapの最大解像度 6K 4K
Microgeometry Map 作成可能(16K) 作成不可(多分…)
LED 明るい(ざっくりw) 普通
LEDの種類 R, G, B, Cyan, Umber, White Whiteのみ
IBL カラーでの動画キャプチャ可能 静止画のみ
(RGB別で撮影する必要があるため)

 
とにかく、諸々性能が上がっていることが分かりますね。
カメラもそうですが、LED周りの性能が飛躍的に上がっています。カラー化した事により、IBLも動画で撮れるようです。

また、プロジェクタからのプロジェクション(投影)がなくなっていることもあり、撮影時間がかなり早くなっています。
(パターンを投影した写真がなくても、実際の写真からreconstructするアルゴリズムが成熟してきた、ということだと思います)

そして納品可能なマップに、displacement map と single scattering map が追加されてます。
(single scattering mapってどんな感じなんだろ・・。見てみたい・・w)
diffuse mapに関しては、ambientなどが除去されもっと綺麗になっているとの事でした。

更に、カメラを後ろに追加し、頭全体のreconstructionも可能になっているようです。

Microgeometry

表中に出たこのMicrogeometryという単語を疑問に思ったかもしれませんが、こちらに関しても言及しておきしょう。
動画で見ると分かりやすいのでまずは以下を見てみて下さい。

 
[プロジェクト ホーム]
Measurement-Based Synthesis of Facial Microgeometry

簡単に言ってしまうと「局所的なマップを採取し、それをうまく当てはめていく事で高解像度マップ(16K)を作り出す技術」です。
これにより、カメラが寄っても高精細なレンダリングが可能となります。
 

そして今回それをdynamicに変化出来るようにした論文動画がコチラ!!(SIGGRAPH2015で採択されました)

 
[プロジェクト ホーム]
Skin Microstructure Deformation with Displacement Map Convolution

凄いですねー。わくわくしますね。
最終的にやっている事は、Microgeometry Mapに対して伸び方向にblur, 縮み方向にsharpのconvolutionをしているだけなのですが、とても素敵な結果となっています。きっちりとした計測ベースからの理論構築ということもあり、非常に説得力があります。
そしてこの論文は、ICTの Koki Nagano さんという日本人の方が書かれました!つまり、Made in Japan!! (USCですが 笑)

今回のICT訪問でKokiさんにも色々とお話が聞け、非常にためになりました。本当にありがとうございました。
(と言うか、LightStageの情報はほとんど彼にご教授いただきましたw)
てか、ラーメン食べすぎですよ。

[ラーメン大好き Kokiさん ホームページ]
KOKI NAGANO
 

最後に

こんな風に色々と書いてみたものの、Paul Debevec氏は現在も常にLightStageを進化させており、
まだまだLightStageの底は見えません。今後の展開にも注目しておきたいところです。(二回目)

ではではー。

Special Thanks to Koki-san !!

※免責事項※
本記事内で公開している全ての手法の有用性,安全性について,当方は一切の保証を与えるものではありません.
これらの手法を使用したことによって引き起こる直接的,間接的な損害に対し,当方は一切責任を負うものではありません.
自己責任でご活用ください.


 

■LAラーメンレポート

せっかくなのでLAでもラーメンシバいてきました。
今回はその二店をご紹介!

新撰組 博多ラーメン リトルトーキョー店

リトルトーキョーにある博多ラーメン屋さんです。

新撰組なのにトンコツとはこれいかに、とは思いつつも非常に美味しかったです。
ちょっと麺が細すぎな気もしましましたが。
ところで、「バリカタ」とか「ハリガネ」とか「粉落とし」って英語でどう言うんだろ(笑)

Tsujita LA

LAにつけ麺なんてないよなー・・と思って調べてみたらあのつじ田がLAに!!
となると行かないわけには行きません。ICTからも近かったので当たり前のように行ってきました。

一週間近くLAに滞在して日本食が恋しくなってきた時期だったということもあり、満足感はおっぱ・・いっぱいでした。
ただ日本のつじ田に比べると香りが落ちる気が・・。麺とかカボスとか。舌(鼻)が馬鹿になってたのかしら?

Kokiさんによると、向かいのTsujita Annexというお店はジロー的なラーメンを提供するそうで、非常に美味しいとの事でした。
今度はそっちに行ってみたいぞ・・!!いや必ず行く・・!!

以上、LAからのラーメンレポートでした!(もう日本ですが)

ではではー。

※免責事項※
本記事内で公開しているラーメンの感想は完全に私見です.

SIGGRAPH2015論文(流体セッション)の紹介

$
0
0

皆さん、こんにちは。開発部の川田です。

今回は、SIGGRAPH2015の流体セッションの1つであるWave-Particle Fluidityの中から、論文紹介をしたいと思います。

Restoring the Missing Vorticity in Advection-Projection Fluid SolversThe Affine Particle-In-Cell Methodという流体の詳細度を向上させる論文について、また、水面のアニメーションを高精細に変換する論文Water Wave Animation via Wavefront Parameter Interpolationについて、これらの合計三つの論文の説明をします。
各論文で、最初に全体的な説明をし、次に手法の主なアイデアと特徴(少し技術的な話)について説明します。
その後に、論文を理解する補助になりそうな情報も入れつつ、感想も書きました。

1. Restoring the Missing Vorticity in Advection-Projection Fluid Solvers

一つ目のこの論文は、流体シミュレーションのときに失われやすい動きの詳細を、計算コストの低い別の方法で求めて乱流のような渦として復元させて補う方法です。

解像度をあまり上げなくても、高い解像度の計算に近い動きを表現し、ダイナミックに変化していく乱流のような流体現象を作り出します。

渦の伸縮も考慮することが可能でリアルな表現が出来て、渦を復元させることで詳細な渦も失うことなく生成できます。

この論文を用いた結果動画は以下です(2:23付近に、ダイナミックに変化していく流れの例が出てきます)。

この論文の著者の公式サイトではソースコードも公開されています。

アイデアと特徴

新たにIVOCK (Integrated Vorticity of Convective Kinematics)という方法を使って、流体の移動を計算する移流項目で失われる流体の詳細情報を渦の方程式の違反に相当する部分とみなして、渦の復元をすることで渦情報の損失を防ぎます。

しかも、どのような移流項目計算を使っても本手法は適用出来るので、流体ソルバー依存になりにくく、この点も優れています。

移流計算の前後で渦情報の違い(速度場の差分)を求め、そして、さらに渦の伸縮を考慮します。そして、圧力計算については、よりコストの低いマルチグリッドのV-cycle計算を、復元する渦情報を基にして行います。その結果、失われた渦を効率的に復元出来るようになります。

移流項目で失われる回転成分としての渦(角運動量)に対して操作を行うことで、回転成分が継続的に自然と蓄積していきます。そのおかげで、例えばVorticity Confinementと呼ばれる渦情報を増幅する手法では難しいような、時間に伴う渦の大きさの変化などの表現が出来ます。

移流項目で失われる情報を補う既存手法はありましたが、(速度場の差分としての)損失情報を渦の情報としてはっきりと扱って、しかも、移流項目に結びつけながら渦を作り出す圧力計算の解釈を行ったという点が優れた方法となっています。

感想

渦の伸縮と圧力計算に相当するV-cycle系計算の組み合わせ次第で、渦の特定の形や動きを再現したりといった応用も出来そうです。(一方で、通常は圧力計算が、渦の挙動を支配的に決めることが多い)

火炎については、大きな圧力が空間に追加されたりするので、流体の非圧縮条件を満たしません。しかし、この手法では質量保存なども考慮して、火炎を扱えるように改良しています。このように、カスタマイズにも比較的適している手法だと思います。

Vorticity Confinement手法などから常に発展を続ける、渦による詳細を生成する方法ですが、ここまで関連手法が向上してきた過程も整理された論文で、詳細を追加する手法の中でも代表的な論文だと思います。論文中の研究のモチベーションや背景を読むだけでも過去の手法についていろいろな勉強が出来て、大変役立ちそうです。

ちなみに、以下はVorticity Confinement手法と本手法の比較画像です((c)本論文の著者、論文中から引用した画像です)。

左から3つ(上下共に)はVorticity Confinementを使って左から順に渦の増幅度合いを強くしていった例で、一番右(上下)の画像は本手法の適用結果です。本手法の方が比較的、全体的に均一でなく、大きさが動的に変化した流れになっていることが分かると思います。

渦に関して興味があれば、以下なども分かりやすいと思います(流体の制御についても、詳しく説明されているので)。
Vortex Based Smoke Simulation and Control

格子を使った流体シミュレーションについての基本的な話は、流体シミュレーションを使った自然現象の表現をご覧下さい!

2. Water Wave Animation via Wavefront Parameter Interpolation

二つ目の論文は、入力の水面のアニメーションを、細かい波面情報を作る関数を使って高解像度に変換する方法です。

この方法では純粋な流体シミュレーションを解かないので計算コストが低く、また、過去にあった波長関数などを使う方法よりもリアルで高精細なアニメーションが作り出せます。

また、水面波の要素として重要な屈折、反射、回折といった現象も実現できて、表現性の高い水面を生成することが可能になります。

この論文を用いた結果動画は以下です。

この論文の著者の公式サイトはこちら

アイデアと特徴

水面アニメーションを表現する入力メッシュに対して、それと交錯していくように波が移動していくと考えて、水面波の先頭をトラッキングします。そして、先頭とメッシュの接点の情報を基に細かい波を構築していきます。

Eikonal方程式という波動における波面を表現する方程式を使っていて、異なる波の振幅情報を合成します。さざなみや表面張力波などについても表現出来ます。

低解像度から高解像度への情報の補間を関数(本手法の独自の方法)を使って行うので、波の周波数のダイキスト限界を超えて、これまでよりも格段に多い数の、かつ、詳細な波面を作り出せます。
具体的には、波を表現するEikonal方程式が多価関数として扱われることになります。この関数を使って波の方程式を段階的に解くことで、複数の重なった波の位置や高さをよりスムーズで高精細な形で記述することが出来るようになっています。しかも、似た挙動の波を近似的に省略して扱える処理も行っていて、さらにコストを下げることに成功しています。

これにより、波長関数では特定のパターンの波の表現しか扱えなかったのに対して、複雑で色々な水面現象を表現可能です。
また、今回の方法で多価関数を使って波面のトラッキングが出来るようになった利点として、干渉や反射といったという波が移動していくときに起こる独特の現象が扱えることも挙げられます。

ちなみに、以下は水面アニメーションを表現する入力メッシュにおいて、波面の先頭をトラッキングしている様子です((c)本論文の著者、論文の動画から引用した画像です)。

感想

本手法を用いずに従来のように流体シミュレーションをした結果との比較もあり、遜色ない結果が得られていると思いますし、この手法の効果の高さが分かります。

制限としては、動的なオブジェクトの干渉はまだ扱えないですが、GPU実装にも対応できるアルゴリズムにもなっています。

水面の入力メッシュで、大きな乱流や極端に激しい動きをすでに取得できていると考えると、本手法は特に効果的であると思います。なぜなら、大きな乱流や極端に激しい動き以外の、流体シミュレーションではコストが非常にかかるような微細な動きについても、今回作り出すことが出来るからです。

Guide Shapes for High Resolution Naturalistic Liquid Simulationという既存方法もあり、この既存手法では水面の表面の特定の部分だけを高解像度で流体シミュレーションして、それ以外の部分は低解像度で計算します。この既存手法では、本手法ではあまり想定していない水しぶきなども表現出来ています。

今後は、多価関数をさらに発展させることで扱える流体現象のバリエーションを増やすことも出来るかもしれません。しかも、流体シミュレーションを解くよりは空間や時間依存性の制限を受けにくい関数ベースの方法なので、ユーザが調整しやすかったりと、制御の観点からもとても意義ある方法ではないでしょうか。
実際、ユーザが直感的に波の強さや大きさを細かく指定できる、ペイントツールのようなインタフェースを使ったデモも、論文動画の中に含まれています。

3. The Affine Particle-In-Cell Method

三つ目の論文は、計算を安定させつつも、流体の渦の成分が失われにくく出来る方法です。この方法を使うと、例えば、注いだワインがグラスにコリジョンして乱れながら、勢いよく外にこぼれるといった挙動が、安定した計算で実現出来ます。

この方法では、パーティクルと格子を用いたハイブリッド手法で、パーティクルは主に流体の移動とトポロジーの変化を扱い、また、格子の部分は主に力学的な情報やコリジョンを扱っています。

CGではよく用いられているFLIP法は計算の安定性に不安があるの対して、この方法では安定して計算が出来ます。しかも渦の成分もあまり失われずに維持できるということで、安定性と正確の両方を実現した論文になっています。



この論文の著者の公式サイトはこちら(動画もあり)

アイデアと特徴

流体シミュレーションをする場合は、計算項目ごとに項目に適した計算方法を選ぶことも多く、パーティクルと格子の2つを複合的に用いる方法もあります。そのような方法の代表的なものとしてまず、PIC法があります。
PIC法では、流体を一般的な流体の振る舞いを持つ非圧縮性にするために、格子で圧力計算を行うという方法が用いられています。
一方で、PIC法では移流項目のような流体の移動を計算する部分については、パーティクルで流体を運んで計算を行います。

今回の方法では、このようなPIC法が持つ数値拡散の問題を解決しました。そもそも、PIC法には、圧力計算をするためにパーティクル情報を格子情報に変換する部分があり、この変換部分で数値拡散の問題が起きていました。
変換時に、複数のパーティクルの情報が1グリッドの中に基本的に格納されるため、回転成分が特に失われてしまうからです。

本手法では最初に、角運動量をパーティクルに対して保持させることが出来るようにします。
次に、流体における回転や移動といった情報を考慮できるように、パーティクルと格子の変換時に、アフィン変換を用いることを考えます。
これにより、パーティクルと格子間の情報の受け渡しの際に、今回新たな情報として、角運動量も連続的に維持出来ることになります。

感想

既存手法に対して新たに今回必要となる、アフィン変換の行列演算や追加情報のデータも比較的小さく、現実的かつ効率的な方法と思えます。移流項目において数値発散が少ないFLIP法のように、数値誤差も少なくてすみますし、FLIP法が持っていた計算の不安定性についても改善出来ています。
このような優れた計算方法を、PIC法から発展させて提案したわけです。

また、色々な流体についての、本手法とPIC法との比較が豊富で、広く効果的な手法であることが分かります。FLIP法の安定性についても本手法との比較をしながら述べられているので、こちらについても勉強になると思います。

パーティクルに回転といった情報を持たせるという意味では、古典的なA Vortex Particle Method for Smoke, Water and Explosionsといった流れに沿って移動していく渦の情報を(パーティクル内に持たせて)注入するような手法がありました。一方で、パーティクルと格子間の情報の受け渡し部分に着目して、回転成分を出来るだけ維持するという本手法の試みは、とてもユニークだと思います。

今回の手法のように、パーティクルと格子の役割分担や目的を明確にして利用することで、非常に高い効果が出ることもこの度示されたので、そのあたりもさらに追求がなされる可能性もあります。過去には他にも、例えば爆発において火炎が急激に広がる高速の部分はパーティクルを用いて、爆発が収束して低速になったときは格子法に切り替えるといった手法もあります(Simulating Gaseous Fluids with Low and High Speeds)。
また、爆発のような高速で伝播する流体シミュレーションについてさらに興味があれば、爆発シミュレーションの制御もご覧下さい。

参考までに

パーティクルと格子の表現の違いについて知りたい場合は、以下も分かりやすいと思います。
Fluid Simulation For Computer Graphics: A Tutorial in Grid Based and ParticleBased Methods
以下はパーティクルを用いた既存手法のまとめの論文で、こちらも参考になると思います。
SPH Fluids in Computer Graphics

さらに、上記の2つの論文で登場した渦について詳しく知りたい場合は、以下も渦手法について比較されていて便利だと思います。
Research on Vortex-Based Fluid Animation

一方で、以下の動画のような元となるシミュレーションを、ウェーブレット関数を基にした処理を適用してさらに高解像度化する優れた研究もありますが、元となるシミュレーション手法自体を向上させた本手法とは性質が異なります。

DFでエフェクト室と共同で行っている高解像度化に関する研究開発について興味があれば、流体シミュレーションのアップレゾ(高解像度化)についてをご覧下さい!

全体の感想

最後になりましたが、今回の発表では流体シミュレーションをするときに生じる詳細(の損失)を、復元したり強化するための方法が目立ちました。
1つ目と3つ目の論文はいづれも色々な流体を扱えつつも、詳細計算についての着目点が異なります。また、2つ目の論文は水面の表現に特化していました。

このように優れた手法がたくさん世に出ている一方で、近年は流体の形状などの制御に関する論文が少ないように思います。
また、今年のシーグラフアジア2015でも夏に続いていくつか発表される、データベースを利用した流体手法についても高い可能性を秘めていて興味深いと思います。流体分野では、多岐にわたるテーマについてハイレベルな研究が行われていることを日々実感しています。

今回は以上となりますが、機会があれば他の流体系の論文も紹介出来ればと思います。
読んでいただき、どうもありがとうございました!


※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

[続WebGL入門] 3DCGソフトで作られたモデルデータをWeb上で描画してみる

$
0
0

久しぶりの投稿になります。TDの田辺です。

今回は、以前に投稿した

 [WebGL入門] javascriptを使って3Dプログラミングをやってみる

という記事の続編として書いていきます。
分からない点等ございましたら、前回の記事も参照して頂ければと思います。

では、進めます。
前回は、WebGLのJavascriptライブラリ 「Three.js」 を使用して、
スクリプト上でカメラやライト、オブジェクト(キューブ)を作成しWeb上に描画するという内容について紹介させて頂きました。

今回紹介させて頂くのは、

 3DCGソフトで作られたモデルデータをWeb上で描画してみる

というものです。
是非、チャレンジして頂ければと思います。

準備

 ■WebGLに対応しているブラウザ
  Mozilla Firefox 24.6.0 を使用いたします。

 ■「dftalk」フォルダ(作業フォルダ)
  どこでも良いので、dftalk という名前でフォルダを作成しましょう。

 ■three.js
  公式サイト からダウンロードできます。
  メニューの download から、 zipファイル をダウンロードしましょう。

  ファイルを解凍したら、必要なライブラリ本体は buildフォルダ 内に 「three.min.js」 として配置されているので、
  先ほど作成した 「dftalk」フォルダ「three.min.js」 をコピーします。

 ■convert_obj_three.py
  objファイル からThree.jsで読める jsonファイル にデータを変換するためのスクリプトです。
  three.jsを取得する際にダウンロードしたzipファイル内の

  mrdoob-three.js-d6384d2/utils/converters/obj/convert_obj_three.py

  に配置されているので、先程と同じく 「dftalk」フォルダ に コピーします。

  

 ■Maya
  バージョン2014を使用します。

 ■objExport プラグイン
  Mayaからモデルデータをobjファイルとして出力するために必要なので用意します。

 ■テキストエディタ
  メモ帳でも大丈夫です。

 ■python.exe
  モデルデータを変換する際にpythonスクリプトを実行するため用意します。

 ■モデルデータ (teapot.obj)
  今回、ティーポットのモデルデータを使用します。
  Wikipediaにも載っている有名なモデルだそうです。
  
  コチラのサイトの下の方にある teapot.obj というリンクの上で
  右クリック名前を付けてリンク先を保存「dftalk」フォルダ に保存します。

  

モデルに簡単にマテリアルを適用する

 ダウンロードした teapot.obj にマテリアルが適用されていないので
 Mayaで簡単に設定したいと思います。

 ■作業の流れ
 1.Mayaでモデルの色を変更(lambertのColorを変更)
 2.plug-inマネージャーでObjExportがロードされているか確認
 3.モデルを選択
 4.上部メニュー > Export Selection を選択
 5.File of Type を OBJexport に変更
 6.ファイル名を teapot.obj に設定して、 「dftalkフォルダ」を選択して出力
 
 マテリアルを設定しているので、同じ場所に teapot.mtl というファイルも作成されたかと思います。
 このmtlファイルにマテリアルの情報が保存されています。
 mtlファイル自体はわりとシンプルな構造をしているので、mayaなどを使わず自前で作ることもできます。
 MTLファイルの書式についての参考ページ

 ■適用前
  

 ■適用後(色をオレンジに)
  

モデルデータを変換する

OBJのモデルデータをThree.jsで扱える json形式のデータに変換します。

 convert_obj_three.py

 というスクリプトを使用します。
 このスクリプトは、公式で配布されているものです。

 batファイルで簡単なツールを作って実行する流れで行きたいと思います。

■convert_obj_three.pyの実行用batを作成

 下記の内容でファイルを作成し、 「dftalkフォルダ」 に入れます。

 ■convert_obj_three.bat
[text]
@echo off
::カレントディレクトリをこのファイルの置かれている場所に設定
cd /d %~dp0

起動するpython.exeまでのパスを記入(“C:\Program Files\python\python.exe”など) %~dp0convert_obj_three.py -i %1 -o %~n1.js

pause
[/text]

  ※「起動するpython.exeまでのパス」 の部分は、各自のpython.exeのインストールされている場所によって変わります。

  ■%から始まる文字について
   bat特有の書き方でそれぞれ下記のような機能があります。
   
   「 %~dp0 」
    ・batファイルの置かれているディレクトリパスを展開

   「 %1 」
    ・batファイルに対して、ドラッグアンドドロップされたファイルのパスを展開

   「 %~n1 」
    ・batファイルに対して、ドラッグアンドドロップされたファイルの名前を展開
   
  ■-i や -o などのフラグについて
   convert_obj_three.pyで使用するフラグです。

   convert_obj_three.py -i <変換元のobjファイルのパス> -o <変換後に出力するファイル名>

■batファイルを実行する

 先程作成したbatファイルは、変換対象の objファイルbatファイル の上にドラッグ&ドロップすることで実行されます。

 

 コマンドプロンプトが開き、実行結果が表示されます。

 

 変換後のファイルは、json形式のデータで、「objファイル名.js」というかたちでdftalkフォルダに保存されています。
 今回の場合は、teapot.js という名前で保存されているかと思います。

 

 これでWeb上に描画出来る形式に変換が完了しました。 
 では、いよいよWeb上に描画する作業に移りたいと思います。

Web上にモデルデータを描画する

■HTMLファイルを作成する

 まずは、下記のコードをテキストエディタにコピーし、 index.html という名前で 「dftalkフォルダ」に保存して下さい。
 Three.jsについての説明に関しては、前回の記事で紹介しているため割愛させて頂きます。

[javascript]






// シーンの作成 ------------------------------------------ var scene = new THREE.Scene();

// カメラの作成 ------------------------------------------ // fov: 画角(視野角) var fov = 75;

var height = 600; // 縦幅 var width = 400; // 横幅 // aspect: アスペクト比、カメラで撮影したものの縦横比 var aspect = height/width;

// near: ニアークリップ、 カメラからの撮影開始位置、これより近いものは撮影しない var near = 1; // far: ファークリップ カメラからの撮影終了位置、これより遠いものは撮影しない var far = 5000;

// カメラ作成 var camera = new THREE.PerspectiveCamera(fov, aspect, near, far); // カメラ配置 camera.position.set(100, 100, 100); // (x, y, z)

// ライティングを設定する ------------------------------------ var color = 'white'; // 光の色

// ライトオブジェクト作成 var directionalLight = new THREE.DirectionalLight(color, 1.0); directionalLight.position.set(-2, 1, 1).normalize(); // 光源の角度を設定 scene.add(directionalLight); // シーンに追加

// 屋外のライティング効果をリアルにしてくれるそうなので、もうひとつライトを追加 var hemisphereLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.25); scene.add( hemisphereLight );

// オブジェクトを配置する ---------------------------------------- // ジオメトリーの作成 var geometry = new THREE.CubeGeometry(50, 50, 50); // サイズ設定(x, y, z) // マテリアルの作成 var material = new THREE.MeshPhongMaterial({color: 'orange'}); // メッシュの作成 cube = new THREE.Mesh(geometry, material); cube.position.set(-50, 0, 0); // 位置を設定(x, y, z) scene.add(cube); // シーンに追加

// レンダラーの追加 ------------------------------------------- var renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize(height, width); // Canvasのサイズ設定

// 背景を灰色に変更 (背景が真っ暗になるのを回避) renderer.setClearColor(0xdddddd, 1 );

// レンダリング ----------------------------------------------- function render() {

// 回転アニメーションを追加 requestAnimationFrame(render);

cube.rotation.y += 0.01; // y軸に0.01加算

// シーンとカメラを渡してレンダリング(表示) renderer.render(scene, camera); }

// カメラコントロール ----------------------------------------- var controls = new THREE.OrbitControls(camera);

// HTMLの読み込みが完了してから実行する window.onload = function(){ // render-area内に描画用エレメントを追加 document.getElementById('render-area').appendChild(renderer.domElement); render(); }



[/javascript]

 前回の記事で作成したものをベースに少し修正を加えました。

 ■修正箇所
  ・カメラのfarの値を大きく
  ・カメラの初期位置を変更
  ・directionalLightの角度を変更
  ・hemisphereLightを追加
  ・Cubeを少し大きく
  ・rennderar.setClearColorを追加し、背景が真っ暗だったのを灰色に変更
  ・render-areaというIDを持つdev要素内で描画するように変更

 コードが書けたら、HTMLファイルをブラウザで開いてみましょう。
 設定の変更により前回よりもオブジェクトがだいぶ見やすくなったと思います。

 
 デモページ!

 では、次にCubeの隣にモデルデータを表示したいと思います。

■モデルデータをシーンに追加する

 66行目の レンダラーの追加 と書かれている行の上に下記のコードを追加します。

 JSONLoaderというものを使用してモデルデータを読み込みます。

[javascript num=66 highlight_lines="66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86"]
// モデルデータをシーンに追加
var jsonObj, faceMaterial; // オブジェクトとマテリアルの変数定義

// JSONLoaderを使ってモデルデータを読み込む
loader = new THREE.JSONLoader();
// 引数としてモデルデータのジオメトリーとマテリアルが入ってきます
loader.load( “teapot.js”, function ( geometry, materials ) {

// モデルデータのマテリアルを作成
faceMaterial = new THREE.MeshFaceMaterial( materials );

// フェースを両面とも表示するための設定
for(var i = 0, len = materials.length; i < len; i++){
materials[i].side = THREE.DoubleSide;
}

jsonObj = new THREE.Mesh( geometry, faceMaterial );
jsonObj.position.set( 50, -25, 0);
jsonObj.scale.set( 15, 15, 15 );
scene.add( jsonObj );
});

// レンダラーの追加 -------------------------------------------
[/javascript]

 画像のようにCubeのとなりにティーポットが表示されたでしょうか。

 
 デモページ!

■モデルデータのマテリアルを変更する

 では、最後にモデルデータのマテリアルに手を加えて、見た目を変更したいと思います。
 load関数の中を下記のように変更します。

[javascript num=66 highlight_lines="74,75,76,77,78,79,80,81,82,83"]
// モデルデータをシーンに追加
var jsonObj, faceMaterial; // オブジェクトとマテリアルの変数定義

// JSONLoaderを使ってモデルデータを読み込む
loader = new THREE.JSONLoader();
// 引数としてモデルデータのジオメトリーとマテリアルが入ってきます
loader.load( “teapot.js”, function ( geometry, materials ) {

// // モデルデータのマテリアルを作成
// faceMaterial = new THREE.MeshFaceMaterial( materials );

// // フェースを両面とも表示するための設定
// for(var i = 0, len = materials.length; i < len; i++){
// materials[i].side = THREE.DoubleSide;
// }

// 新しくマテリアルを作成
faceMaterial = new THREE.MeshPhongMaterial({color: 'pink', side: THREE.DoubleSide});

jsonObj = new THREE.Mesh( geometry, faceMaterial );
jsonObj.position.set( 50, -25, 0);// 位置を設定(x, y, z)
jsonObj.scale.set( 15, 15, 15 );// スケール設定
scene.add( jsonObj );// シーンに追加
});
[/javascript]

 ティーポットがオレンジ色からピンク色に変更されたでしょうか。
 このようにモデルを読み込んだ後は、マテリアルなどの設定を変更することが出来ます。

 
 デモページ!

あとがき

 ここまでお付き合い頂きありがとうございました。
 今回は、ティーポットの軽いモデルだったため、スムーズに描画まで出来ましたが、
 ポリゴン数が多いモデルなどでは、上手くいかない場合があるかもしれません。
 色々試して頂ければと思います。
 いずれこの技術を使ってWeb上で3Dモデルライブラリなどが作れたりしたら面白いですね。
 今後も調査を続けて行きたいと思います。


※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください


Shotgunをより活用していくために

$
0
0

こんにちは、TD室の森本です。

今回はMotionBuilder周りの記事から離れて
最近よく関わることになったShotgunについて書いてみようと思います。

Shotgunとは?

Shotgunとはプロジェクトの管理ツールで下記に公式ページがあります。
Shotgun公式ページ
AreaJapan Shotgun紹介ページ

また、DFTalkでは先日行われたCEDECでのShotgunに関連するセッションの記事も以下に掲載しています。
DFTalk 【来場御礼】CEDEC 2015セッション

Shotgunの機能について簡単に紹介していくと

アセットのバージョン管理

アセットバージョン管理例
上図のようにアセットのバージョンを登録して管理することができます。
上図ではキャラクターに対してですが、アセットワーク、ショットワークともにバージョン管理することで、
各チーム間のパイプラインフローの向上に役立ちます。

例えば、アセットチームが新しいバージョンのキャラクターをUPした場合に、
すでに作業を開始していたアニメーションチームに通知がくることで、
スムーズにシーン内のアセットを最新のものに変更することができます。
さらに変更内容やこれまでの履歴についても簡単に確認できます。

RVPlayerとの連携レビュー機能

RVPlayerとの連携機能
RVPlayerとの連携機能により、Shotgun上に登録されたチェックデータをPlayer上で再生することができ、
修正内容を書き込んだ際に同時にShotgunにも登録されます
これまでの修正内容も履歴として閲覧できるため、スムーズなチェック体制を構築できます。

プロジェクトの進捗管理

Shotgunページ作成
Shotgunのデータベースに入力したデータから必要な情報をウィジェットをつかって、
レイアウトを自由に設定したページを作成できます。
Shotgunのデータベースを中心にすることでリアルタイムに情報が更新されるため、
プロジェクトの制作進捗管理にも有効に活用できます。

簡単に紹介しましたが他にも様々な機能がありますので、詳細は上記リンク先で確認してください。

今回はテクニカル面の話としてよりShotgunを有効活用していくために、
Shotgunのデータベースとページについての話を中心に書いていきます。

現在Shotgunのバージョンはv6.3.2を使用しているため、上記バージョンでの記事になりますが
今回は概要的なことを書いてますので大きくは変わらないと思われます。

Shotgunのデータ構造

まずはShotgunのデータ構造がどうなっているかについて書いていきます。
Shotgunのデータ構造は以下の図のような構造をしています。
Shotgunデータ構造1
まずAsset、Sequence、Shot、Task、People、Note等の各種大きな枠組みのEntityと呼ばれるデータがあります。
それぞれのEntityはFieldというデータをもっています。

もう少し具体的な例に置き換えた図が以下になります。
Shotgunデータ構造2

Shotgunでの表示事例

上図では例としてShotEntityを中心に構造例を挙げています。
Shotgun上の見え方として、Grouping設定等の表示設定をすることで
Sequence>Shotのようなツリー構造の表示にすることはできますが、
中身のデータ構造としてはそれぞれSequenceとShotのテーブルが別々に存在しています。

Shotgunでは標準でAsset、Shot、Note等の基本的なEntityが用意されています。
単純な例として一部ではありますが、Entity同士がつながっているイメージは以下のようになります。
このほかにも標準の状態でも多数のEntityがあり、Entity同士の繋がりも書ききれないため下図がすべての構造ではありません。
Entityの繋がり
また、上図ではEntityに対して説明が入っていますが、Entityの使い方自体もカスタマイズできるので一例として考えてください。
デフォルトで用意されているEntityでも十分な種類が用意されていますが、
さらにCustom枠として自由に設定できるEntityも多数用意されているので、
それぞれの会社のパイプライン、ワークフローにあわせたデータ構造を持たせることができます。

Entityのカスタマイズ以外にEntity内のFieldに関しても追加でカスタマイズが可能になっていて、
例えばあるプロジェクトで、そのプロジェクト特有の情報を入れるための枠がほしい場合も、
プロジェクト毎や会社全体の枠かの選択も含めたカスタマイズができます。
権限設定も閲覧、編集をField毎に細かくカスタマイズできるため、
管理側のみ閲覧したい情報の枠を設定するといったことも可能です。

Shotgunのページについて

Shotgunのページは上記で説明したデータ構造から、Filter設定を通して必要なデータに絞り込んだものを表示することで作られています。

Shotgunページについて

Entity同士がFieldでリンクされて繋がっているため、様々なFilter設定が可能です。
ページに関しては全員で共有するShareページとPrivateページがあり、以下の図のような特徴があります。

よくあることとしてデザイナーが自分用に進捗管理するために、
担当の表を表計算ソフト等をつかって管理していることがありますが、
上図のようにShotgunのPrivateページを使うことで、
自分の担当分だけを表示した表の個人用のページを簡単に作ることができます。

すべてのページの情報元になっているデータベースは共通のものを利用しているため、チームリーダーや制作管理者は、
自分の担当チームのメンバーに絞りこんだページを設定することで、進捗状態をリアルタイムで管理することが可能です。

ツールとの連携について

Shotgunについて簡単に説明してきましたが、運用する上で重要なこととして情報の更新頻度があります。
Shotgunを中心にしてパイプラインを構築する上では避けては通れない部分ですが、すべての情報を常に手動で更新し続けるのは
ヒューマンエラーや情報の遅延を招くため、ツールとの連携は必要不可欠です。

Shotgunのデータベースにアクセスする方法としてPythonAPIがあります。
ShotgunPythonAPI
基本的にはWeb上でのShotgunへのアクセスと同じ仕組みになっていて、
情報を取得する場合はEntityとFilter設定の指定からデータを取得し書き換えたりすることができます。
上記APIを使えばPythonの知識とツールの知識があれば、
各会社のパイプライン、ワークフローに合わせた既存ツールとの連携も可能ですが、
1から作っていくことになるので検証も含めた時間がそれなりに必要になります。

他の方法としてはワークフロー、パイプラインをある程度あわせる必要がありますが、
Shotgunから提供されているShotgunPipelineToolKitを使うこともできます。
ShotgunPipelineToolKit
こちらを使えば特別な設定なしに対応している各DCCツールで、Shotgun用のメニューが追加されて、
そのメニューにある項目からファイルのOpen、Save、Publish等の処理を行うことができます。
Shotgunのメニューから実行された処理は自動でShotgunのデータベースとも連携されます。
またpythonの知識があれば設定ファイルと処理用のPythonスクリプトを追加編集することで
比較的簡単にカスタマイズも可能なつくりになっています。

まとめ

ここまで説明してきましたがShotgunはカスタマイズ性が高く、
会社毎のワークフロー、パイプラインに併せて設定を細かく変更することができます。
また、設定に関してもShotgunのWEBのUIベースでデータの構造さえわかっていれば、
Entity、Fieldの設定やページの設定ができるため、
プロジェクトのチームリーダーや制作管理の方にも慣れれば使いやすいと思います。
また、Privateページもあるのでデザイナー単位の個人レベルで使いやすいページを構築できます。
表示がWEBベースで権限設定が細かく追加できることもあり、外注先、子会社との情報共有にも使いやすいです。

ただし、Shotgunベースでパイプラインを進めるには情報の更新は重要であり、
中途半端に導入した場合は余計に混乱する事態になりかねません。
ShotgunのWEBから直接情報を書き換えたり、データをUploadしたりすることは可能ですが、
極力Shotgun上に登録する情報を増やしたほうがデータの構造上リンクを辿ってとれる情報が増えるため、
すべてを手動でまかなうのは現実的ではないのでツールでの自動化のサポートは必須だと思います。

DFでもShotgunを導入後ツールとの連携を進めていますが、まだまだ改善していける余地は残っているので日々改良を進めています。

今回はDFで独自にカスタマイズしている部分は置いておいて、まずはShotgunのベースの部分について書いてみました。
また何か機会があればAPI周りやToolKit周りの話を書いてみようかと思います。

それでは、また。

※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

【3dsMAX】MAXScriptでPythonつかってみた【MaxPlus初級編:part01】

$
0
0

みなさんはじめまして。今年入社した新米TD市川です。
そんな私もついにDF TALKを執筆させて頂く事になりました!温かい目で見ていただければ幸いです。

はじめに

今回紹介していく内容は、MAXにおけるPythonの扱い方の紹介です。
Mayaを使っている人にとっては馴染み深いPythonですが、MAXも2014から実装されて、かれこれ2年以上の月日が経ちましたね。それからバージョンも上がり、2015ではDF_TALKで紹介することも多いPySideが扱えるようになりました。弊社では最近になって2015を導入しましたので、これを一つのきっかけにと、MAXをテーマに取り上げています。

MAXScriptでPythonを実行してみよう!

MAXScriptでPythonを実行する方法は二通りあります。
①Python形式の文字列を実行
[code]
python.Execute("print 'Hello Python'")
--もしくは
python.Execute "print 'Hello Python'"
[/code]
この時注意が必要なのはMAXにおける文字列についてです。
MAXScriptでは「”(ダブルクォーテーション)」で括ったもは文字列、
       「’(シングルクォーテーション)」で括ったものはオブジェクトなどを表すときに使います。
Pythonでは「”」と「’」をどちらとも文字列として扱えるので、MAXScript上での取扱いには注意が必要です。

②Pythonファイルを指定して実行
[code]
python.ExecuteFile("pythonファイルのパス")
--もしくは
python.ExecuteFile "pythonファイルのパス"
[/code]
この実行方法ではPythonファイルが必要なので簡単なスクリプトをPython形式で保存して実行してみましょう。

ではさっそく①の方法で実行してみました。
実行の仕方は、上部メニューから、[MAXScript->MAXScript Listener]もしくはショートカットキーの[F11]でListenerウィンドウを開きます。ピンク色の部分(マクロ レコーダ)にソースを貼り付け、Enterキーを押します。

下の白色の部分(出力)にメッセージが表示されました。

無事に実行できたでしょうか?

pythonでMAXScriptを実行してみよう!

ようやくタイトルにあった「MaxPlus」の登場です。MaxPlusとはPython内でMAXScriptを使用するためのAPIになります。
ここでお気づきの方もいるかとは思いますが、MAXScriptでPythonを実行して、さらにその中でMAXScriptを実行するのが基本的な運用方法となります。…ややこしいですね。

そしてさらにややこしいのはその使い方にも見られます。
以下のスクリプトをPython形式で保存し、新規シーンで実行してみましょう。
[python]
# -*- coding: utf-8 -*-

# MaxPlusの読み込み。
import MaxPlus
# 指定されたオブジェクトを作成。今回はボックス。
boxObject = MaxPlus.Factory.CreateGeomObject(MaxPlus.ClassIds.Box)
# 指定されたノードを作成。
boxNode = MaxPlus.Factory.CreateNode(boxObject, ‘testObject’)
[/python]
シーンの原点に「testObject」という名前のボックスが1つ出来たと思います。

オブジェクトとノード

ボックスを一つ作るだけのはずなのに結構長い文章を書かなければなりません。
Mayaでスクリプトを書いたことがある人であれば、なおさら感じることだと思います。

先ほどのスクリプトを見てみましょう。
処理の中で2回呼び出されている「Factory」というクラスがありますね。このクラスは何かを作るときに使用するのですが、なぜ1つのボックスを作るのに2回も実行しなければいけないのでしょうか?

まず「オブジェクト」についてみていきましょう。
新規シーンにして、上のスクリプトを少し変更したこちらを実行して下さい。
[python highlight_lines="5,6,7"]
# -*- coding: utf-8 -*-

import MaxPlus
boxObject = MaxPlus.Factory.CreateGeomObject(MaxPlus.ClassIds.Box)
# 同じ処理を三回。
for i in range(3):
boxNode = MaxPlus.Factory.CreateNode(boxObject, ‘testObject’)
[/python]
今度はボックスが3つ作られると思います。この状態はモデルを複製したときの「Instance(インスタンス)※」にあたります。
つまり、編集した情報を共有している状態となります。
※インスタンスについては以下のHelpを参照して下さい。
3dsMax Help:コピー、インスタンス、参照を作成する

試しにパラメーターをいじって確認してみて下さい。…せっかくなのでそれもプログラムでやってみましょう。
先ほど作成したノードからオブジェクトを取得しパラメーターを変更してみます。以下のスクリプトを実行して下さい。
[python highlight_lines="8,9,10,11"]
# -*- coding: utf-8 -*-

import MaxPlus
# 先ほど作ったノードを取得。
boxNode = MaxPlus.INode.GetINodeByName(‘testObject’)
# ノードからオブジェクトを取得。
boxObject = boxNode.GetObject()
# オブジェクト内にあるParameterBlockの各アトリビュート値を変更。
boxObject.ParameterBlock.LengthSegs.Value = 5
boxObject.ParameterBlock.WidthSegs.Value = 5
boxObject.ParameterBlock.HeightSegs.Value = 5
[/python]
こちらが実行結果です。結果が判りやすいようにボックスはずらしてあります。

作成したボックス全ての分割が増えていると思います。Modifierを割り当てた場合も共有されます。
ここでなんとなく「編集情報はオブジェクトの中にある」ということがわかったと思います。

では次に「ノード」はどういったものなんでしょう?
プログラムを使って確認してみましょう。新規シーンにして実行してみて下さい。
[python highlight_lines="9,10,11,12,13,14,15"]
# -*- coding: utf-8 -*-

import MaxPlus
boxObject = MaxPlus.Factory.CreateGeomObject(MaxPlus.ClassIds.Box)
# 分割数を増やします。
boxObject.ParameterBlock.LengthSegs.Value = 5
boxObject.ParameterBlock.WidthSegs.Value = 5
boxObject.ParameterBlock.HeightSegs.Value = 5
for i in range(3):
# 名前に数字を付けて差別化。
boxNode = MaxPlus.Factory.CreateNode(boxObject, ‘testObject’+str(i))
# スケールをかけて段々大きくする。
boxNode.Scaling = MaxPlus.Point3(i+1, i+1, i+1)
# 位置を見やすいように100刻みで増やす。
boxNode.Position = MaxPlus.Point3(i*100, 0, 0)
[/python]
こちらが実行結果です。

ボックスが横に並んで表示され、今度は名前も、大きさも違うと思います。これらの変更はノードから行っています。
つまり「位置や大きさなどの表示情報はノードの中にある」ということがわかります。
再度オブジェクトについて確認すると一度しか分割数を変更していませんが全て変更されていますね。
もし全部別々に編集したい場合は2行目~6行目をループの中に入れてあげれば別々に編集できるボックスを作ることが出来ます。

遠回りしてしまいましたが、ボックス一つを作るのにあれだけ書く理由がわかっていただけたでしょうか?

MaxPlusをもっと使ってみよう!

ここまで読んでいただければボックスを好きなように並べて大きさも変えられるようになりました。
というわけで、ひたすら沢山のボックスを並べてみましょう。今回は個別に編集するのでオブジェクトはループの中で作ります。
[python highlight_lines="14,15,16"]
# -*- coding: utf-8 -*-

import MaxPlus
for j in range(10):
for i in range(10):
x = i * 30
y = j * 30
boxObject = MaxPlus.Factory.CreateGeomObject(MaxPlus.ClassIds.Box)
boxNode = MaxPlus.Factory.CreateNode(boxObject, ‘testObject_%s_%s’%(str(i), str(j)))
boxNode.Position = MaxPlus.Point3(x, y, 0)
# 偶数の場合はなにもしない。
if ((i+j)%2) == 0:
continue
# 奇数の場合はmeshsmoothをかける。
smoothModifier = MaxPlus.Factory.CreateObjectModifier(MaxPlus.ClassIds.meshsmooth)
boxNode.AddModifier(smoothModifier)
# ついでにスケールもかけて位置を調整。
boxNode.Scaling = MaxPlus.Point3(1.5, 1.5, 1.5)
boxNode.Position = MaxPlus.Point3(x, y, -6.25)
# 分割数も増やしてみる。
boxObject.ParameterBlock.LengthSegs.Value = 5
boxObject.ParameterBlock.WidthSegs.Value = 5
boxObject.ParameterBlock.HeightSegs.Value = 5
[/python]
全部で100個のボックスが出来たと思います。
ただ並べるだけではつまらないので1つおきにモディファイアの「meshsmooth」を使い、位置や大きさを調整しました。

こちらが実行結果です。

マテリアルを設定していないのでデフォルトの色がまばらに付いてしまっていますね。

せっかくなのでこちらも設定してみましょう。
[python highlight_lines="11,12,13,14,15,16,17,18"]
# -*- coding: utf-8 -*-

import MaxPlus
for j in range(10):
for i in range(10):
x = i * 30
y = j * 30
boxObject = MaxPlus.Factory.CreateGeomObject(MaxPlus.ClassIds.Box)
boxNode = MaxPlus.Factory.CreateNode(boxObject, ‘testObject_%s_%s’%(str(i), str(j)))
boxNode.Position = MaxPlus.Point3(x, y, 0)
# マテリアル作成
r = (float(j + 1) / 10.0)
g = (float(i + j + 2) / 20.0)
b = (float(i + 1) / 10.0)
defaultMaterial = MaxPlus.Factory.CreateDefaultStdMat()
defaultMaterial.Diffuse = MaxPlus.Color(r, g, b)
# 作成したマテリアルを設定
boxNode.Material = defaultMaterial
if ((i+j)%2) == 0:
continue
smoothModifier = MaxPlus.Factory.CreateObjectModifier(MaxPlus.ClassIds.meshsmooth)
boxNode.AddModifier(smoothModifier)
boxNode.Scaling = MaxPlus.Point3(1.5, 1.5, 1.5)
boxNode.Position = MaxPlus.Point3(x, y, -6.25)
boxObject.ParameterBlock.LengthSegs.Value = 5
boxObject.ParameterBlock.WidthSegs.Value = 5
boxObject.ParameterBlock.HeightSegs.Value = 5
[/python]

こちらが実行結果です。

今度はついでにグラデーションを付けてみました。
RGBの順番を入れ替えたり数値の出し方を変えたりして遊んでみて下さい。

あとがき

ここまでお付き合い頂き、ありがとうございました。
MAXScriptもMaxPlusも始めはわかりづらい所が多いとは思いますが、使っていくうちに愛着がわいてきたり、こなかったり。
今回は初めて取り扱うものであったため基礎的な部分だけになってしまいましたが、
これをきっかけに「MAXでPythonを使ってみようかな?」と思っていただければ幸いです。
次回はもう少し実用的な部分に触れてみようかと思います。

※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

crowdツールノウハウ ~Massiveでbrain、略してMassiverain~

$
0
0

[あいさつ]

お久しぶりです。
最近はめっきりcrowdというよりもTD色が強まっている
crowdTDの 護守(ごのかみ) です。

11月ですね。
ということは今年も残すところわずかということですね。あ…
 
 

さて、今回はTD色が強まっている自分をちょっとcrowd色にゆり戻して、
さらには初心を振り返るということでちょっと前の記事を掘り起こし・掘り下げてみたいと思います。
題して、

『Massiveでbrain、略してMassiverain』

けっこうニッチな内容なんで、需要はあるのか怪しいですが、
私の記事は大抵ニッチですからね仕方ないですね。
 

前回の記事:
crowdツール共通のブレインの考え方 ~デザイナーでもわかる群集入門~

 
 

[導入]

日本でMassiveのノウハウ記事ってかなり珍しいんじゃないでしょうか?
もしかして、これが最初で最後かもしれませんね。
 
 
今回は車のbrainを作っていきます。

「車の…ブレイン…?」

って感じですが、要は道路をプロシ-ジャルで走る車を作ります。
この記事では基本的な考え方の部分だけを紹介します。

ひとつひとつのコネクションを見ていってもいいんですが、
ものすっごい長編になってしまうので、細かいつなぎ方とかは省きます。

ちなみにこれは、私がMassiveのbrainの勉強をしているときに作ったものなので
記憶が曖昧な部分もありますが、ご容赦を。
 
 
とりあえず先に結果だけ見せておきましょう。

 
 
 
 

1. 条件の確認

さて、まずはこの記事
crowdツール共通のブレインの考え方 ~デザイナーでもわかる群集入門~
でも書いたように条件を明らかにしていきます。
 

今回は題材が「道路を走る車」とちょっと曖昧ですので、
噛み砕いていく必要がありますね。

「道路を走る車」に最低限必要なルールを列挙していきます。

A. 道路をはみ出さない(道路に沿って走る)
B. レーンによって進行方向は統一
C. 他の車に衝突しない
D. 地面の傾斜に沿う

最低限としてはこんなところでしょうか。
実はこれでもまだ曖昧なところがあるので、これらを更に細分化していきます。
 
 
A. 道路をはみ出さない(道路に沿って走る)
・レーンの右側寄りになったら左側に修正(逆も然り)
・レーンをはみ出してしまった場合レーンに復帰

 
B. レーンによって進行方向は統一
・レーンの方向を取り、その方向に走る
・逆向きだった場合方向転換する

 
C. 他の車に衝突しない
・前方の近い位置に車があった場合回避
・前方の近い位置に車があった場合減速
・上記二つでも衝突しそうな場合は停止

 
D. 地面の傾斜に沿う
・地面の傾きを検知して車を傾かせる

 
 
 
B, Dは比較的簡単に実現できます。
この二つは、やり方がもはやテンプレートみたいなものなので、それに合わせてやればOKです。
レーンの向きや地面の傾きはMassive内で
チャンネル(MayaでいうAttrのようなもの)を使うことで簡単に取得できます。

何気に難しいのがA, Cです。
個人的にはAのはみ出してしまった場合の対処が一番手こずりました。
 
 
 
 

2. 制御構造の構築

さて次は実際に作っていくにあたっての制御構造を考えていく必要があります。
crowdツール共通のブレインの考え方 ~デザイナーでもわかる群集入門~
この記事での例は簡単な内容だったのでここをすっ飛ばしていますが、
今回は色々と複雑なのでここをしっかりと組みます。
 
 
まずは「制御構造とはなんぞや?」ってことなんですけど、
Agent自身を動かすための直接的な情報を統合する構造のことです。
以下具体例入れて説明しますが、文章がちょっと硬いので読み飛ばしてもらってもかまいません。
 

Agent自身が持っている情報はTranslate, Rotate, Scaleといったものがあります。
今回はroot以外に骨は考えていませんので、
この九つの情報(各三軸あるので3×3)をどのように出力していくかが重要となります。
1.で考えたルールに沿わせるためにTranslate, Rotate, Scaleに値を実際に出力していきます。
たとえば、車がレーンの右寄りの位置を走っていた場合、ルールAが適用されます。
ルールAを解決するため、必然的に車を今よりも左側に寄せないといけません。
つまり、出力としてはRotateYに左向き、つまりマイナスの値を入れることになります。
ところが、単純にRotateYにマイナスを入れるだけだと、左側の車にぶつかってしまいます。
というわけで、ルールCが適用されRotateYに反対の値プラスが入ります。
ここで、単純に上書きしてしまうとルールCを守るためにルールAが破られるというジレンマが発生します。
これを回避するために、これらを統合し同時に扱うためのものが制御構造というわけです。

 

わかりやすくいうと各ルールから来る情報を足し算なり掛け算なりして最終的な結果を出す構造ってことですね。
今回はScaleを考える必要がないので、実質TranslateとRotateだけを制御すれば問題ありません。
もっというと、車の構造上TranslateもZ軸だけ考えればいいですし、
Rotateも地面の傾き用のX軸とZ軸を除けば、Y軸だけを考えればいいんですね。
 
余談ですが、車の制御は「アクセルとハンドルという二つの少ないインプットで制御できるもの」
としてよく引き合いに出されます。
そのアクセルとハンドルがそれぞれTranslateZとRotateYに相当するということですね。
 
 
 
 
さて以上を踏まえて、それぞれのルールにおける制御で必要なものを割り出します。

A. RotateY
B. RotateY
C. TranslateZ, RotateY
D. RotateX, RotateZ

これを扱いやすい処理にしていきます。
RotateYにはHighとLowのプライオリティをつけて、
RotateYを合成するだけでなく各ルールの優先も決められるように準備しておきます。
TranslateZも同様にHighとLowのプライオリティなどを用意しておいて柔軟に対応できるようにしておきます。
制御構造は、後でモジュールを組み立てていく際に、必要になる値があったりして付け足したりすることがあるので、
とりあえずの構造だけ組み立てておけばOKです。

 
(クリックで拡大)
 
 
 
 

3. モジュールの組み立て

これで必要な情報がそろいました。
まずは1.の基本ルールから2.の制御構造へ渡す大まかな流れを組みます。

(クリックで拡大)
こんな感じになりました。
左側にあるのが基本ルール(1.)モジュールで、
右側にあるのが制御構造(2.)モジュールですね。
真ん中にあるSpeedというのは移動速度調整用のモジュールです。
このSpeedモジュールは単に遅い速いを調節するだけでなく
加速度や慣性っぽい動きをするための仕組みも組み込まれています。
 
 

Laneモジュール

このモジュールはルールA, B用になります。
レーンに関する情報を取得するものはここにまとめました。

(クリックで拡大)
一番下のノード群はレーンから外れたとき用の処理です。
レーンから外れてしまうと自分がレーンのどちら側にいるのかを判別できなくなってしまうので
レーンに復帰するというのが難しいんですよね。
これは、直前までの位置を取得して覚えさせることで解決しました。
 
 

Vehicular gapモジュール

いわゆる車間距離です。

(クリックで拡大)
ここでのポイントは目の前の車の求め方。
MassiveではvisionといってそのAgentから見た情報より
視界に何かあるかを判断する機能があります。
しかし、この機能は処理がかなり重くなってしまうため通常では使いません。
では、何を代わりに使うかというとsoundを使います。
Agentはそれぞれ音を発することができます。
音といっても実際に音を鳴らしているわけではなく、
自分の周囲にある情報を拡散することで自分の位置などの情報を他に報せているんですね。
それを総じてsoundとMassiveでは呼んでいます。
このsoundを利用して、自分の前方にいるものをとってきます。

ただ、ここで注意しないといけないのが、
前方といっても隣のレーンにいる車は取得しないようにすることです。
もし、とってしまうと対向車線に車が来たらその都度変な動きをしてしまうのです。
soundは聞こえてくる方向も取得できますので、その情報を利用します。
 
 

Speedモジュール

先程も述べましたが、スピード調整用のモジュールです。
加速度や慣性っぽい動きをするための仕組みをいれて、車っぽい動きになるようにしています。

(クリックで拡大)
 
 

付加要素

とりあえず上記の状態で実行するとこんな感じになります。

あとはちょこちょこと要素を足していきます。

・複数車線
・レーンの分岐・変更
・信号の解釈
・障害物を避ける
・交差点での減速
・カーブでの減速
・傾斜での減速・加速
・車線による車の優先度

これでもまだ足りないと思いますが、車を走らせるだけでもけっこうルールがあるのがわかると思います。
更には、それらをひとつひとつ実現するにも地味に工夫が要ります。
 

【複数車線】

デフォルトだと、レーンの真ん中しか走りませんので、
これをレーンの右側と左側を走れるようにします。
今、自分がレーンの左右どちら側にいるかを取ることができますので、
その情報を利用し、右寄りを真ん中、左寄りを真ん中と認識させることで2レーンを実現できます。
2レーン以上も同様の考え方でできます。
二車線の場合は、レーンを引くときに2レーン分引くとか工夫もできるんですが、
二つもレーン引くの面倒ですし、変更にも手間がかかるのでbrainで実現しました。
 

【レーンの分岐・変更】

単純に分岐するだけだと、分岐時にどのレーンを優先したらいいのかわからなくなります。
右にいくときは右に曲がるレーンを優先すればいんですが、どれが右のレーンかはAgentからはとりにくくなります。
なので、ここはルールとして、右は赤色のレーン左は青色のレーンと決めてしまいます。
ただ、これだけだと十字路の際に直進するAgentがうまく取得できないので
分岐地点のまっすぐな部分にも黄色の色をつけます。
それ以外の一本道にはさっきの三色以外の色をつけておきます。
このレーンの色で交差点を判断できるようになりますので、
減速も容易にできます。

Agentは現在の自分が上にいるレーンの色まで取得できますので、
これでうまいことレーンを変更することができます。
 

【信号の解釈】

これは、先程使ったsoundをここでも使用します。
ここでは信号の色の情報を音に置き換え発しています。
また、信号の向きも取得し判断するようにしています。
これは交差点で本来自分の車線にない信号の情報まで取ってしまうのを防ぐためです。

ちなみにこれを導入しても信号のAgentがないと意味がありませんので、別途作成します。
 

【障害物を避ける】

これも信号と同じような解釈で作成することができます。
ただ、信号のように向きとかまで考慮する必要がないので簡単にできます。
 

【カーブ・傾斜での減速】
これは単純です。
曲がるときにスピードを落とすように設定すればいいんですね。
傾斜も同じように、上るときは減速、下るときは加速にすればいいです。
 

【車線による車の優先度】

これは交差点での事故を防ぐ効果があります。
ここでのポイントはどの車が直進で左折や右折をしているのかを取得するところにあります。
自分から見て他の車がどのように進むかというのは非常に取りづらいです。
なので、ここでは逆転の発想で、自分が曲がるときにウィンカー、また直進の際にも直進のサインを出すようにします。

あとは他の車のウィンカーサインを取得して道を譲るようにしてあげれば完了です。
 
 
 
最終的にはこんな感じのノードコネクションになりました。

(クリックで拡大)
 
 
 
 

[おわりに]

こんな感じでMassiveのbrainを組んでいきます。
brainは一度組み方さえ理解してしまえば流用が効きますので積み重ねしやすいですよね。

これを組んでみて思ったのは
意外と信号機のアルゴリズムがめんどくさいってことですかね。
一瞬全部赤になるときがあったり、タイミングを調節しやすくしたりするのが意外と面倒です。

それから、レーンの組み方もしっかりとしてあげないと、
このbrainでもうまく制御できないんですよね。
右折左折レーンの作り方にもコツがあるのでこのコツを守らないと
うまく交差点の対応ができなかったりします。
裏を返せば、まだこのbrainでは柔軟に対応できてないところが多いということなんですよね。
 
 
突き詰めれば突き詰めるほど物事にはルールというものが存在しているというのがわかります。
また、そのルールを再現するためには、brainを作れるように見方を変える必要もあるというのがよくわかります。

とはいえ、私らがやっているのは結局のところエンターテイメントなんで
見栄えがよければOKなところもあります。
ということで、あんまり深く考え過ぎないのも大事ですね。
ちなみに今回のbrainもいい感じになるようなマジックナンバーを使用してたりするのでご愛嬌です。

 
 
 
 

それではまたどこかで(:3ノシ )ヘ
 
 
 
 

【Unity】 ~Unityでシェーダーを書いてみる~:その1【初心者向け】

$
0
0

皆さん始めまして。
今年の四月に入社しました開発部の辻です。
気づけば今年もほとんど終わってしまいました。時間の流れの速さを感じます。

さて今回の記事では、Unityのシェーダーについて紹介していきます。

CGにおけるシェーダーはキャラクターや背景の見た目を決定する重要な要素です。
しかし、シェーダーを学習するとなると、まずシェーダー以外の部分でのプログラミングが必要になることが多いです。
やりたいことはシェーダーなのに、それ以外の部分で躓いてしまう人も居るのではないでしょうか?
Unityは僕自身がシェーダーを勉強するために触っていて、個人的に使いやすかったので今回紹介しようと思いました。

この記事ではUnityのダウンロードからUnityの簡単な説明をした後に、オブジェクトにちょっとしたシェーダーを割り当てて説明するところまでやっていく予定です。
初心者向けですので、既にUnityや他のシェーダー言語を使っている方々にとっては物足りないような内容になってしまいますが、ご了承くださいませm(__)m

全体の構成は以下のようになっています。

・そもそもUnityとは
・Unityでシェーダーを書くメリットは?
・Unityをインストールしよう
・プロジェクトを作る
・シェーダーの前準備
・シェーダーを作る
・頂点シェーダーについて
・フラグメントシェーダーについて

それでは参りましょう。

・そもそもUnityとは

Unityとは、無料でも使用出来るゲームエンジンのことです。
ライセンスの形式は無料のPersonal Editionと有料のProfessional Editionがあります。
有料版のほうがより多くのサポートが受けられるわけですが、無料版で使える機能も非常に充実しています。
今回はPersonal Editionで、バージョン5.2.3のUnityを使用しました。

iPhoneやwebPlayerなどなど、様々な環境に対応した出力が簡単に行えるので、
スマートフォンゲームだけでなく、様々なゲームの制作で使用されています。
また、無料で使用できるため使用者が多くコミュニティが広いのも特徴的です。
そのため、インターネット上で情報がとても集めやすくなっています。

・Unityでシェーダーを書くメリットは?

Unityでシェーダーを作成するメリットは、まず環境構築が楽なことだと思っています。
シェーダーを書く場合、まずシェーダー以前にシェーダーを適用するオブジェクトはもちろんライトの位置等も自分で設定しないといけません。
Unityでは、プリミティブなオブジェクトの作成はもちろん、外部から.fbxや.obj形式のモデル読み込みにも対応していますし、モデルの位置の変更やライトの設定もプログラムで制御しなくて良いので簡単に行えます。

加えてシェーダーの反映がスムーズです。
シェーダーを変更したら、その内容はすぐに反映されますし、エラーもコンソールに表示してくれるのでデバッグもしやすいです。
いちいちビルドして結果を見るという手間が無いので快適に開発できます。

また、Unityのシェーダーはシェーダーファイル一つで動かせるのもポイントだと思います。
例えばOpenGLでは、OpenGLのソースコードをC++等で作成した上でGLSLのファイルも作成しなければなりませんが
Unityでは簡単なものであれば、シェーダーのファイル一つ作るだけで動かすことができます。

Unityでシェーダーを覚えても他の環境で使えないんじゃ……と思う人も居るかもしれません。
シェーダー言語はいくつか種類がありますが、根本的なアルゴリズムの部分で必要な情報が違うわけではありません。
例えばLambert反射を実装する場合、どの言語であってもライトの向きと法線の方向を使用します。
ただシェーダー言語によってその情報を持って来る方法が違うだけなので、Unityで学んだ知識が他で無駄になる、ということは無いはずです。

・Unityをインストールしよう

さて、Unityでシェーダーを書こうにも、Unityをインストールしないことには始まりません。
というわけでこのページからインストーラをダウンロードします。

・補足
現在の最新バージョンはUnity5.3.0ですが、この記事は5.2.3の環境で作成しています。ですので、この記事内の説明と多少食い違う部分がある可能性があります。
気になる方は以前のバージョンをこちらから入手することができます。

インストーラをダウンロードした後に起動すると、このような画面が出てくると思います。(このページの画像はクリックすると大きくなります)

ダウンロードするフォルダ等を設定しつつ、指示に従ってインストールを行ってください。

インストールが終わったら起動します。
恐らく初回ではPersonal EditionとProfessional Editionのどちらを使うか聞かれると思うので
Personal Editionを選択します。
次にサインインの画面になると思います。
Unityのアカウントを作成していない人は作成してから入りましょう。

こんな画面が出ればおっけーです。

とりあえずこれでプロジェクトを作る準備が出来ました。

・プロジェクトを作る

次にプロジェクトを作ってオブジェクトを生成するまでやって見ます。
New Projectを押すとプロジェクト作成画面になります。プロジェクトの名前はお好みで、今回は下のようにしました。

3Dか2Dか選択できますが、今回は3Dを選択します。

開くと画像のような画面になっていると思います。
初めて見ると情報量が多くてびっくりしますが、一つ一つ簡単に説明を。

……と、その前にちょっとだけレイアウト変更をします。
画面の一番右上の“Layout”というプルダウンをクリックして、“2by3″を選択してください。

“2by3″にするとこんな感じです。画面は大きく五つに分類できます。

個人的にこの画面が一番見やすいのですが、他のレイアウトがいいという方は
同じく“Layout”からお好みのレイアウトを選びましょう。

それでは各画面の見方に参ります。
Unityの画面には、
1・Scene
2・Game
3・Hieralchy
4・Project
5・Inspector
の五つがあります。

1・Scene

ここでは、オブジェクトの位置の変更や、回転、拡大縮小などを行います。
後述するマテリアルの登録等も、この画面に対して行います。

2・Game

ここでは、実際カメラに映る結果が表示されています。
ゲームを作成する場合には、ここがプレイヤーの見る画面になるわけですね。

3・Hieralchy

Sceneに配置されているオブジェクト達がここに一覧として表示されています。
オブジェクトの選択はSceneのオブジェクトを直接クリックする以外にここから選択することも可能です。

4・Project

ここではこのプロジェクト内に存在するオブジェクトからscriptファイルまでの全てが表示されます。
Hieralchyよりもより多くのものがここで管理できます。

5・Inspector

ここは選択されたオブジェクトの詳細が表示される場所です。
アトリビュートの変更などもここから行います。

次に、簡単にオブジェクトを生成してみます。
画像のように、一番上の“GameObject”から、“3D Object”>”Sphere”を選びます。

画面の中央に白い球体ができていれば成功です。
出てきたSphereをクリックすると移動や拡大縮小が行えます。
どの動作をするかは、画面左上のこれ

をクリックする、

もしくはキーボードで”q,w,e,r”を押すと切り替えることが出来ます。

それぞれ、
q>画面そのもののスクロール
w>移動
e>回転
r>拡大縮小
です。

視点を回転させたい場合は”Altキー”を押しながら、”左クリック+マウス移動”で。
視点を前後させたい場合は”Altキー”を押しながら、”右クリック+マウス移動”で出来ます。

この辺の操作はMayaとほぼほぼ同じなので、使ったことがある人はなじみやすいかもしれません。
これで簡単にUnityが触れるようになったはずです。

・シェーダーの前準備

シェーダーの話に入る前に、Unityのシェーダーを使用するにはまずマテリアルが無いといけません。
Unityではマテリアルにシェーダーを登録し、そのマテリアルをオブジェクトに登録する形になります。
というわけで作成して行きましょう。

画像のように、右クリック>Create>Folderでマテリアルを入れるフォルダをCreateします。
同じように右クリック>Create>Materialでマテリアルが作成出来ます。

白い玉の映ったマテリアルができたと思います。名前は自由に付けて構いません。
フォルダの外に作ってしまった場合はドラッグしてフォルダにドロップすれば移動が可能です。
このあたりの操作は普段のファイル操作と変わりませんね。

作ったマテリアルは、左クリックでドラッグした後、Scene内のマテリアルを設定したいオブジェクトの上で
ドロップすると登録することが出来ます。
マテリアルを選択するとInspectorが画像のようになっていると思います。

この内部を弄ることで表示される結果を変えることができます。
試しに色を変えました。Albedoのところにある、スポイトみたいなマークの隣の四角いUIをダブルクリックすることで、色を変更できるパレットが開きます。

変更されると、四角いUIの色も対応して変わります。良く見ると下と左上に表示されてる球体の色も変わっていますね。

白い球体が真っ赤になりました。

このInspector、マテリアルを選んだ状態でよく見るとにShaderというプルダウンがあります。

実はUnityのシェーダーはここに表示され、どのシェーダーを使用するかはここから選択できます。
試しに別の好きなシェーダーに変えてみると、Inspectorにあるアトリビュートが変わるのが確認できると思います。
シェーダーの登録はこれだけ簡単に行うことができるのです。
これから、ここに自分で作成したシェーダーを実際にオブジェクトに登録して行きます。

・シェーダーを作る

FolderやMaterialと同じように、シェーダーもCreateのShaderから作成することが可能です。
四種類候補が出てくると思いますが、今回はImageEffectShaderをCreateします。

補足:Unityのバージョンによっては、Create>Shaderで作れるシェーダーが1種類の場合があります。その場合は後述する内容とソースコードの中身が一致しません。

Createした後は、好きな名前をつけましょう。今回は“myShader”にしてみました。

作成したシェーダーファイルを開くと以下のようなソースコードになっていると思います。
もし上手く作れない場合は、メモ帳などに以下のソースコードをコピペして、
“好きな名前.shader”で保存した後にUnityのProjectにドロップするのでも大丈夫です。

[cpp]
Shader “Hidden/myShader”
{
Properties
{
_MainTex (“Texture”, 2D) = “white” {}
}
SubShader
{
// No culling or depth
Cull Off ZWrite Off ZTest Always

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include “UnityCG.cginc”

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};

v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.uv;
return o;
}

sampler2D _MainTex;

fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// just invert the colors
col = 1 – col;
return col;
}
ENDCG
}
}
}
[/cpp]

これがシェーダーの中身になります。
恐らく初見ではわけがわからないと思います。僕もそうでした。
まずはこのシェーダーを使って見るために、
一番上のこの部分

[cpp]
Shader “Hidden/myShader”
[/cpp]

[cpp]
Shader “Custom/myShader”
[/cpp]

こんな感じに変更します。
これによって、先ほど作成したマテリアルのShaderのところにCustomが追加され、
自分のつけた名前のシェーダーが選べるようになっているはずです。
ちなみにCustomの部分は好きな名前にするとそれが追加されます。せっかくなのでDF_TALKに変えてみました

シェーダーを作ったら、マテリアルのShaderのプルダウンから、自分のつけた名前のシェーダーを選びましょう。
このシェーダーはテクスチャに登録した画像のカラーを反転させる内容になっています。
せっかくなので下のようなテクスチャを登録してみました。

テクスチャ画像は、その画像のあるフォルダから、Projectの中にドラッグするだけで
登録できます。
登録したテクスチャ画像は、マテリアルのInspectorにあるNone(Texture)となっている場所に
ドラッグ&ドロップしましょう。

オブジェクトもちょっと増やしてそれぞれにマテリアルを適用してみました。
なんかちょっと不思議な見た目ですね。

複数オブジェクトを出して、カメラを回転して見ると、なにやら重なって見えます。

これはシェーダーのカリングと深度値の設定によるものです。
今回は詳しく説明しませんが、気になる人は先ほどのソースコードの

[cpp num=10]
Cull Off ZWrite Off ZTest Always
[/cpp]

この部分を

[cpp num=10]
Cull Back ZWrite On ZTest LEqual
[/cpp]

に変更して見てください。見慣れた感じ(?)に描画されると思います。

ちょっと脱線してしまいましたが、このファイルを書き換えていくことで、自分の書いたシェーダーを使うことができるわけです。
中身について理解するために、簡素なシェーダーを以下に書きます。

公式のサンプルを少し弄ったものになっています。
公式サンプルはこちら
Unityのシェーダーは3種類ありますが、今回は頂点・フラグメントシェーダーを使用します。
以下のソースを書き込んで動かしてみてください。書き込む際は先ほどCreateしたシェーダーを上書きするか、新しくCreateしましょう。

[cpp]
Shader “DF_TALK/myShader”//ここは自分の設定した名前でどうぞ
{
Properties
{
//色を設定するアトリビュートを作る
_myColor (“myColor”, Color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader
{
Pass
{
CGPROGRAM//ここからCgでプログラムを記述するという宣言をする
#pragma vertex vert
#pragma fragment frag

float4 _myColor;

//頂点に対しての処理
float4 vert(float4 vertexPos : POSITION) : SV_POSITION
{
// 座標変換後の位置を返す
return mul(UNITY_MATRIX_MVP, vertexPos);
}
//ピクセル単位の処理
float4 frag() : COLOR
{
return _myColor;
}
ENDCG//CGPROGRAMの終わりがここという宣言をしている
}
}
}
[/cpp]

アトリビュートが“myColor”だけのシェーダーが作れたはずです。
myColorを変更するとオブジェクトの色が変わるのでちょろっと弄ってみてください。

それでは、このシェーダーでそれぞれ何をやっているのか、順を追って説明していきます。

[cpp num=3]
Properties
{
    //色を設定するアトリビュートを作る
    _myColor (“myColor”, Color) = (1.0, 1.0, 1.0, 1.0)
}
[/cpp]

ここでは、UnityのInspectorに表示するアトリビュートを設定します。
_myColor>このプログラムの中で扱う時の名前。
“myColor”>Inspectorに表示される名前。
Color>アトリビュートの種類。
となっています。後ろにある=(1.0, 1.0, 1.0, 1.0)の部分では、初期値を設定しており、四つ数値があるのは、ColorというアトリビュートがRGBA(赤、緑、青、透明度)の四つのステータスを持っているからです。
初期値の部分は、アトリビュートの種類によって書き方が変わります。

[cpp num=8]
SubShader
{
    //省略
}

[/cpp]
SubShaderは、シェーダーの中身を記述する部分です。
SubShaderは複数宣言することができ、その時動いている環境に対応したシェーダーが選ばれるようになっているそうです。
ゲームエンジンなので、様々な環境に対応出来るようになっているわけですね。誰かに使ってもらったりするわけでなければ特に気にしなくて大丈夫だと思います。

[cpp num=10]
Pass
{
    //省略
}
[/cpp]

このPassはSubShaderの中に最低一つ無ければならない処理になっています。
複数のPassを使うことでより複雑な表現を行うこともできますが、今回は扱いませんので、
SubShaderの中にPassを記述するという部分を覚えておけばよいでしょう。

[cpp num=12]
CGPROGRAM
ENDCG
[/cpp]

この二つの宣言で囲まれた部分に、どの種類の言語でシェーダーの記述を行うかを決定しており、今回はCgというシェーディング言語で書くことを設定しています。
他にも種類がありますが、今は知らなくても大丈夫です。

[code num=13]
#pragma vertex vert
#pragma fragment frag
[/code]

頂点シェーダーとフラグメントシェーダーの処理を行う関数が、どのような名前になるかを設定しています。
今回の頂点シェーダー関数はvert、フラグメントシェーダー関数はfragという名前になっています。
ここでは名前を決めているということがわかってもらえたら大丈夫です。

[cpp num=16]
float4 _myColor;
[/cpp]

ここでは変数を設定しています。
_myColorは、Propertiesで設定したものと同じ名前を設定する必要があり、こうすることでUnity側のInspectorで設定した値をシェーダー内で扱うことが出来ます。
“float4″で宣言しているのは、先ほども説明したとおり_myColorが4つの要素を持っているためです。
例えばこれが二次元の座標を示す変数であれば、“float2″となります。

次はシェーダーの肝である頂点・フラグメントシェーダーの頂点シェーダー関数についてです。

・頂点シェーダーについて

[cpp num=19]
//頂点に対しての処理
float4 vert(float4 vertexPos : POSITION) : SV_POSITION
{
    // 座標変換後の位置
    return mul(UNITY_MATRIX_MVP, vertexPos);
}
[/cpp]

ここが頂点についての処理を行っている頂点シェーダーです。先ほど決めた名前の関数を宣言しています。
頂点シェーダーの処理は“オブジェクトの各頂点が、画面のどこに表示されるのか”を決定します。

(float4 vertexPos : POSITION)の部分で、受け取る頂点情報を設定しています。
変数の後ろに : POSITIONをつけることで、Unity側から、オブジェクトのローカル座標の頂点情報が入ってきます。
この : POSITIONのように、コロンの後ろに大文字で宣言されている部分をセマンティクスと言い、セマンティクスによって決められた役割が変数に与えられます。
一番後ろの : SV_POSITIONは、もろもろの座標変換を行った後の頂点座標であることを宣言しています。
戻り値はfloat4もしくはfloat4の変数を含んでいる構造体でなければならないようです。

[cpp num=22]
return mul(UNITY_MATRIX_MVP, vertexPos);
[/cpp]

mulという関数は、行列演算を行ってくれる関数です。
入力されてきた頂点情報vertexPosに、UNITY_MATRIX_MVP行列をかけているわけですね。
UNITY_MATRIX_MVPには、いわゆる座標変換行列が入っていて、
Model View Projectionの三つの変換がまとまっています。これを行うことで、
頂点の情報がしっかりとカメラに映る位置に移動すると思ってもらえれば大丈夫です。
試しに、

[cpp num=22]
return vertexPos;
[/cpp]

として座標変換を止めてみると、画像のように画面に凄い主張してくるオブジェクトが表示できるはずです。
Scene上でカメラを動かしてみても微動だにしないと思います。

・フラグメントシェーダーについて

頂点シェーダーで出力した情報によって、オブジェクトの位置や向きが決まりました。
次はそのオブジェクトが描画される範囲をピクセル化します。これをラスタライズと呼びます。

この“ラスタライズされた各ピクセルに対して処理を行う”のがフラグメントシェーダーです。
ちなみにフラグメントシェーダーはピクセルシェーダーと呼ばれることもあります。

[cpp num=25]
//ピクセル単位の処理
float4 frag() : COLOR
{
return _myColor;
}
[/cpp]

今回はUnityのInspectorで設定できるColor情報をそのまま使用しています。
: COLORは、フラグメントシェーダーでカラー情報を返すことを設定しています。

このフラグメントシェーダーでは、頂点シェーダーの処理を経てラスタライズされたピクセルを
_myColorで塗りつぶす処理を行っているわけです。
大体のシェーダーは、ここで更に法線情報やライトの向きなども使用し、どのような色をつけるか計算を行っています。

これで説明がひと段落しました。

個人的に大事なところは、頂点シェーダーとフラグメントシェーダーの役割を知ることがまず一つ。
頂点・フラグメントシェーダーの流れは他のシェーダー言語でもあるので、これを覚えるだけでもシェーダーのイメージが変わるのではないでしょうか。

二つ目はセマンティクスが何の情報を持ってくるのか知ることです。
これはCg特有の書き方ですが、何を持ってこれるのかわかりやすいと思います。
もし、別のサンプルやアルゴリズムを見て欲しい情報があったら、
まずセマンティクスで持ってくることはできないかどうかを探して見るのが良いと思います。

次に、ここまでのことを踏まえて、初めにCreateしたシェーダーの中身を見直して見ます。
初めて見た時と見え方が変わっていると良いのですが……どうでしょうか?
少しカリングとデプスの設定を書き換えてあるのと、説明のコメントアウトを追記してあります。
また、後で他のアトリビュートも扱いたくなった時用に、全種類では無いですがPropertiesにアトリビュートを追加してます。
公式のPropertiesの説明はこちら
良ければいろいろ変更してみてください。

[cpp]
Shader “Custom/myShader”
{
Properties
{
//テクスチャを入力したい時の宣言
//”white”はデフォルトのテクスチャで、それで初期化を行っています
//何も書かずに”"{}とすることもできます
_MainTex (“Texture”, 2D) = “white” {}

//Boxに数値を入力する形のアトリビュートです
_Value (“Value”, float) = 1.0

//こっちはスライダーで数値を選べるアトリビュートです
_Slider (“Slider”, Range(0.0, 1.0)) = 1.0

//ColorとVectorです。要素の数は同じですが、表示のされ方が違います
_Vector (“Vector”, Vector) = (0.0, 0.0, 0.0, 0.0)
_myColor (“myColor”, Color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader
{
//カリングと、デプス描画についての設定を行っています
Cull Back ZWrite On ZTest LEqual
//Cull Off ZWrite Off ZTest Always

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

//便利な構造体や関数が定義されているinclude
//今回は使われてないですが、とりあえず書いておくのでも今は問題ないはず
#include “UnityCG.cginc”

//入力に使う構造体です
//頂点入力のPOSITIONに加えて、オブジェクトのuv座標である
//TEXCOORD0が増えています。
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

//出力に使う構造体です
//入力と見比べると、POSITIONがSV_POSITIONに変わっていますね
//uvの座標をフラグメントシェーダーで受け取りたいので、TEXCOORD0のセマンティクスが宣言されています
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};

//入力がappdataになっています
//また、戻り値の型がfloat4ではなく上で宣言したv2fになっています
//v2fの中にあるvertexがfloat4でSV_POTISIONなので問題ないわけですね
v2f vert (appdata v)
{
//構造体を生成
v2f o;

//頂点情報には先ほどと同じく変換した情報を入れます
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

//uv座標を引き渡しています
o.uv = v.uv;
return o;
}

//上で宣言したアトリビュート達。書き換えて見たい場合に使ってみてください
//float4の個別のアトリビュートにアクセスしたい時は
//_Vector.xのように後ろにつけることで、その要素を扱うことができます
float _Value;
float _Slider;
float4 _Vector;
float4 _myColor;

//Propertiesで宣言したテクスチャを使えるように宣言しています
//2Dテクスチャはsampler2Dで定義します
sampler2D _MainTex;

//戻り値がfixed4になっています。これはfloatより精度の低い小数の型です
//頂点シェーダーの出力はフラグメントシェーダーで受け取ることができますこれは先ほどのシェーダーには無かった部分ですね
//(v2f i)の部分で、頂点シェーダーの戻り値と同じ型を宣言しているのがわかります
// : SV_Targetは新しい表記で、COLORと同じ意味のセマンティクスみたいです(勉強不足で断言できません…)
fixed4 frag (v2f i) : SV_Target
{
//出力するカラーを作っています
//tex2Dという関数は、Sampler2Dとuv座標を引数にとって、今描画しようとしているピクセルは、テクスチャのどこのピクセルなのかを計算しています
fixed4 col = tex2D(_MainTex, i.uv);

//カラーはRGBAそれぞれ0.0~1.0の範囲になるので、1からcolを引くことによって、色が反転します
col = 1 – col;
return col;
}
ENDCG
}
}
}

[/cpp]

・終わりに

ここまで読んで頂いた方はお疲れ様でした。
実際にライトや視線のベクトルを使ってシェーディング! みたいな内容は、次回以降やっていけたら良いなと思ってます。
至らぬ点も多々あったかと思いますが、何か一つでも皆様のプラスになれば幸いです。

それでは。

※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

【Python】正規表現を使った快適コーディングのすゝめ ~はじめの一歩から三歩ぐらいまで~

$
0
0

どーも、TDの小野です。
2016年はじめての投稿になりました(←ホントは年末投稿予定だった、なんてことはない)。今年も弊社とDF Talkをよろしくお願いします。

さて、今回はPySideから少し離れて、プログラミングの基礎のお話をします。
テーマは「正規表現」です。

正規表現のマニュアルって、どのプログラミング言語にもありますけど、読みにくいんですよね。もっといろいろ例を載せてほしいなぁと、勉強し始めたころは思っていた記憶があります。
以前、DF Talkでも正規表現についての記事を書いてはいるのですが、今回は実例を交えながら、もう少し詳しく解説したいと思います。
この記事では、Pythonにおけるコードを書いていますが、正規表現に関してはどの言語も似ているので、Pythonではない言語を使われている方も、まずはここで理解を深めていただければと思います。

正規表現とは

まず正規表現とはなんなのでしょうか?なぜ必要なのでしょうか?
正規表現とは、いくつかの文字列をひとつのパターンで表現する機構です。そして、正規表現をプログラミングに取り入れる目的は、人間が文字列を見たときに感覚的に解析、理解していることを、コンピュータにもできるようにするためです。つまり、きちんと使いこなすことで、文字列の検証、解析が的確にでき、プログラムにおける不具合の解消にもつながります。

簡単な例を挙げましょう。
下の文字列を見たときにあなたはどれがバージョン名か分かりますか?

INU_arm_v001

おそらく全ての人がv001と思ったでしょう。でも、v001と判断したのはどうしてですか?

頭の中をのぞいてみるとこんな考えがあったのではないでしょうか?

「INU, arm, v001は別のグループっぽいな。」
「versionの頭文字vがついてるグループがあるな。」
「バージョンだから数字が入ってそうだな。」

これらの知識を考慮して、人間は適切な情報を文字列から取得しています。
この能力をコンピュータで実現させるために正規表現が必要になります。

正規表現を作ってみよう

バージョン名をコンピュータに認識させるため、まずはルールを決めましょう。
今回はバージョン名のルールを「vからはじまる数字三桁」としました。

冒頭で、「正規表現とは、いくつかの文字列をひとつのパターンで表現する機構」と言いました。そこで、上のルールに則って、実現しうるバージョン名を全て考えてみましょう。
v000、v001、v002、……、v010、v011、……、
v100、v101、v102、……、
……

はい、もうパターンが見えてきましたね。一番初めの文字が絶対”v”で次の文字は0~9、その次も0~9、その次も0~9が入れば全て表現できそうです。これらをひとつの文字列で表してみましょう。
以下のように表せます。

v[0123456789][0123456789][0123456789]

ながっ…
でも、これがPythonの認識できる、「vからはじまる数字三桁」を表した正規表現パターンになります。
vは絶対なのでそのまま。vの後は0~9までの数字でどれでもいいので[0123456789]。三文字分それが続くので[0123456789][0123456789][0123456789]になります。

ここで出てきた [] は文字クラスと呼ばれ、文字の集合を意味します。
[]は文字列の中で一文字を表し、対象となった文字が、中にあるパターンにマッチするかが調べられます。

でも、長すぎない?
はい、長すぎますね。連続する文字コードの場合、このように範囲で書くこともできます。

v[0-9][0-9][0-9]

短くなりましたね。
例えば、「数字は2から8しかありえないよ」ってルールの場合は[2-8]になりますし、
「aからdの文字列か、vか」みたいな時は[a-dv]のように書けます。

また、大抵の言語では特殊文字が用意されており、Pythonでも以下のように10進数を書き表せれます。

\d

すなわち [0-9] の部分が \d になるので、さらに正規表現は短くなり、 v\d\d\d になります。

また、同じパターンの繰り返しの場合は以下のように回数を指定して書くことが出来ます。

{m} (m:繰り返し回数)

ということで、正規表現はもっと短くなり、v\d{3}と書き表すことができます。

実際に認識させてみよう

では、ここで実際にモジュールを使って、文字列を認識させてみましょう。

Pythonの場合モジュールはreを使い、マッチングにはsearchかmatchメソッドを使います。
例えば、以下のように書くと文字列をパターンにマッチングできます。

[python]
import re

name = “v001″
PATTERN = “v\d{3}”

if re.search(PATTERN, name):
print “Found”
else:
print “Not found”
# Found

[/python]

searchとmatchの違いは?

どちらもマッチングが成功した場合、MatchObjectが返ってきます。
失敗した場合はNoneが返ってきます。

[python]
PATTERN = “v\d{3}”
name = “v001″
mObj = re.search(PATTERN, name)
print mObj
# <_sre.SRE_Match object at 0x14191da58>
mObj = re.match(PATTERN, name)
print mObj
# <_sre.SRE_Match object at 0x1418e0238>

name = “v”
mObj = re.search(PATTERN, name)
print mObj # None

[/python]

では、違いは何でしょう?

search
→パターンが文字列の中にある場合マッチ

match
→0番目の文字からの文字列がパターンと同じ時マッチ

searchとmatchの違いの典型的な例は、
name = “scene_v0001″
のとき、search関数ではマッチしますが、match関数ではマッチしません。
search関数でマッチする理由は、v000にマッチしているからです。

[python]
PATTERN = “v\d{3}”
name = “scene_v0001″
mObj = re.search(PATTERN, name)
print mObj # <_sre.SRE_Match object at 0x1418e0238>

mObj = re.match(PATTERN, name)
print mObj # None

[/python]

注意しないといけないのは、matchメソッドは完全一致をマッチとするわけではないということです。
文字列の一番初めから探して、一致するものはマッチしたものとして扱われます。
次の例を見てみましょう。

[python]
PATTERN = “v\d{3}”
name = “v0001_scene”
mObj = re.search(PATTERN, name)
print mObj # <_sre.SRE_Match object at 0x1418e0238>
mObj = re.match(PATTERN, name)
print mObj # <_sre.SRE_Match object at 0x14191dac0>

[/python]

どちらもマッチしましたね。これはmatchメソッドも”v000″にマッチしたからです。
このように各メソッドの挙動を把握したうえで、正規表現を作り、使い分ける必要があります。

マッチングルールはできるだけ多く

searchメソッドでマッチングさせる際、以下のような場合に間違って認識してしまう可能性があります。
[python]
PATTERN = “v\d{3}”
name = “INU_env123_material”

[/python]

こういうときも”v123″に一致してしまいます。
このような場合はmatch関数を用いてマッチさせるか、パターンに他の条件を付け加えましょう。

例えば、
「バージョンは絶対文字列の最後にある」
という条件をつけてみましょう。
文字列の末尾、または改行の直前を表すため、特殊文字 $ を取り入れます。
すなわち正規表現パターンは以下のようになります。

v\d{3}$

これで、「三桁の数字の次は文字列の末尾か改行」というルールが付け加わりました。

[python]
name = “INU_env123_material”
SEARCH_PAT = r”v\d{3}”
sObj = re.search(SEARCH_PAT, name)
if sObj:
print “Found”
else:
print “Not found”
# Found

name = “INU_env123_material”
SEARCH_PAT = r”v\d{3}$”
sObj = re.search(SEARCH_PAT, name)
if sObj:
print “Found”
else:
print “Not found”
# Not found

[/python]

もしくは
「vの前には絶対アンダーバーがある」というルールを加味してみましょう。

_v\d{3}

と書くことによって、vの前にアンダーバーがある場合にだけマッチするように絞り込めます。

マッチング条件を緩める

ルールを増やして絞り込むだけでなく、緩めて広い範囲にマッチさせることもできます。
例えば、「vの前の文字はアンダーバーか、ハイフンかのどちらか」というルールの場合、 [] をつかって、その条件を実現できます。

[_-]v\d{3}

[python]
name = “INU_arm-v001″
SEARCH_PAT = r”[_-]v\d{3}”
sObj = re.search(SEARCH_PAT, name)
if sObj:
print “Found”
else:
print “Not found”
# Found

[/python]

では、こんなときは?
「バージョンの桁数が2桁かも…」
ここでは {m,n} という書き方が使えます。mが最小の繰り返し回数、nが最大の繰り返し回数です。

[_-]v\d{2,3}$

○ INU_arm-v001
○ INU_arm_v01
× INU_arm-v0001

最後に$をつけた理由は3番目の例のケースでマッチさせないためです。

マッチした情報を取得する

マッチしたら、マッチしたものの情報が欲しときがありますよね。例えば、バージョン名は何?とか。
そのような場合、括弧でくくるとグループとして扱ってくれ、MatchObjectのgroup関数でアクセスできます。

_(v\d{3})$

[python]
SEARCH_PAT = r”_(v\d{3})$”
name = “INU_arm_v003″
sObj = re.search(SEARCH_PAT, name)
if sObj:
print sObj.group() # _v003
print sObj.group(0) # _v003
print sObj.group(1) # v003
else:
print “Not found”

[/python]

group()もしくはgroup(0)で、パターンに一致した文字列が、group(1)で、パターンの括弧でくくられた文字集合に一致した文字列が返ってきます。
グループが複数ある場合はこの数字が前方からの数分、上がっていきます。

例えば、上記の文字列からarmという部位名も取得してみましょう。ルールは「アンダーバーもしくはハイフンで囲まれた、大文字か小文字のアルファベットが一文字以上ある」とします。正規表現パターンは以下のようになります。

_([a-zA-Z]+)_(v\d{2,3})$

アンダーバーに挟まれている括弧(赤文字)が、部位名に一致する文字集合になります。大文字でも小文字でもいいアルファベットなので、[a-zA-Z]にしました。
そして、[a-zA-Z]の後の + 「最低一文字は、直前の文字パターンに一致する文字がある」ことを示します。

[python]
SEARCH_PAT = r”_([a-zA-Z]+)_(v\d{2,3})$”
name = “INU_arm_v003″
sObj = re.search(SEARCH_PAT, name)
if sObj:
print sObj.group(0) # _arm_v003
print sObj.group(1) # arm
print sObj.group(2) # v003

[/python]
 

ちょっとバージョン名から離れて、ファイル名で考えてみる

以下の文字列を見てください。よくあるファイルの命名規則ですよね。

DFT_010_0020_v003
JUM_002_0203A_v001
TS_101B_0035_v007

これらの名前は以下のルールによってつけられました。

「プロジェクトコードは大文字アルファベット」
「シーケンス名は数字三桁」
「ショット名は数字四桁」
「シーケンス名、ショット名は、どちらも大文字アルファベットのsuffixがつく可能性がある」
「バージョン名はvから始まる数字3桁」
「各名称の間はすべてアンダーバーで繋がれる」

ここで、例えば以下のようなファイル名
DFT_010_0020_v003.ma
からプロジェクトコード、シーケンス名、ショット名、バージョン名を取得する場合を考えてみましょう。

プロジェクトコード

まずはプロジェクトコードに注目します。大文字アルファベットを表す正規表現は[A-Z]ですね。
「一文字以上の…」はどのように表現すればいいでしょうか?
ここでは、先ほど出てきた + 記号を使います。 + は、その直前のパターンが最低一回以上繰り返されることを示します。
今は[A-Z]が一回以上繰り返されればいいので、[A-Z]+になります。

シーケンス名

シーケンス名は三桁の数字で構成されるので\d{3}で表現できそうです。しかし、「大文字アルファベットのsuffixがつく可能性がある」を考慮しなければなりません。まずはプロジェクトコードと同様に、大文字アルファベットのパターンを[A-Z]にしましょう。
そして、ここで + と同じように、直前のパターンの繰り返しを表す記号を紹介しましょう。

* は直前のパターンが0回以上繰り返される場合に使用します。つまり、「何文字あってもいいし、全くなくてもいい」場合に使えます。
例えば、A[A-Z]*に以下の文字列をマッチさせると、次のような結果になります。
○ A
○ AB
○ ABC
○ ABB
× Ac

? は直前のパターンが0回か1回繰り返される場合に使用します。
例えば、A[A-Z]?に以下の文字列をマッチさせると、次のような結果になります。
○ A
○ AB
× ABC
× ABB
× Ac

今回のシーケンス名ではsuffixの文字数は制限されてないので、\d{3}[A-Z]*で表現できそうです。もしルールが、「一文字だけつく可能性がある」であれば\d{3}[A-Z]?が適切なパターンになるでしょう。

ショット名

同じ考え方でショット名のパターンも作れます。
\d{4}[A-Z]*

バージョン名

バージョン名は今まで使っていたバージョンパターンを使いましょう。

全部くっつける

これらを考慮してアンダーバーでつなぐと、今回のファイル命名規則に沿った正規表現パターンができます。
[A-Z]+_\d{3}[A-Z]*_\d{4}[A-Z]*_v\d{3}

マッチした文字列を後で取得するため、グループとして、プロジェクトコードなどをカッコでくくりましょう。
([A-Z]+)_(\d{3}[A-Z]*)_(\d{4}[A-Z]*)_(v\d{3})

これでマッチさせると以下の様な結果が得られます。

[python]
name = “DFT_010_0020_v003.ma”
SEARCH_PAT = r”([A-Z]+)_(\d{3}[A-Z]*)_(\d{4}[A-Z]*)_(v\d{3})”

sObj = re.search(SEARCH_PAT, name)
if sObj:
print sObj.group(0) # DFT_010_0020_v003
print sObj.group(1) # DFT
print sObj.group(2) # 010
print sObj.group(3) # 0020
print sObj.group(4) # v003
else:
print “Not found”

[/python]

これでプロジェクトコード、ショット名などがマッチング結果から取得できますね。
しかし、シーケンス名が何番目のグループかわかりにくかったり、グループの番号が変わった際、全部番号を書き換えないといけないという欠点があります。

例えば、命名規則が変わって、エピソード名がシーケンス名の前に入ることになったとします。

[python]

name = “DFT_01_010_0020_v003.ma”
SEARCH_PAT = r”([A-Z]+)_(\d{2})_(\d{3}[A-Z]*)_(\d{4}[A-Z]*)_(v\d{3})”

[/python]

すると今までグループ2として取得していたシーケンス名をグループ3で取得しなければなりません。プログラムの様々なところでこの正規表現を使っていた場合、番号の書き換えが大量に起こります。

グループに名前をつける

前項の欠点を解決するため、グループに名前をつけることを考えましょう。そうすることで、あとでアクセスしやすくなります。

グループを示す括弧のなかで、(?P<name>…)のように書くと、そのグループの名前を定義できます。
例えばプロジェクトコードは(?P<project>[A-Z]+)のように書くことで、projectという名前でアクセスできます。

[python]
name = “DFT_010_0020_v003.ma”
SEARCH_PAT = r”(?P [A-Z]+)_(?P\d{3}[A-Z]*)_(?P\d{4}[A-Z]*)_(?Pv\d{3})”

sObj = re.search(SEARCH_PAT, name)
if sObj:
print sObj.group(“project”) # DFT
print sObj.group(“sequence”) # 010
print sObj.group(“shot”) # 0020
print sObj.group(“version”) # v003
else:
print “Not found”

[/python]

これだと命名規則が変わったとしても、コードの書き換えはほとんど発生しません。
また、一気に取得することもできます。そのためにはgroupsやgroupdict関数を使います。

[python]
SEARCH_PAT = r”(?P [A-Z]+)_(?P\d{3}[A-Z]*)_(?P\d{4}[A-Z]*)_(?Pv\d{3})”
sObj = re.search(SEARCH_PAT, name)
if sObj:
print sObj.groups() # (‘DFT’, ’010′, ’0020′, ‘v003′)
print sObj.groupdict() # {‘project’: ‘DFT’, ‘version’: ‘v003′, ‘shot’: ’0020′, ‘sequence’: ’010′}
else:
print “Not found”

[/python]
 

その他機能紹介

最後にいくつか機能をご紹介します。

1. 「ここがこうなら、あそここう」機能

なんと名前をつけていいか分かりませんが…
同じ文字列が絶対出てくる場合、グループ名を使って「ここにはあそこと同じ文字が入るよ」というのを定義できます。言葉で説明してもわかりにくいので例をあげましょう。

例えばファイルのパスが以下の様な場合に、フォルダ構造とファイル名が正しいかを検証したいとします。

C:\project\DFT\sequences\010\0020\common\DFT_010_0020_v003.ma

ファイルを管理するツールを作っていると、3階層目のプロジェクトコードとファイルのプロジェクトコード、5階層目のシーケンス名とファイルのシーケンス名などが一致しないといけないことなどはよくあります。
このルールをマッチングに取り入れる際に、次のような正規表現が使えます。
(?P=name)

実際に正規表現パターンで上のパスを表現してみました。

(?P<project>[A-Z]+)\\sequences\\(?P<sequence>\d{3}[A-Z]*)\\(?P<shot>\d{4}[A-Z]*)\\common\\(?P=project)_(?P=sequence)_(?P=shot)_(?P<version>v\d{3})

(?P<project>[A-Z]+)が3階層目のプロジェクトコードに一致し、ファイル名部分の(?P=project)が3階層目のプロジェクトコードと同じ文字列が入ることを示します。
この正規表現でsearchマッチングを行うと、
C:\project\DFT\sequences\010\0020\common\DFT_010_0020_v003.ma にはマッチしますが、
C:\project\DFT\sequences\010\0020\common\DDD_010_0020_v003.ma にはマッチしません。

[python]
name = r”C:\project\DFT\sequences\010\0020\common\DFT_010_0020_v003.ma”
SEARCH_PAT = r”(?P [A-Z]+)\\sequences\\(?P\d{3}[A-Z]*)\\(?P\d{4}[A-Z]*)\\common\\(?P=project)_(?P=sequence)_(?P=shot)_(?Pv\d{3})”
sObj = re.search(SEARCH_PAT, name)
if sObj:
print “OK”
else:
print “Not good”

# OK

name = r”C:\project\DFT\sequences\010\0020\common\DDD_010_0020_v003.ma”
sObj = re.search(SEARCH_PAT, name)
if sObj:
print “OK”
else:
print “Not good”

# Not good
[/python]

この機能を使うことで、正しい名前がつけられているか、チェックすることができます。

2. 「ここがこうなら、あそここう」機能

これもなんと名前をつけて良いか…。
正規表現で条件によって違うパターンをマッチさせることができます。

例えば以下のファイル名を見てみましょう。

DFT_010_0020_comp_v001.aep

このファイルから出力されるファイルには以下のように必ずoutputを示す“o_”のprefixがつくとします。
また、イメージシーケンス番号がバージョン名と拡張子の間に入るとします。

o_DFT_010_0020_comp_v001.0010.exr

Pythonの正規表現では、この2つのファイル名を一つのパターンで表すことができます。
そのためには「ファイル名が”o_”で始まっていたら、イメージ番号がバージョンの後に入って、拡張子が変わる」という条件を正規表現パターンに組み込む必要があります。

まずは、“o_”の部分をグループとして扱わないと行けないので、(?P<output>o_)としましょう。
そして、このグループは、存在する場合としない場合があるので“?”が後に付き、(?P<output>o_)?になります。
バージョン名までは、今までのパターンを用いましょう。すると、次のように表現できます。

(?P<output>o_)?[A-Z]+_\d{3}[A-Z]*_\d{4}[A-Z]*_comp_v\d{3}\.

通常であればこのあとファイル拡張子がつくので、aepなどが続けばよさそうですね。
ここで、バージョン名の後に、イメージ番号が続く場合の正規表現を考えてみましょう。
ファイル拡張子が決定しているなら、以下のようになりますね。
\d{4}\.exr

では、課題である、“o_”で始まっている時だけこのパターンに一致させるようにするにはどうすればいいでしょうか。

ここで次の表現を使います。
(?(id/name)yes-pattern|no-pattern)

“id/name”の部分にはグループ名やインデックス番号が入ります。
そのグループに一致するものがあるなら、”yes-pattern”の部分に書かれている正規表現がマッチングに使用され、一致していないなら、”no-pattern”の部分に書かれている正規表現が使用されます。
すなわち、今回の場合は(?(output)\d{4}\.exr|aep)と書けば、outputグループがマッチしたとき、yes-patternである
\d{4}\.exrが使われ、マッチしなかったときはaepが使われます。

全体の正規表現パターンは
(?P<output>o_)?[A-Z]+_\d{3}[A-Z]*_\d{4}[A-Z]*_comp_v\d{3}\.(?(output)\d{4}\.exr|aep)
のようになります。
グループ番号でも構いません。
(o_)?[A-Z]+_\d{3}[A-Z]*_\d{4}[A-Z]*_comp_v\d{3}\.(?(1)\d{4}\.exr|aep)
output”でアクセスする代わりに、”1″でアクセスしています。

[python]
SEARCH_PAT = r”(o_)?[A-Z]+_\d{3}[A-Z]*_\d{4}[A-Z]*_comp_v\d{3}\.(?(1)\d{4}\.exr|aep)”
imageName = “o_DFT_010_0020_comp_v001.0010.exr”
sObj = re.search(SEARCH_PAT, imageName)
if sObj:
print “OK”
else:
print “Not good”
# OK

sceneName = “DFT_010_0020_comp_v001.aep”

sObj = re.search(SEARCH_PAT, sceneName)
if sObj:
print “OK”
else:
print “Not good”
# OK

badName = “o_DFT_010_0020_comp_v001.exr”

sObj = re.search(SEARCH_PAT, badName)
if sObj:
print “OK”
else:
print “Not good”
# Not good

[/python]

3つ目のbadNameでは”o_”で始まっているにも関わらず、イメージ番号がバージョンの後に入っていないので、 Not goodになります。
このように条件によって違うパターンをマッチできました。

まとめ

いかがだったでしょうか。今回は長々と正規表現について解説させていただきました。
正規表現、奥深いですよね。文字列解析などに使うと本当に便利です。
Mayaなどではワイルドカードが用意されており、そちらを使うことも多いですが、予期せぬ文字列など、きちんと処理できない場合があります。
正規表現を正しく使うことで、効率的なコーディングだけでなく、文字列の検証により適切なデータを使用できるようになり、不具合の減少にもつながります。

正規表現と共に、どなた様も快適なコーディングをお楽しみください。

ではまた次回。

※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

Viewing all 131 articles
Browse latest View live