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

≪アニメーション室≫ DFは何故MotionBuilderを使い続けるのか!!

$
0
0

はじめまして。アニメーション・フェイシャル室の藤松です。
今回DF_TALKに初登場したことをきっかけに、この場を借りてDFのアニメーション室について色々ご紹介していきたいと思います。
タイトルにもなっていますが弊社アニメーション室が何故MotionBuilderを使用し続けるのかについて少しお話させて頂きます。
これを機会にMotionBuilderを使い始める個人や企業が増え、今後の機能向上に繋がれば幸いです。

アニメーション ワークフローの紹介

まずは弊社がMotionBuilderをどのように使用しているのか説明します。
大きく分けて3種類のワークフローが存在します。

TypeA
MotionCaptureがメインのワークフロー
基本はこの流れが主流になります。

TypeB
MotionCaptureとキーフレームアニメーションが混ざったワークフロー
SplineIKなどMotionBuilderでは再現できないリギングがある場合の流れです。
キーフレームアニメーションがメインでもこのタイプで進めることも多いです。

TypeC
キーフレームアニメーションがメインで形状変化を伴う特殊なリギングが必要なワークフロー
DeformerなどMotionBuilderでは再現できないリギングの場合のワークフローになります。

TYPE_ATYPE_BではアニマティクスとアニメーションをMotionBuilderで作業しています。
MotionBuilderでの作業が終了したらMayaSceneを構築してエラーやめり込みなどの最終確認をします。
ShotWorkではアニメーション室のデーターを元に各ジャンルの作業が進むので責任重大です!!
しかし構築関係はヒューマンエラーが出やすいジャンルだと思うので、これらの作業は殆ど自動で行われています。
確認作業とめり込み修正だけは担当者がする感じですね。
以上のワークフローを見ても分かる通りMotionBuilderの使用率が大半を占めています。
その理由の1つとして言えるのが再生速度の速さです!!
キャラクターが多くなれば多少のコマ落ちはしますが、体感ではほぼリアルタイムで再生ができ、更にその状態でエディット作業が出来てしまうところだと思います。
個人的な意見ですがレイアウト作業(Layout Artist )やアニメーション作業(Animator )で一番うれしい機能は扱いやすいリギングと何度でもトライ&エラーできるスピードかと思っています。
経験を経て感覚を掴めてくるようになれば、自ずと作業時間は短くなりトライ&エラーの回数も減ってくるかもしれませんが、
やはり色々な条件を試し、より良くなる方法を追求したくなるかと思うのでMotionBuilderの満足度は高いです!

※レイアウト作業では再生速度も重要だと思いますが、それ以外にも重要なポイントがあるので次回にでもお話しできたらと考えています。

実際MotionBuilderがどのくらい早いかを動画で見てみましょう

キャラクター27体
簡易エフェクト:雨
簡易ライティング
簡易影
簡易フォグ

© SEGA
※モーションキャプチャ及びフェイシャルキャプチャ、シーンエディットを担当

動画を見てお分かりになったと思いますが、キャラクターがこの体数で簡易エフェクトや接地用の影も表示しても尚20fps前後の再生速度が出ます。

XSIで同じシーンのfpsを調べてみたところ・・・1fps前後(笑)という結果になりました。
まともに再生しないことが分かりますね。(1番再生の早いWireframe状態です)
MotionBuilderはXSIの約20倍以上という驚愕の速さだとお分かり頂けたと思います。

※Mayaでもほぼ同じ結果になると思います。

簡易エフェクトやShadowをHideした状態の動画

キャラクター27体(透明マッピングの表示はさせていません)
簡易フォグ

© SEGA
※モーションキャプチャ及びフェイシャルキャプチャ、シーンエディットを担当

この状態であればほぼ30fpsで再生してしまいます。
ここまでストレス無く再生できると納得いくまでトライ&エラーに集中できますね。

影や簡易エフェクトがない状態だと約40倍以上の早さになりますね!!

※30fps以上を表示しているフレームもありますが、MotionBuilder上の設定で30fpsで再生速度を設定しているのでリアルタイムでは30fps以上の再生速度は出ていません。

今回のfpsテストで使用したバージョンはMotionBuilder2013と2015になります。

MotionBuilderの扱いやすいRigging

先ほどお話した扱いやすいリギングという点でもMotionBuilderは優れていると思います。
シンプルなリギングの分、他のソフトと比べるとDeformerやSplineIKなど対応していない所もありますが、
昨年開催された、クリエイティブカンファレンスにおいて弊社開発室が発表したSplaineIKのプラグインもあり、
更に使いやすくカスタマイズされています。
簡単にIKからFKに切り替えられ、階層の組み替えも簡単にでき、fullbodyやBodyPartなど瞬時に切り替えができるソフトはなかなか無いと思っています。
更にこれらの機能が標準で装備されていながら軽いという所が最大のポイントかと思っています。
とMotionBuilderの事を熱く語ってしまいましたが、あくまでも個人的な視点でお話させていただきました。
再生の速さとリギングの扱いやすさ以外にまだまだ沢山の機能が豊富にそろっているので、
これを機に今後もこちらで色々な機能を紹介させてもらえればと思っています。

それと、クリエィテイブカンファレンスつながりで、今年もクリカンに参加することになりました。
http://cgw.borndigital.jp/2014/session.html#a-4
既に申し込みは締め切られてしまっているので、事前に参加申し込みを済ませている方限定となってしまいますが、当日は是非弊社のセッションにお越しください!!
当日は、4Kの映像を60fpsで再生できる環境をご用意してお待ちしております。

アニメーター大募集!

2015年1月からデジタルフロンティア大阪を始動いたします。
スターティングメンバーとして一緒にDF大阪を盛り上げてくれるアニメーターを募集しております。
経験者の方はもちろん、初心者の方でもレイアウトやアニメーションが好きな方や興味がある方応募お待ちしてます!!
3Dソフトの経験があればMotionBuilderの経験が無くても大丈夫です!!弊社でお勉強して頂きます!!
もちろん東京でも随時応募お待ちしております。
募集要項はこちらになります。よろしくお願いします!!

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


≪アニメーション室≫ DFでのLayout作業とは!!

$
0
0

皆さん、こんにちは。二回目の登場になりますアニメーション・フェイシャル室の藤松です。
前回は個人的な視点でMotionBuilderについて熱く語ってしまいましたね。
でも、それだけアニメーションという分野で優れたソフトだと思うからこそ語らせてもらいました(笑)
今までMaya・XSI・MAX・MotionBuilderでレイアウトやアニメーション作業を行ったからこそ分かる内容です。
一般的な使い方はキャプチャーデーターを変換するツールとしか使われていないのが現状だと思いますが、
1歩踏み込んで使い始めると、なかなか離れられなくなるソフトだと思っています。

前回は話の途中でレイアウト作業というキーワードを出させてもらいました。
今回はその事について、もう少し詳しくお話していきたいと思います。

日本ではLayout作業を専門でやっている人は少ないと思いますし、分業している会社も少ないと思います。
DFでも分業していませんが、アニメーション室の中ではとても重要視しています。

DFでのLayout作業の流れ

画像でもわかる通り、Layout作業は各部署の素材や資料が集中的に集まってくる工程になります。
最後には各部署へのデーターUP作業も必要になるので、エラーが出ないように自社のtoolにて自動化しています。

DFでのレイアウト作業はアニマティクス(Animatics)と呼んでいます。
アニマティクスとはコンテなどの大雑把な2Dイメージを3Dにしていくのが主なお仕事になります。
コンテの1枚絵では良く見えるのに、3Dでは思ったように表現が出来ないことがとても多いです。
なので、コンテの意図をふまえたうえで、自分で構図やカメラワークを考えます。

主な作業内容は、ローモデルでのカメラワークやキャラクターのアニメーション付け、そしてカットの構図を作成することです。
この段階で爆発や破壊のタイミングや大筋のキーライトも決めてしまいますし、
カットの追加や削除もある程度各自の判断で行います。
アニメ業界でいう原画の役割と似ていますね。
DFではこれらの作業をかなり詰めた所まで持っていくのでアニマティクスと呼んでいます。
なので、アニマティクスを見れば後のショット作業に必要な情報が具体的になっているので、
その後の作業がブレる事が少ないです。

※海外ではAnimatics→Layout(Previs)といった流れで作業するのが一般的ですが、
DFでは昔からAnimatics=Layout(Previs)といったい感じで作業しているので、
違和感を感じる方がいると思いますがご理解ください。

良いレイアウトと悪いレイアウト

画像を使って良いレイアウトと悪いレイアウトを紹介します。

悪いレイアウトの例

この画像は左右ともに悪いレイアウトの例です。
左の画像は人物が左右に寄りすぎて、中心に空間を空けすぎですし、頭上の空間が狭すぎて圧迫感が出てしまいます。
次に右の画像では、黄色い人物を画面中心でとらえすぎて、画面の上手と頭上に空間を空けすぎています。

これらのように構図が安定しないと違和感が出てしまいます。

良いレイアウトの例

左の画像は上手と頭上の空間に注意しながらレイアウトしました。
安定した構図になったと思います。

その理由を右の画像で解説します。
カメラなどでよく使われるテクニックとして三分割法というのがあります。
画面の上下左右を均等に三分割にわけ、その線上や交差する点に重要な要素を配置する方法です。
そうすると全体のバランスが取れ安定した構図が作れます。
右の画像のように、赤で丸く囲んだ所と赤線に重要な要素を配置したので安定した構図になりました。

このようにカメラの撮影テクニックを使用するとレイアウトの上達に役立ちます。
特にこれからCGを学ぶ方は参考にしてください。

Layout作業で求められる能力

もともとレイアウトとアニメーションでは求められる能力が違うと思っているので、
どちらもできる人材はとても貴重だと思っています。
個人的な見解ですが、レイアウトではある程度カメラの知識と編集の知識が必要だと思っています。
カメラの知識と書きましたが、実際に撮影現場でカメラを扱えるほどの知識は必要ありません。
良いレイアウトと悪いレイアウトでも説明しましたが撮影テクニックが分かれば十分です。
後はシーンの状況に応じたレンズ選びやカット構成を考えた構図作りの知識や、
演出上カットを追加したり欠番にしたりする演出能力も必要だと思います。
そう考えるとアニメーション以外に必要な事が色々ある事がわかりますね。
※あくまでも個人的な見解です

まとめると映像のベースを作っている事がお分かりになったと思います。
基本コンテはありますが、大筋演出の意図が変わらなければ、ある程度作業者自身に構成を任せられている作業になります。

逆にコンテが完璧であれば、それをなぞるように作れば完成してしまいそうですが、
完璧なコンテはほぼありません。(笑)
その後の工程はアニマティクスをベースに最終的な質感を詰めていく作業になるので、とても重要な作業だと思います。

そんな映像作品の核になる作業のアニマティクス!!!一緒に作ってみませんか!!
っと、この流れで前回の記事でも書きましたが、アニメーターの募集もしちゃいます!!

アニメーター大募集!

2015年1月からデジタルフロンティア大阪を始動いたします。
スターティングメンバーとして一緒にDF大阪を盛り上げてくれるアニメーターを募集しております。
経験者の方はもちろん、初心者の方でもレイアウトやアニメーションが好きな方や興味がある方応募お待ちしてます!!
3Dソフトの経験があればMotionBuilderの経験が無くても大丈夫です!!弊社でお勉強して頂きます!!
もちろん東京でも随時応募お待ちしております。

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

DFでのレイアウト作業はアニマティクス(Animatics)と呼んでいます。
アニマティクスとはコンテなどの大雑把な2Dイメージを3Dにしていくのが主なお仕事になります。
コンテの1枚絵では良く見えるのに、3Dでは思ったように表現が出来ないことがとても多いです。
なので、コンテの意図をふまえてうえで、自分で構図やカメラワークを考えることが多いです。
主な作業内容は各ジャンルのローモデルなどを使用して、カメラワークやキャラクターアニメーションをつけて、カットの構図を作っていく仕事です。
この段階で爆発や破壊のタイミングや大筋のキーライトも決めてしまいますし、カットの追加や削除もある程度各自の判断で行います。
アニメ業界でいう原画と似ていますね。
DFではこれらの作業をかなり詰めた所まで持っていくのでアニマティクスと呼んでいます。
なので、アニマティクスを見れば後のショット作業に必要な情報が具体的になっているので、その後の作業がブレる事が少ないです。

[Maya] Latticeを編集した後でDivisionを変えてみる

$
0
0

こんにちは。TD室の石田です。
今回はタイトルの通り編集した後のLatticeのDivisionを変える方法を試みてみました。

Mayaユーザーの方はご存知だと思いますが、Latticeの頂点を動かすとその後一切Division数を変更することが出来なくなります。
地味だけどイラっとくる仕様ですよね。
という訳でDivisionが本当に変更が出来ないのか検証してみました。

 
 

    ■■■■ Latticeのアトリビュートを調べてみる ■■■■

さて、変更したいからと言ってどこから調べたらいいものかと考えて…。
「そういえばmayaAscii(以下.ma)でデータの検証をしてた時にLatticeの記述に違和感があったなぁ」
と言う記憶からLatticeを保存した.maの記述を読んでみました。

[cpp num =1]
createNode lattice -n “ffd1LatticeShape” -p “ffd1Lattice”;
setAttr “.cc” -type “lattice” 2 2 2 8 -0.5 -0.5 -0.5 …. ;
[/cpp]

.ccアトリビュートにlattceType…、だと??
試しにLatticeを作ってDivisionと頂点を変えて.maに記述された文をコピペで実行してみました。
あら、不思議、先ほど保存した状態に戻っちゃった。てことは

編集した後でもDivisionが変えられる!!

話しは戻りますが、上記で「Latticeの記述に違和感があった」と書きました。
その違和感を説明したいと思います。 
meshを例にすると

[cpp num =1]
setAttr “.vt[0:10]” 0 0 0 …..;
[/cpp]

のように.maで記述されています。script操作でもやっている記述がズラズラと書かれています。
mesh.vtxを選択するとscriptEditerに「select -r pSphere1.vtx[0] ;」と出るので
scriptで動かすにはここを指定してあげればいい、と言うことは想像出来ますよね。

であればLatticeは「select -r ffd1Lattice.cc[0] ;」と出るのか?と言うとそんな訳ありません。
「select -r ffd1Lattice.pt[0][0][0] ;」と出ます。
でも.maの記述は.ccアトリビュートにsetAttrをしています。.ptにsetAttrしていません。

これは何かある…。
この.ccのデータタイプはなんなのか。と思ってmanualで調べてみると

アトリビュート名:cached (cc)
データタイプ:lattice

mesh、nurbsSurfaceなどもこういったデータ型があります。
latticeの場合はlattice型をここに入れると反映されるみたいですね。
試した方もいるかと思いますがこういったデータ型はgetAttrしても情報を取得出来ません。

mesh、nurbsSurfaceなどには残念ながらcachedのようなアトリビュートは…、
あれ、、、無いかと思ったらcachedInMeshって何ぞ??時間が無いので今度調べてみますw

話しは戻りまして、
cachedでsetAttrが出来るならgetAttrも出来るんじゃ…、と思いきや出来ませんでした。
getAttrが出来れば情報を取得してLatticeのDivisionを変えられるのに…。
という訳でscriptを書きました。

 
 

    ■■■■ lattice編集script ■■■■

[python num =1]
#-*- coding:utf-8
import maya.cmds as cmds
import maya.mel as mel

#editDivisions(‘ffd1Lattice’,s=4,t=4,u=4)

def editDivisions(*arg,**kwargs):
sd = kwargs.get(‘sDivisions’,kwargs.get(‘s’,2))
td = kwargs.get(‘tDivisions’,kwargs.get(‘t’,2))
ud = kwargs.get(‘uDivisions’,kwargs.get(‘u’,2))
ltc = arg
if not arg:
ltc_list = cmds.ls(cmds.ls(sl=1,dag=1,s=1),type=’lattice’)
if not ltc_list:
cmds.warning(‘please select lattice.’)
return
else:
ltc_list = cmds.ls(cmds.ls(ltc,dag=1,s=1),type=’lattice’)
if not ltc_list:
cmds.warning(‘please select lattice.’)
return

ltc = ltc_list[0]
p_ltc = cmds.listRelatives(ltc,p=1)[0]
p_base,ltc_base,ltc_ffd = getltcNodes(ltc)

#
## Latticeが移動していた場合、ダミーLatticeの頂点がワールド座標になるためその回避処理
reCnctNode = cmds.listConnections(ltc_base+’.worldMatrix[0]‘,c=1,p=1)
cmds.connectAttr(p_ltc+’.worldMatrix[0]‘,ltc_ffd+’.baseLatticeMatrix’,f=1)

#
## ダミーLatticeを作成して編集したいLatticeにデフォームをかける
p_dmyLtc,dmyLtc = createDmyLtc(p_ltc,sd,td,ud)
cmds.lattice(ltc,e=1,g=dmyLtc)

#
## Latticeの頂点全座標を取得する。
ltcPts = cmds.ls(dmyLtc+’.pt[*]‘,fl=1)
resultPoints = []
for ltcPt in ltcPts:
resultPoints.append( cmds.xform(ltcPt,q=1,os=1,t=1) )

#
## ダミーと回避処理を無かったことにする
cmds.delete(p_dmyLtc)
cmds.connectAttr(reCnctNode[0],reCnctNode[1],f=1)

#
## setAttr(.cc)するための記述を纏める。mel版
vCmd = ‘ ‘.join( [str(sd),str(td),str(ud),str(sd*td*ud)] )
strPts = []
for pt in resultPoints:
for value in pt:
strPts.append(str(value))
ptCmd = ‘ ‘.join(strPts)
setMel = ‘setAttr “%s.cc” -type lattice %s;’%(ltc,vCmd+’ ‘+ptCmd)
print setMel
mel.eval(setMel)

## setAttr(.cc)するための記述を纏める。python版
#
## 記事では触れませんが、pythonはargumentを255個以上渡すとエラーになるようです。
#
#vCmd = ‘,’.join( [str(sd),str(td),str(ud),str(sd*td*ud)] )
#strPts = []
#for pt in resultPoints:
# strPts.append(str(pt))
#ptCmd = ‘,’.join(strPts)
#setCmd = ‘cmds.setAttr(“%s.cc”,%s,type=”lattice”)’%(ltc,vCmd+’,'+ptCmd)
#print setCmd
#cmds.delete(p_dmyLtc)
#eval(setCmd)

#
# LatticeBase,LatticeBaseShape,FFD のノードを取得する。
def getltcNodes(ltc):
ffd_node = cmds.listConnections(ltc,s=0,d=1)
ltc_base = cmds.ls(cmds.listConnections(ffd_node,s=1,d=0,sh=1),type=’baseLattice’)
p_base = cmds.listRelatives(ltc_base[0],p=1)[0]
return p_base,ltc_base[0],ffd_node[0]

#
# Latticeのデータ型を取得するためにダミーのLatticeを新しく作成する。
def createDmyLtc(node,sd,td,ud):
p_ltc = cmds.createNode(‘transform’,ss=1,n = ‘dmy_’+node,p = node)
d_ltc = cmds.createNode(‘lattice’,ss=1,n = ‘dmy_’+node+’Shape’,p = p_ltc)
cmds.setAttr(d_ltc+’.sDivisions’,sd)
cmds.setAttr(d_ltc+’.tDivisions’,td)
cmds.setAttr(d_ltc+’.uDivisions’,ud)
cmds.parent(p_ltc,w=1)
return p_ltc,d_ltc
[/python]

 
軽く処理の流れを説明します。

  1. Divisionを編集したいLatticeを指定
  2. 入力されたDivisionでダミーLatticeを作成する
  3. Latticeが動いている場合用にobject座標を取得するために小手先処理
  4. LatticeにダミーLatticeを追加
  5. 変形したダミーLatticeの頂点座標を取得する
  6. Lattice.cc用にsetAttr文を整理する
  7. Lattice.ccにset

見ての通り無理やり感がありますね…。
2、4、5、はFFD(free form deformation)の関数が用意されていればスマートに書けたのに…。
3、はmatrix関数を使えばコネクションを繋げ直したりしなくてもよくなります。
6、7、はlattice型をgetAttr出来ればいいのに…。

2、4、5、はFFDが実装出来たら記事がたくさん書けるのでパスします。と言うか荷が重いです^ ^
3、は行数が増えるので今回はパスします。
今回は特別に6、7、の処理をOpenMayaで書きました!!解説はしません!!w

[python num =1]
import maya.cmds as cmds
import maya.api.OpenMaya as om

def editDivisions2(*arg,**kwargs):
sd = kwargs.get(‘sDivisions’,kwargs.get(‘s’,2))
td = kwargs.get(‘tDivisions’,kwargs.get(‘t’,2))
ud = kwargs.get(‘uDivisions’,kwargs.get(‘u’,2))
ltc = arg
if not arg:
ltc_list = cmds.ls(cmds.ls(sl=1,dag=1,s=1),type=’lattice’)
if not ltc_list:
cmds.warning(‘please select lattice node.’)
return
else:
ltc_list = cmds.ls(cmds.ls(ltc,dag=1,s=1),type=’lattice’)
if not ltc_list:
cmds.warning(‘please select lattice node.’)
return

ltc = ltc_list[0]
p_ltc = cmds.listRelatives(ltc,p=1)[0]
p_base,ltc_base,ltc_ffd = getltcNodes(ltc)
reCnctNode = cmds.listConnections(ltc_base+’.worldMatrix[0]‘,c=1,p=1)
cmds.connectAttr(p_ltc+’.worldMatrix[0]‘,ltc_ffd+’.baseLatticeMatrix’,f=1)
p_dmyLtc,dmyLtc = createDmyLtc(p_ltc,sd,td,ud)
cmds.lattice(ltc,e=1,g=dmyLtc)

ltcData = getMData(dmyLtc,a=’cc’)
setMData(ltc,a=’cc’,id=ltcData)

cmds.delete(p_dmyLtc)
cmds.connectAttr(reCnctNode[0],reCnctNode[1],f=1)

def getMObject(node):
sel_list = om.MSelectionList()
sel_list.add( node )
dagPath = sel_list.getDagPath( 0 )
dagPath.extendToShape()
return dagPath.node()

def getMData(*arg,**kwargs):
attr = kwargs.get(‘attr’,kwargs.get(‘a’,None))
mobj = getMObject(arg[0])

nodeFn = om.MFnDependencyNode( mobj )
attribute = nodeFn.attribute(attr)
plug = om.MPlug(mobj, attribute)
return plug.asMObject()

def setMData(*arg,**kwargs):
inData = kwargs.get(‘inData’,kwargs.get(‘id’,None))
attr = kwargs.get(‘attr’,kwargs.get(‘a’,None))
mobj = getMObject(arg[0])

nodeFn = om.MFnDependencyNode( mobj )
attribute = nodeFn.attribute(attr)
inPlug = om.MPlug(mobj, attribute)
inPlug.setMObject(inData)
[/python]

 

OpenMayaはステキですね!!scriptレベルでなんでも出来る!!
cachedいいですね。
latticeに注目していなかったら気が付かなかったなぁ…。
これはたぶんMeshでも出来そうな予感…。夢が広がる…!!

ちなみにundoは出来ませんので注意を!!w
それではまた~

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

≪セットアップ室≫リギングアーティストを目指す君へ。セットアップ室ってこんなところ

$
0
0

みなさんこんにちは。野澤です。
すっかり寒くなりましたね。ついこの前一年が始まったと思ったらもう終わりそうですね。。
さて、今日はDFのセットアップ室について紹介してみようかなと思います。
実は4月の組織改変で、私がセットアップ室の統括をする立場となりまして…。
いい機会なのでちょっと紹介させてください。

セットアップってなに?

セットアップと言うのはおそらく日本だけで、海外だとリギングって呼んだりするみたいですが、キャラクターや小道具や乗り物などをアニメーションさせるための準備をする行程の総称です。
DFでは、このセットアップを専門にするリギングアーティストと呼ばれる職種があります。
キャラクター室から上がってきたキャラクターに骨を入れ、スキニングを施し、コントローラを仕込み、アニメーターが使いやすいセットアップを目指します。
この説明だけ聞くと、技術や知識が先行しがちな印象を受けるかもしれませんが、実はそうとも言えません。なぜならCGアニメーションは造形が1フレーム単位で変化しているパラパラ漫画のようなものだからです。
どんなにすごい知識を持っている人でも、身体の変形具合いの違和感に気付けないと、この仕事は成り立ちません。
また、せっかく綺麗に変形出来ても、操作が重くてアニメーターの足枷になってしまうようなセットアップでは方々から非難轟々です。
だから私はセットアップをする人はモデリングセンスを持ち合わせている事がもっとも重要だと思っています。
それに加えて、アニメーションを付けられる様に、コントローラーを仕込んだり、身体の変化で小道具がストレスなく自動で動く様なギミックを考える合理的な思考も必要になります。

ここまで読んでもらって、セットアップってちょっと楽しそうかもって思ったあなた!
そうです、セットアップはすごく楽しいのです。
魅力あふれるキャラクターを自分が考えたセットアップで動かせる様に出来て、モデリングで服のシワや筋肉を作ることも出来るんです。

例えば、筋肉ってずっと同じ形だとすごく変に見えるんですよ。
ポーズの変化に合わせて違和感なく身体や衣服が変形していくのを構築していく作業はとてもクリエイティブでホントに楽しいです。

そんなセットアップに少し興味を持ってくれた貴重な方々の為に、もう少し掘り下げていこうと思います。

事前準備

DFではキャラクターデザインが固まってきた辺りで、リギングアーティストとディレクターとの間で演出ミーティングをします。
身体の部位を穴が空くほど見て、どの様に変形したら魅力が出るかを話し合います。
キャラクターアーティストが実際に作り込む前に、後の行程で動かしやすい様にアーティストとコンセンサスをとったり、効率よくセットアップを進められるようにモデリングしてもらう事前準備がすごく重要です。

また布や髪の毛を動かすのもリギングアーティストの仕事です。
完成したキャラクターが上がってきたら、いよいよセットアップ作業の始まりです。

あ、、ちょっと待って、ここでいきなり手を動かし始めるのは間違いです。
セットアップって実は同じ作業を繰り返す事がとても多いんです。
なので、まずはどのパーツとどのパーツは共通化出来るか、仕組みが似ている箇所はないかなどを自分の担当キャラクターだけでなく、同じ作品の中に出てくる別のキャラクターなどとも見比べながら考えます。
場合によっては過去の作品の仕組みを応用出来ないかなども検討します。
共通化出来るものがあったら流用出来るような仕組みにセットアップします。
だって繰り返しの作業は人間よりもコンピュータがやった方が速くて正確ですからね。

こうして、自分の担当キャラクターを与えられた時間のなかでいかに効率よく出来るか綿密な計画を立てたらいよいよスキニング開始です。

リギング

ここで、ゼロからセットアップ作業をしていくのは時間の無駄なので、DFではリギング補助ツールがいろいろ揃っています。
キャラクターに生命を吹き込む為に必要な、クリエイティブな作業になるべく集中出来る様に繰り返し作業はどんどん自動化しています。

リギングアーティストを目指す人はMELスクリプトやPythonトを覚えると作業スピードがどんどん上がります。

シミュレーションセットアップ

セットアップ作業が終盤に差し掛かると次に来るのがシミュレーションセットアップです。
髪の毛や服、しっぽなど揺らす必要がある箇所すべてに色んな手法を駆使してシミュレーションセットアップをしていきます。
Maya標準のnClothやnHairはもちろんのこと、サードパーティのクロスシミュレーションプラグインや内製のシミュレーションプラグインなど手法は様々です。

こうしてセットアップを終えてアニメーターが使える状態になったら、一旦アニメーターの手に委ねます。
詳しい説明は省きますが、弊社ではMotionBuilderとMayaでアニメーションが付けられるようにセットアップする必要があります。

シミュレーション作業

アニメーターの努力の結晶から生まれたアニメーションにディレクターOKが出たら、次はシミュレーション作業です。
揺らした方が自然に見える部分は、可能な限り揺らします。
ですがこれも全て一からやらずに一回目のシミュレーションまでの仕込みは自動化し、自動でシミュレーションされたものの修正から作業出来る様な内製ツールを活用しています。
シミュレーションをする上でとても大事なことはリズム感と柔らかさ、アニメーションの滑らかさです。
セットアップでは、モデリングセンスとロジカルシンキングを使い、シミュレーションではアニメーションセンスとリズム感を働かせます。
セットアッパーによってどちらかの作業の方が得意、不得意が多少出ますが、タイプの異なる作業を交互に行うことで、マンネリ化を防ぎ脳のリフレッシュにも繋がっています。

まとめ

キャラクターを作ったり、背景を作ったり、エフェクトを作ったりといった派手さはありませんが、作品の中ですごく重要なウェイトを占めた、非常にやり甲斐のあるリギングアーティストにあなたもなってみませんか?

DFセットアップ室では、経験者、未経験者問わずリギングアーティストを大募集しております。
我こそはと思う方は是非下記のリンクからご応募ください。
ご応募はこちら

今回はすごく浅いレベルの紹介になってしまいましたが、ここで終わらせるとリクルート目的の宣伝記事じゃないかとお叱りを受けてしまいそうなので、ここで一つ皆様にお約束をさせて頂きます。
来年の一月より、我がセットアップ室の現役リギングアーティスト達(+私)による実践で役に立つリギング講座をこのDF TALKでシリーズ化し不定期連載します!!
是非ご期待ください!
記事に期待してもらえる方は下のいいね!を押してください。
みなさんのいいね!が彼らと主に私のエネルギーの源となります笑
いいね!が少なかったらやm(ry

それではまた来年!

徒然なるままに #3 [AfterEffects: nukeのLog2Linを実装してみる](プラグインのおまけつき)

$
0
0

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

今回は、AfterEffects(以下AE)のCineon(log)変換プラグインを実装した時の流れを紹介してみます。
最後におまけも付けておきましたので、是非ご一読をw

概要(コンポジット室からの依頼)

以前、コンポジット室より以下の様な相談を受けました。

AfterEffectsのCineonConverterを使うと、32bitモードだとしても
Negative Value(マイナス値)が死んでしまう(clampされてしまう)のだが、なんとかならないか?

とある実写映画の案件で、コンポジット室は以下のような流れを想定していました。

  1. 編集所から来たdpx(Cineon)をlinearに変換
  2. linearでコンポジット
  3. 逆変換を掛けたdpx(Cineon)を編集所へ納品

ところが、[1.]の変換でAEのCineonConverterを使ってしまうとあるべきマイナス値がなくなってしまい、[3.]の逆変換を掛けた時に絵が完全には戻らない事案が発生したと言うのです。つまり、dpxの情報が欠損してしまうわけです。

本当かなー?と思って試してみたところ、本当でした(笑)(AE CCの32bitモードで検証)
nukeのLog2Linというノードではそんな事はなく、問題なく元に戻せました。(さすがnuke)

じゃあnukeだけ使ってコンプすれば良いじゃんという話ではあるのですが、ライセンス数や慣れの問題もあり、簡単な事ではありません。DFではまだAEでのコンポジットも現役であり、対応しなければならないのです。
 

nukeのCineon計算式

ではまず、nuke上でのCineonカーブがどのような計算式(定義)になってるのか確認してみましょう。
nukeを立ち上げ、Node Graph上で “s” キーを押すと、Project Settingsが出てきます。
そこのLUTタブのCineonをクリックすると、以下の様なカーブが表示されます。

これがnukeで定義されているCineonカーブです。

では次に、このCineonカーブの中身をみてみましょう。
実はnukeでは、このようなカーブ計算式が簡単に見れるようになっています。

カーブ上にマウスカーソルを置き、 [右クリック] – [Edit] – [Edit Expression] を実行します。

すると、以下の様な計算式が表示されます。

素敵だ・・素敵過ぎるぜnuke・・。
これはやはりAEをオミットしてnukeに(ry

..失礼しました。

さて、今度はnukeのLog2Linノードを作ってみましょう。
nukeのNode Graph上でtabを押し、”Log2Lin”と打ちこむと以下の様なノードが作られます。

black, white, gammaなどのパラメータがあるので、先程の式とどう違ってくるのかを検証します。

dpxを「①Cineon(ReadノードのcolorspaceでCineonを選択)で読んだもの」と、「②linear(Readノードのcolorspaceでlinearを選択)で読んでLog2Linをデフォルトで掛けたもの」を比較します。すると①と②の結果が完全に一致したため、先程の計算式はこのLog2Linのパラメータデフォルトと同一である事が分かりました。

コンポジット室に確認したところ、このデフォルト設定が当てられて元に戻せればそれで良いという事だったので、この計算式をAEに移植する事にします。上記三つのパラメータくらいは実装しようかと思ったのですが、blackの計算式へのあてはめ方がすぐには分からなかったので今回は諦めました。(その後解明出来たのですが面倒なので実装してません 笑)
 

AfterEffectsへの実装

実はAEでは、このような計算式を簡単に当てられるエクスプレッション等を書く事は出来ません。
PixelBenderなら簡単に出来たんじゃないかなぁとか思いつつも、既にAdobeにオミットされてしまったのでその手も使えません。

と言う事はつまり、プラグインとして実装するしかないんですね。(AE敷居たけーw)
とはいえ、このくらいの計算を行うプラグインであれば、そこまで大変ではありません。

まずおさらいですが、Log2Linの計算式は以下になります。
[cpp]
(pow(10, (1023*x – 685) / 300) – 0.0108) / (1 – 0.0108)
[/cpp]

 
そして、逆変換の計算式も出しておきます。
[cpp]
(log10((1 – 0.0108)*x + 0.0108) * 300 + 685) / 1023
[/cpp]
 

さーここまできたら後は実装するだけです!


実装しました↓

簡単ですね!(ぁ

諸々はしょっちゃって申し訳ないんですが、今回はプラグインもシェアさせて頂きますので許して下さい(笑)
もし興味があれば使ってやって下さい。

Downloadはコチラ

但し、追加要望やバグ報告があっても基本的には対応致しませんw
あくまで自己責任でご使用下さいネ。

これにて無事に、AEの「0(ゼロ)clampされないCineon変換プラグイン」が出来ました。
はー良かった良かった。

Special Thanks to Yasshi !

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

 

コンポジター募集中!!

コンポジット室室長に頼まれたのでついでに広告しときますw

≪コンポジター募集中!!≫

只今デジタル フロンティアでは専業のコンポジターを募集しています。
現在も実写やフルCG限らず様々なプロジェクトが目白押しの中、
本年末にかけても大型プロジェクトが複数控えており一緒にフィニッシュワークを
担当していただける仲間を大募集中です!
経験者の方は勿論ですが初心者の方でもコンポジットワーク、画像処理、
カラーマネジメント等に興味のある方なら大歓迎です。

募集要項はこちらになります。よろしくお願いします!!
【-コンポジット室- コンポジター】募集要項

 


■甘味紹介 #3 [レイズンウィッチ]

今回は 小川軒レイズンウィッチ をご紹介。

(DF甘味部で撮影した写真なので別の甘味も入っちゃってますがご勘弁をw)

手土産として評価の高いレイズンウィッチですが、実はDFのすぐ近くに店舗が存在します。

小川軒 [HP]
小川軒 [食べログ]

僕が初めて食べたのは、新橋にある「巴里 小川軒」のレイズン・ウィッチでした。
でも今は渋谷勤務だしもう行くこともないかなーと思っていた矢先、Tokyu Food Showに小川軒が・・!(臨時出店してました)
すかさず購入し、その際に「これって新橋にあるお店ですよね?」と聞いたところ「いえいえそうではありません」とのご返答。なんでかなーと思ったら、小川軒って幾つかに分かれて存在してるんですね。
小川軒レーズンウイッチの解剖

なんか色々あるんですなー。
人生って難しい。
でもたまに食べたくなる。
それがレイズンウィッチ。

さー今日も一日頑張ろう。

≪セットアップ室≫MotionBuilderの為のキャラクターセットアップ

$
0
0

はじめまして。セットアップ室の松山です。

今回のDF_TALKではDFセットアップ室の業務内容と、その中でも初めに行う”LDセットアップ”について紹介したいと思います。
セットアップに興味を持っている方のちょっとした助けになれば幸いです。

各種セットアップの紹介

まずは、弊社のセットアップ作業を簡単に説明します。大きく分けて下記の4つになります。

LDセットアップ

アニメーションの為の軽量セットアップ
弊社では主にMotionBuilderでのセットアップまでを指す

HDセットアップ

レンダリングの為の最終セットアップ
弊社では主にMayaでのセットアップを指す

シミュレーションセットアップ

衣服、毛髪のシミュレーションの為のセットアップ

シミュレーションショットワーク

アニメーションの入ったシーンに対してのCloth/Hairシミュレーション

motionBuilderのパフォーマンス

弊社でのアニメーション作業は、主にMotionBuilderで行います。メリットとしては、Mayaと比較して圧倒的に高速であることがまず上げられます。詳しくは↓の記事をご覧ください。
≪アニメーション室≫ DFは何故MotionBuilderを使い続けるのか!!
アニメーション作業に関してMotionBuilderがいかに有効であるかを力説しています。

LDセットアップについて

まず始めにジョイント作成を行うわけですが、ここでの基本方針について触れておきます。

バインドポーズはリラックスポーズ。ただしRotateを(0,0,0)にした場合Tポーズになるように

基本的には腕を45度程下ろした状態でバインドを行っています。ただし、最終的にMotionBuilder上でCharacterizeという処理はT-ポーズで行います。なのでT-ポーズから腕を下ろす際の角度はRotate値として残しておきます。

T-ポーズにした時の軸をWorld座標と同じにしておく

弊社ではジョイントの位置調整を、例えば左肩から肘ならX軸+、左股関節から膝ならY軸-方向へのTranslateで行っています。基本的にはTranslate1軸+Rotateです。

初期位置に即座に戻れるようにしておく

バインドポーズをSet Preffered Angleします。PrefferdAngleというアトリビュートとしての本来の用途とは違うのですが、適切に設定していればGo To BindPoseよりも安定して、しかも部分的に初期位置に戻ることが(特別なツールを必要とせずに)出来ます。

試行錯誤する上で、”最初に戻る”という動作は極力手順を省きたいです

ジョイント作成が完了したらバインドします。この時、スキニング補助の為にリグを作成することもあります。
シェイプを利用しないコンストレイン(Point,Orient,Aim,Parent,Scale,PoleVector)、 SingleChain/RotatePlaneIKハンドルはそのままMotionBuilderに移行できます。エクスプレッションやノード類は手間がかかりますが処理によっては再現可能です。※詳しくは割愛します。

Maya上でセットアップが完了したらFbxとしてExportします。
※Exportする際はバインドポーズで行います。どんなポーズでExportしても、モデルのNormal(法線)はバインドポーズ時点のものが出力されてしまうので、例えば90度傾いた状態でExportすると、Normalが90度傾いた状態になってしまいます。

左がバインドポーズ、右がY軸に90度回転させた状態でExportしたモデルです。正しくライティング出来ない・・・

同様にバインドポーズ、90度回転させたSphereです。Normalの向きが不正なのがわかります。

Characterize

MotionBuilderでCharacterizeという処理を行います。
Characterizeとは、簡単に言うとそのままではただのジョイント階層に過ぎないデータを、キャラクターとして定義することです。Characterizeを行う事で、キャラデータはモーションキャプチャーのデータを利用し、HumanIKでアニメーション編集し、またキャラクター同士のモーション流用が可能になります。

弊社でデータ上気をつけていることは、Characterizeは必ず腕、脚、指のジョイントを水平もしくは垂直にし、Twistさせない状態で行う事です。特にLDセットアップでは簡易的な(Rotate値をそのまま利用する)補助ジョイントを使用する事が多いので、ヒンジ(単軸)回転するジョイント(肘、膝、指)に極力3軸回転させないようにしています。

極端な例ですが肩が内旋した状態でCharacterizeすると・・・

肘が内旋しているにも拘らず作成されるControllRigの肘関節は水平な軸になります。

肘の向きが違う上に当然3軸Rotate値が入ります。データ納品で制限がある場合致命的ですね。

Characterの機能の紹介

Characterには色々設定項目があるのですが、今回はその中で”Roll”という項目を紹介します。

Characterを選択すると、NavigatorにCharacterの各種設定項目が表示されます。Modifiersの中にRoll項目が存在します。

Rollは、肩と手首を回転させた時に肘ジョイントの挙動を定義します。HumanIKで肩を回転させた時、Roll回転を肘に伝えるかどうかがここで決まります。極力ヒンジ関節に3軸RotateはさせたくないのでRoll値は0.0にしています。※肩と手首には補助ジョイントを作成し、肩の根元からRollすることを防いでいます。

Relationコンストレイン

MotionBuilderはMayaと比較して、再生パフォーマンスが非常に優れていますが、ノード数に対する速度低下(特にOpenScene時)はMayaと比べて顕著です。
なのでノード数は極力少なくすることが求められるのですが、その際コンストレインを1つにまとめる為にRelationコンストレインを利用しています。また、Mayaで組んだエクスプレッションをMotionBuilderで再現する為にもRelationコンストレインを利用しています。

Relationの一例。十数個のコンストレインが一つにまとめられます。

もう一例。もっとも単純なコネクションの一つです。手首ジョイントのrotateの値をXYZに分解し、X軸を割り算してTwist回転の補助ジョイントに適用します。

高速であることのメリット

繰り返しになりますが、MotionBuilderはMayaと比べて再生パフォーマンスに優れています。これはちょっと矛盾した言い方になりますが、MotionBuilderは”重くても軽い”です。たとえば、20万超のFaceを持つキャラクターで、Maya上では4~5fpsにしかならないキャラクターでも、MotionBuilderではリアルタイムで再生することが可能です。近景の群衆アニメーションで、十数体のキャラクターが必要なケースでも、極端なリダクションをしなくても耐えられるパフォーマンスを発揮できます。軽くを求めればより高速に、クオリティを求めれば最終ルックに近いモデルでアニメーションを行う事が出来るという事がMotionBuilderの強みといえるのではないでしょうか。


2015/01/28更新

ご質問が寄せられたので回答させていただきます。

>質問は、local Rotation axes についてになります。
>記事によると、spine、arm、legによってprimary axisが違うようなのですが、
>これらは、統一しなくても問題は起こらないのでしょうか。

これは、ControllRig(HumanIK)の設定に関係しています。

作成されたControllRigのFKEffectorのScale軸は適用するジョイントのAxisがどうであれWorld座標と同じものになります。
※Rotate軸は+X軸をPrimaryAxisとしたものになります。
ジョイントとControllRigのAxisが一致しない場合、特にScale値のControllRig->ジョイント間の転送に不具合が起こるのでこういう形をとっています。

また、特に足首jointに関しては、子jointをAimしたAxisよりも、接地面と平行な軸でのScaleの方が使用する機会が多いだろうという事もあります。(踏み込んだ時の足の沈み込みなど)

>また、worldを基準に設定されているように見えるのですが、
>それは、後に接続するコントローラーの為なのでしょうか

Rotateマニピュレータで編集する場合、ChannelBoxで数値調整する場合、あるいは複数のコントローラを一括で編集する場合、になるべく感覚のズレが無いようにと思いこの形をとっています。勿論円錐状にモデリングされたマントであったり、龍の首や蛇など、ジョイントの進行方向を軸にしたいが直線状にモデリングされていない場合、など例外は存在します。必ずしもworld座標基準で、という厳格なルールは敷いていません。



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



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

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


≪セットアップ室≫nClothを使ったクロスシミュレーションセットアップ(初心者向け)

$
0
0

はーい、こんにちは。
部長に騙されてDF TALKを書くハメになりました。セットアップ室の櫻井です。

今回はnClothを使った簡単なクロスシミュレーションセットアップの紹介を淡々とさせて頂きます。(チーム内のやり方とは、省いていたり少し違ったりしています)

DFでは殆どのプロジェクトでクロス(ヘアー)シミュレーションします。
それをセットアップチームがセットアップし、チーム内でシミュレーション作業をします。

今回使う画像のmayaのバージョンは2015です。日本語になっていますが、社内では英語です。

ではまず、スキニングが完了したモデルを用意します。(間に合わせのモデルですみませんっ)

とりあえず名前がないとやり辛いので、ベースモデルと言うことで服をcoat_base、体をbody_baseと仮で命名しておきます。

coat_baseを二つ複製し、ひとつをシミュレーション用のメッシュ(以下coat_simobj)。もうひとつをそのまま最終確認用にします(以下HIcoat_simobj)。
シミュレーションするメッシュは、求めているクオリティの具合を見て削減しましょう。ハイレゾのままだと重すぎる場合があるからです。やる気がなくなります。
削減の仕方としては、リトポしたり、装飾品(ポケット等)をバッサリ無くしたりします。
厚みのあるメッシュを1枚にしたい場合は、衝突する面に合わせます。
例えるなら、服の袖に厚みがあった場合、内側に合わせます。外側に合わせてしまうと、内側は衝突するメッシュが無いので体にめり込んでしまいます。

出来上がったシミュレーション用のオブジェクトを1つ複製しcoat_simobj_baseと仮で命名しておきます。(後で記述するペイントマップで使います)
coat_simobjを選択した状態でnDynamicsのnMesh→nClothの作成(Create nCloth)をクリック。

nCloth オブジェクトに変換され、nucleus1とnCloth1が出来ます。
これでシミュレーションができる状態になります。

しかし、そのままでは衝突するものが無いので、coat_simobjは永遠と落下していってしまいます。
そうならないように、nClothオブジェクトと衝突させるオブジェクトを作ります。
これをコリジョン(衝突)オブジェクトといいます。そのままですね。

body_baseを複製して、必要の無いメッシュを削減します。(シミュレーションするメッシュが衝突しない所等)

画像のは雑ですが、コリジョンオブジェクトはクロスのシルエットにもろに影響するので、適当に作ると痛い目に遭います。
シミュレーションをした際のシルエットを意識しながら作りましょう。
シミュレーションオブジェクトと始めから干渉していたりすると、そこからめり込んだり破綻するのである程度余裕を持たせましょう。

作ったコリジョン用のメッシュを選択した状態でnDynamicsのnMesh→パッシブコライダの作成(Create Passive Collider)をクリック

コリジョンオブジェクトに変換され、nRigid1が出来ます。
変換されたオブジェクトには、元のbody_baseと同じ動きになるようにしてあげます。(同じスキニングやwrap等)

一旦再生をして見ましょう。

はだけてしまいました。
首元から肩、胸周りをマップを使って大きく動かないように制御してみましょう。

前に作ったcoat_simobj_baseをcoat_baseとwrapして、同じ動きをするようにします。
sim_obj_baseとnclothノードを選択し、キーボードの↓を押します。
ハイパーグラフを起動し、sim_obj_baseShapeのworldMeshからnClothShapeのinputMeshへ接続します。

アトリビュートエディターのダイナミックプロパティから、入力メッシュ引き付け(input Mesh Attract)を1にします。

ダイナミックプロパティマップの中の入力メッシュ引き付けマップタイプを頂点単位(Per-vertex)にします。

coat_simobjを右クリック→ペイント(Paint)→nCloth 頂点マップ(nCloth Vertex Maps)→入力引き付け(Input Attract)をクリック

ペイントツールが起動しますので、あまり揺れて欲しくない部分を塗ります。

はだけなくなりました。

マント等のシンプルな形状のものには、マップタイプをテクスチャにし、ランプマップを使うと楽に制御できたりします。

次に、キャラクターが動いたときに破綻しないかアニメーションさせてみます。
脇の部分がコリジョンに挟まれ破綻してしまいました。(不思議な破綻の仕方だなぁ・・・)

nucleusのアトリビュートのサブステップ(subSteps)を上げたり、コリジョンの精度を上げたり、厚みを下げたりしてみましょう。
Autodeskのmayaのチュートリアルを参考に出来ます。(というかここ見れば大体出来るんじゃ・・・)Lesson 3: 高解像度メッシュをラップしてアニメーション時にズボンをシミュレートする

精度を上げてもダメなら、デフォーマーやターゲットでコリジョンオブジェクトに隙間を作りましょう。

(極端な例ですが)
今回はスカルプトデフォーマー(Sculpt Deformer)を使用しています。Animationのデフォーマーの作成(Create Defoemers)→スカルプトデフォーマー(Sculpt Deformer)で作ります。
脇の下以外は変形させたくないので、スカルプトデフォーマーを選択した状態でデフォーマの編集(Edit Deformers) →メンバーシップの編集ツール(Edit Membership Tool)を起動し、脇以外を選択から外します。

破綻はなくなりました。

HIcoat_simobjとwrapして、実際にレンダリングするモデルでめり込みや破綻、肩や肘のシルエットが著しく変わっていないか確認します。

以上、簡単なクロスシミュレーションセットアップでした。

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

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

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

***

他の方でCG以外の話を書いていたので私もすこし。

まぁ何かといいますと、最近ミニ四駆にどっぷりはまっています。
ノー残業デーの時にはたまに一人で新橋へ寄って走らせています。(↓新橋にあるコース)

なんでもまたブームが来ているようで、私が参加した公式大会では3000名以上もエントリーがあったようです。
興味のある方はこの波に乗りましょう。

↓ちなみに私のミニ四駆です。サスペンションが付いてます。

改造の話とか書きたかったんですが、やっぱりそこは自重します・・・。

ではさようなら。

NukeのPluginを作ってみる

$
0
0

皆さん、お久しぶりです開発部の山口です。

そろそろNukeのPluginでも作ってみたいなとか思い始めたので
今回はそれをネタに書き進めたいと思います。

Welcome to the NDK Developer Guide Nuke8.0

まず始めに

プラグイン開発といえばNukeのSDKが必用ですが、すでにNuke Development Kitということで
NDKという名前でインストールされていますので、それを使用します。
※今回の説明で使用するNukeのバージョンは”8.0v5″です。

NDKは以下の場所にありますので探してみてください。

・”Nukeインストールディレクトリ”\Nuke8.0v5\Documentation\NDK
※例: C:\Program Files\Nuke8.0v5\Documentation\NDK

この中のファイルを直接修正してもいいのですが、元のファイルはそのまま
残しておきたいので、NDKフォルダごと自分の開発したい場所にコピーします。

準備ができましたらNDKフォルダにあるvc10フォルダ内の
ソリューションファイルをVisual Studioで開きます。

 ・NDK\vc10\ExamplePlugin.sln

Visual Studioのバージョンは同フォルダのReadme.txtを読むと2010を使用すると
書いてありますのでVisualStudio 2010を使用します。
フォルダ名がvc10となっているので、そこからも分かりますね。

ソリューションファイルを開くと、下記のような構成になっているかと思います。

ビルドの設定

次にビルドの設定を行います。フォルダを移動させた場合はincludeパス,
libパスの2つを通しなおす必用があります。
※移動させなかった場合は、全て相対パスで記述されているので設定する必要は無いです。

■インクルードパス

■ライブラリパス

その他注意事項として、ビルドで作成されたDLLを自動的に“.nuke”フォルダに
コピーするコマンドが仕込まれています。

このコマンドが設定されていると、ビルドしてすぐにデバックできるので便利ですが
リリース時に邪魔になる場合もあるので気をつけてください。

ビルドして、実行してみる

パスの設定が終了しましたら、Releaseでビルドしてみます。
※Debugではなぜかクラッシュするのでここでは使用しません。

無事終了すれば、ビルド後にコピーのコマンドが実行され、“.nuke”フォルダにExamplePlugin.dllという
ファイルが作成されていると思います。

“.nuke”にはすでにPluginPathが通っているので、この場所に配置されたPythonやDLL,アイコンは自動的に
Nukeで認識するようになります。

試しに以下のどれかの操作を行ってみてください。
※PLEでは動作しません

■ 下記のコマンドをNode Graphにペーストする
※文字列と{}の間のスペースがないとエラーになります。{}は無くても良いです。

[python]
ExamplePlugin {}
[/python]

■ 下記のコマンドをScriptEditorで実行する

[python]
nuke.createNode(‘ExamplePlugin’)
[/python]

■ Windowで実行する

NodeGraphの場所でショートカットの”x”を押します。
するとTCL/Pythonを実行するウインドウが出てきます。

そこに上で紹介したコマンドを入力し、実行します。
※下記はTCLで実行した場合の例です。

実行結果

以上が成功すると下図のようにノードが作成されると思います。

“.nuke”について

NukeではユーザーのPriferenceなどの設定情報を格納しておく
.nukeフォルダというものがあります。

1度でもNukeを起動していれば、環境変数”USERPROFILE”(もしくは%HOME%)で設定されている場所に
作成されていると思います。エクスプローラーに下記のとおり文字列を入力してみてください
※隠しフォルダになってる場合がありますので、直接パスを指定しないといけないかもしれません。

プラグインのパッケージ化

実際にプラグインをリリースする場合には、このまま”.nuke”フォルダに配置するのも
ファイル類が煩雑になりすぎるので、ひとつのフォルダにまとめリリースしたいです。

まずDLLファイルを置く場所を決めます。試しにCドライブの直下にNukePluginsという
フォルダを作成します。

そこにビルドしたDLLを配置します。

次にこのフォルダを.nukeフォルダと同じように、プラグインを認識するフォルダになるように
Nukeに登録します。

登録方法は2つ有ります。

1. 環境変数でパスを登録

NukeにはNUKE_PATHという環境変数が有りますので、そこに追加していきます。
起動バッチに以下の要領で追加していきます。

[cpp]
SET NUKE_PATH=C:\NukePlugins
[/cpp]

2.initファイルに登録

“.nuke”フォルダにinit.pyファイルを作成すると、Nuke起動時にそのファイルが
実行されるようになります。これはGUIが作成する前に実行されるファイルです。

ここにpythonでプラグインパスを通します。

[python]
# -*- coding: utf-8 -*-
import nuke
nuke.pluginAddPath(‘C:/NukePlugins’)
[/python]

これでC:\NukePluginsフォルダが認識されるようになりました。

次にコマンドでノードを作成するのも面倒なのでメニュー登録を行います。

C:\NukePluginsフォルダ内にmenu.pyというファイルを作成し、ここに
メニューへ登録するコマンドを記述していきます。

[python]
# -*- coding: utf-8 -*-
import nuke
toolbar = nuke.menu(‘Nodes’)
toolbar.addCommand(‘CustomTools/ExamplePlugin’, ‘nuke.createNode(“ExamplePlugin”)’)
[/python]

これでメニューに登録されました。

サンプルプラグインのビルド

これだけでは何もできないので、exampleフォルダに入っている物を何か
ビルドしてみます。

 ・Nuke8.0v5\Documentation\NDK\examples

例としてGradeノードをビルドしてみます。

まず以下の手順を行います。

 1. プロジェクトの名前をExamplePlugin -> Gradeに変更
 2. プロジェクトの中にGrade.cppを登録
 3. ExamplePlugin.cppを削除

これを全て行うと下図のようになります。

※ExamplePlugin.cppを残しても良いのですが、今回は除きます。

このままビルドし、実行してみると、無事Grade.dllが作成されていると思います。

ただこのままだと既存のGradeとかぶってしまうので成功しているかどうか分かりません。
試しにhelpのコマンドを変更してビルドしてみます。

[cpp]
// Grade.C
// Copyright (c) 2009 The Foundry Visionmongers Ltd. All Rights Reserved.

const char* const HELP =

Applies a linear ramp followed by a gamma function to each color channel.


A = multiply * (gain-lift)/(whitepoint-blackpoint)

” B = offset + lift – A*blackpoint

” output = pow(A*input + B, 1/gamma)


“The reverse option is also provided so that you can copy-paste this node to ”
“invert the grade. This will do the opposite gamma correction followed by the ”
“opposite linear ramp.”;
[/cpp]

[cpp]
// Grade.C
// Copyright (c) 2009 The Foundry Visionmongers Ltd. All Rights Reserved.

const char* const HELP =”hogehoge”;
[/cpp]

変更してビルドし、ノードのヘルプを確認してみます。

■通常のグレードノード

■Helpを変更したノード

ヘルプのメッセージが変更されていれば成功です。

ノードの名前の変更

このままGradeノードのプログラムを修正しビルドしても、ノードの名前を変更しないと
既存のものとかぶってしまい、どちらかが使用できなくなるのでノードの名前を変更したいと思います。

※ノードの名前が同じ場合、PluginPathで先に見つかったものが実行されます。
PluginPathは下記のコマンドで確認してください。

[python]
import nuke
print nuke.pluginPath()
[/python]

NukeにはDLLの名前と”CLASS”変数に登録した名前が一致している場合に
Nuke内でノードとして自動で認識されるという仕様があります。
※他にもやり方はありますが、ややこしいので今回は省きます。

まず変数を修正します。Grade.cppの下記の変数の名前を変更します。

[cpp]
static const char* const CLASS = “Grade”;
[/cpp]

[cpp]
static const char* const CLASS = “CustomGrade”;
[/cpp]

次にDLLの名前を変更します。プロジェクの設定でプロジェクト名が
DLLの名前になるように設定されているので、プロジェクト名を修正します。

以上で完了です。
これでビルドすれば、CustomGradeという名前のノードが作成できると思います。


最後に

今回はビルドして配置、簡単なNukeの仕組み等をお話しました。
次回は何か作成してみたいと思います。

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


空間フィルタリングを別の表色系で!

$
0
0

こんにちは、開発部の高山です。

今回は画像処理をRGB以外の表色系で行うとどうなるかという話です。
画像処理の例として空間フィルタリングを扱っていこうと思います。


空間フィルタリングとは

空間フィルタリングは画像処理でよく用いられる処理の1つで、
入力画像のピクセルごとに自身とその周囲のピクセルを使って計算することで様々な効果を与えるものです。
例えばぼかしやノイズ除去、エッジ抽出など、様々な用途に使うことができます。

では、実際に空間フィルタリングの計算方法を見ていってみましょう。
例えば、あるピクセルとのその周囲のピクセル値(例えば、RGBのR)が以下の画像のようだったとします。

これにフィルタ(下画像)の対応する部分の数値を掛けていきます。

画像の例だと順番に 10×0.1, 20×0.2, 30×0.3, 40×0.4, 50×1, 60x(-0.4), 70x(-0.3), 80x(-0.2), 90x(-0.1) といった感じですね。
そしてこれらを足し合わせたものが出力結果となります。
この例ですと、(10×0.1) + (20×0.2) + (30×0.3) + (40×0.4) + (50×1) + (60x(-0.4)) + (70x(-0.3)) + (80x(-0.2)) + (90x(-0.1)) = 10 が結果となります。

これを全てのピクセル、RGBそれぞれに行っていくことで、空間フィルタリングを行った結果画像が得られます。

計算だけ見ても分かりにくいと思うので、実際にどういう効果を与えることができるのか見ていきましょう。


1.平滑化フィルタ(ぼかし効果)

最初の例ではフィルタに適当な値を入れて説明しましたが、これに特定の値を入れることで様々な効果を与えられます。

例えば以下のようなものに代表される平滑化フィルタを用いて計算を行うと、ぼかし効果のようなものが得られます。

結果はこんな感じに。上の画像が全体画像(クリックで拡大)で下の画像が一部を抽出したものになります。

入力画像 結果画像

範囲を広げたり、繰り返しフィルタをかけることで、より様々なぼかしを表現できます。

2.鮮鋭化フィルタ(シャープ化)

逆にシャープ化させるためには以下のような鮮鋭化フィルタを用います。

これを適用した例はこんな感じに。

入力画像 結果画像

計算結果が範囲外になる場合があるので注意しましょう。

3.ソーベルフィルタ(エッジ抽出)

次は以下のようなフィルタ。これはソーベルフィルタと呼ばれるものの1つで、エッジ抽出を行う場合によく使われます。

結果はこんな感じ。

入力画像 結果画像

表色系とは

このような画像処理を行うときに、大半のプログラムやアルゴリズムではRGB表色系が用いられています。
RGB表色系はもちろんすぐれた表色系なのですが、輝度が分かりにくかったり、色差が等間隔でなかったりと欠点もあります。
そういうこともあり、他にも様々な表色系が提案されています。
今回はそれらの表色系について軽く触れ、それらを使って画像処理を行ったときにどういった違いが生じるかを検証してみました。

HSV表色系

HSV表色系はHSB表色系やHSI表色系とも呼ばれ、H(色相)・S(彩度)・V(明度)とそれぞれの成分の役割がはっきり分かれている所が特徴です。
HSVという名前にはピンとこなくても以下の画像のような色相環は見たことがある人は多いのではないでしょうか。

RGBよりも直観的に分かりやすいため、大抵のソフトでサポートされています。

Lab表色系

正確にはCIE-L*a*b*表色系などと呼ばれ、知覚的に均等である(人間が感じる色の違いの尺度が、距離として正しく計算される)ことを重視した表色系です。
Lが明度を表し、aとbで色を表現しています。

人間の知覚に沿って作られた表色系なので、成分ごとに範囲が違うなど分かりにくい部分はありますが、
色の距離を用いる場合はRGBよりもLabの方がより正しい結果が求められます。


結果

RGB表色系をHSV表色系、Lab表色系に変換したうえで空間フィルタリングするとどういう違いが出るかを実験してみました。
ただし、HSV表色系に関してはHを使うと色味がおかしくなってしまうため、HはそのままでSVのみ空間フィルタリングを行っています。

1.平滑化フィルタ(ぼかし効果)

RGB表色系 HSV表色系 Lab表色系

2.鮮鋭化フィルタ(シャープ化)

RGB表色系 HSV表色系 Lab表色系

3.ソーベルフィルタ(エッジ抽出)

RGB表色系 HSV表色系 Lab表色系

平滑化フィルタは弱めのぼかしということもあってあまり違いは見られませんが、他の2つはそれなりに違いが出ています。
どの表色系がよりよい結果なのかは求めたいものによって変わってくるでしょう。

というか検証して分かったのですがRGBとLabの結果変わってないですね..._| ̄|○
より複雑なフィルタリングを行う場合は違いが出てくるはずですが、まあこれも1つの結果かと。


まとめ

今回主張したかったことを箇条書きにすると
・空間フィルタリングは1つのコードで色々な効果が得られてお得
・画像処理を実装するときにRGB以外の表色系も試してみては
・主張をするときは結果がともなわないと説得力に欠ける

こんなところで。では、また~。


おまけ

今回の画像生成に使ったコードの空間フィルタリング部分を載せておきます。
表色系の変換式は申し訳ありませんが長くなりすぎるので割愛。

[cpp]

const int SIZE = 1;
float WEIGHT[3][3][3] = {{{1.0f / 9.0f, 1.0f / 9.0f, 1.0f / 9.0f}, {1.0f / 9.0f, 1.0f / 9.0f, 1.0f / 9.0f}, {1.0f / 9.0f, 1.0f / 9.0f, 1.0f / 9.0f}},
{{-1.0f, -1.0f, -1.0f}, {-1.0f, 9.0f, -1.0f}, {-1.0f, -1.0f, -1.0f}},
{{-1.0f, 0.0f, 1.0f}, {-2.0f, 0.0f, 2.0f}, {-1.0f, 0.0f, 1.0f}}};

float clamp(float x, float min, float max){
if(x < min) x = min;
if(x > max) x = max;

return x;
}

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;
}

Color Pixel_Filter_RGB(Color *data, int x, int y, int width, int height, int method){
int MAX = 255;

int mx, my;
float w;
float r, g, b;

r = 0.0f;
g = 0.0f;
b = 0.0f;
for(int dy=-SIZE;dy<=SIZE;dy++){
for(int dx=-SIZE;dx<=SIZE;dx++){
mx = mirror(x + dx, 0, width);
my = mirror(y + dy, 0, height);

w = WEIGHT[method][dx + SIZE][dy + SIZE];

r += (float)data[mx + my * width].R / MAX * w;
g += (float)data[mx + my * width].G / MAX * w;
b += (float)data[mx + my * width].B / MAX * w;
}
}

r = clamp(r, 0.0f, 1.0f) * MAX;
g = clamp(g, 0.0f, 1.0f) * MAX;
b = clamp(b, 0.0f, 1.0f) * MAX;

return Color::FromArgb((int)r, (int)g, (int)b);
}

[/cpp]


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

≪セットアップ室≫Maya Utilityノードのセットアップでの使い方 第1回

$
0
0

初めまして。SU室、期待の新人の栁澤[@Null0218]です。笑
早いもので、昨年4月に入社してからもうすぐ1年が経ちます。

私事ですが先日、個人的に作ったツールを3D人さんに取り上げていただきました。
とても嬉しいですし、ありがたい事なのですが『どーてぃ』と検索すると、
上位に私の名前もヒットするようになってしまいました。
皆さんもツール名にはくれぐれもお気をつけください。
 
 
 
さて、今回はmayaでセットアップをする際に役立つUtilityノードの使い方についてご説明させて頂きたいと思っています。
ただノードの数も多いので、まず第1回目はどういったノードがあるのか簡単にご紹介をしたいと思います。

そもそもUtilityノードとは

UtilityノードというのはHypershadeのcreateタブにあるUtilitiesの中のノードのことです。
おもに数値の演算などを行うことができます。

これらのノードの組み合わせで計算を行うことにより(場合によりますが)expressionなどより速い評価が望めます。
それではセットアップでよく使いそうなノード一つ一つ簡単にご紹介したいと思います。
 
中にはmatrixの計算をするノードも存在するのでこちらの記事も合わせてご覧ください。
わかった気になるマトリックス
わかった気になるマトリックス 第2回
わかった気になるマトリックス 第3回
わかった気になるマトリックス 第4回
 

カテゴリ

単一計算系
3軸計算系
matrix系
計測系
制限系

 
 

単一計算系

単一計算系は一つの値と一つの値を計算し出力するノードです。
 

addDoubleLinear


[python]
cmds.shadingNode(‘addDoubleLinear’, au=True)
[/python]
input1とinput2の和をoutputするノードです。
 

multDoubleLinear


[python]
cmds.shadingNode(‘multDoubleLinear’, au=True)
[/python]
input1とinput2の積をoutputするノードです。
 


3軸計算系

3軸計算系は三つの値と三つの値を計算し出力するノードです。
 

blendColors


[python]
cmds.shadingNode(‘blendColors’, au=True)
[/python]
color1とcolor2の割合をblenderの値で割合を決め出力します。
本来はtextureなどをブレンドしたい時に使いますが、セットアップの際はAの値とBの値を行き来したい時に使用します。
例)FK用jointのrotate値とIK用jointのrotate値をそれぞれcolor1とcolor2にコネクションし、
blenderの数値でFKとIKのrotate値を切り替えます。
 

multiplyDivide


[python]
cmds.shadingNode(‘multiplyDivide’, au=True)
[/python]
operationにより計算方法が異なります。
No Operation・・・input1の値をそのまま出力します。
Multiply・・・input1の値とinput2の値の積を出力します。
Divide・・・input1の値からinput2の値の商を出力します。
Multiply・・・input1の値をinput2の値でそれぞれ累乗した結果を出力します。
 

plusMinusAverage


[python]
cmds.shadingNode(‘plusMinusAverage’, au=True)
[/python]
このノードは三軸計算系に分けましたがinput1D~input3Dまでinputの数を選択できます。
さらに、このノードのinputは配列型なので複数の値を入力できます。
operationにより計算方法が異なります。
No Operation・・・inputの最初の値をそのまま出力します。
Sum・・・inputに繋がれた全ての和を出力します。
Subtract・・・inputに繋がれた順番に全ての値の差を出力します。
Average・・・inputに繋がれた全ての値の平均値を出力します。
 

reverse


[python]
cmds.shadingNode(‘reverse’, au=True)
[/python]
1からinput1の値をそれぞれ減算して出力します。
なぜreverse(反転)なのかというとinputが1の時に0をoutputし、inputが0の時に1をoutputするためです。
visibilityなどの2値で扱われるものに使用します。
 

vectorProduct


[python]
cmds.shadingNode(‘vectorProduct’, au=True)
[/python]
Vector Matrix Prduct、Point Matrix Prduct用にmatrixを入力することもできます。
operationにより計算方法が異なります。
No Operation・・・inputの最初の値をそのまま出力します。
Dot Product・・・input1の値をinput2の値で内積して出力します。
Cross Prduct・・・input1の値をinput2の値で外積して出力します。
Vector Matrix Prduct・・・input1の値とmatrixの積を求めベクトル座標を出力します。
Point Matrix Prduct・・・input1の値とmatrixの積を求め点座標を出力します。
Normalize Outputにチェックを入れると正規化された値を出力します。
※Vector Matrix PrductとPoint Matrix Prductの違い
Vector Matrix Prductは方向ベクトルが出力されるのでmatrixのtanslate部分は加味されません。
Point Matrix Prductはmatrix上の点座標なのでtanslate部分も加味されます。
この手の計算はexpressionで書くよりノードで計算した方が後々見やすいと思います。
 


matrix系

matrix系は4×4の行列計算ができるノードです。
matrixを使うと擬似コンストレインや、ベクトルの向きから回転値を導き出すこともできます。
ここではどのようなmatrixの演算がノードで出来るのかだけを簡単に説明します。
※ノードによってはmatrixNodes.mllというプラグインをロードしないと使えないのでご注意ください
 

addMatrix


[python]
cmds.shadingNode(‘addMatrix’, au=True)
[/python]
このノードにはmatrixInという配列型のinputがあり、複数のmatrixを入力できます。
matrixInをに接続された行列全ての和を出力します。
 

wtAddMatrix


[python]
cmds.shadingNode(‘wtAddMatrix’, au=True)
[/python]
addMatrixのmatrixInにweightがつけられるノードです。
matrixInにweightInの値を乗算して繋がれた配列全ての和を出力します。
 

multMatrix


[python]
cmds.shadingNode(‘multMatrix’, au=True)
[/python]
このノードのmatrixInも配列型なので複数の値を入力できます。
matrixInをに接続された行列全ての積を出力します。
 

composeMatrix


[python]
cmds.shadingNode(‘composeMatrix’, au=True)
[/python]
taranslate、rotate、scale、shear等のアトリビュートをmatrixに変換し出力します。
 

decomposeMatrix


[python]
cmds.shadingNode(‘decomposeMatrix’, au=True)
[/python]
composeMatrixの逆の計算を行います。
matrixをtaranslate、rotate、scale、shear等のアトリビュートに変換し出力します。
 

fourByFourMatrix


[python]
cmds.shadingNode(‘fourByFourMatrix’, au=True)
[/python]
16個の数値を入力することができ、それらを4×4のmatrixとして出力します。
 

inverseMatrix


[python]
cmds.shadingNode(‘inverseMatrix’, au=True)
[/python]
入力されたmatrixの逆行列を出力します。
 


計測系

計測系はノード間の角度や距離を求められるノードです。
 

angleBetween


[python]
cmds.shadingNode(‘angleBetween’, au=True)
[/python]
vector1とvector2のなす角を求め出力します。
rotateでドリブンキーを設定する場合こういったノードを組み合わせることでフリップしにくくなります。
 

distanceBetween


[python]
cmds.shadingNode(‘distanceBetween’, au=True)
[/python]
point1とpoint2の距離を求め出力します。
点座標(point)はmatrix(inMatrix)でも代用可能です。
strech表現のrig等で使えます。


制限系

制限系はinputの値の範囲や条件を決めることができます。
 

clamp


[python]
cmds.shadingNode(‘clamp’, au=True)
[/python]
inputの値の最少値~最大値を決めて出力することができます。
最少値  < input < 最大値 ならばinputを出力
input < 最少値  ならばminを出力
最大値 < input ならばmaxを出力
 

condition


[python]
cmds.shadingNode(‘condition’, au=True)
[/python]
expressionでいうところのif文です。
First TermとSecond TermがOperationの条件と合っていればColor If Trueを出力しそうでなければColor If Falseを出力します。
 
 
セットアップで使うUtilityノードはこれくらいでしょうか。
もちろん、この他にもノードはたくさんあって組み合わせ次第では面白いことがたくさんできます。
デフォルトの機能ではできないことも組み合わせ次第では実現できるかもしれません。
なんかすごくワクワクしますよね!笑
 
せっかくここまで説明したのでこんな感じでも使えますという簡単な一例を…
前の記事でご紹介した通りセットアップ室ではclothシュミレーションも行っております。
nClothを使う際、nucleusのWind Directionってベクトルの数値入力になっていて設定しにくいですよね。
このような分かりにくいベクトルも、こんな感じでUtilityノードを使えば使いやすくなるかもしれません。

vectorProductのoperationはVector Matrix PrductでNormalize Outputにチェックを入れ、direction_rootの階層下にdirection_tipを配置させればdirection_rootのrotateでWind Directionを編集することができます。

(クリックすると再生されます。)
※動画では見やすくするためにannotationを使っています
…と、このような感じで使い道は様々です!
 
次回は今回の内容を踏まえて、ノードネットワークの組み立て方の例をあげてご説明したいと思っております。
皆様も是非Utilityノードを使ってみてください!
 
 
 
※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

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

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

≪セットアップ室≫安定した旗のClothSimulationを試みる。(前編)

$
0
0

こんにちは、セットアップ室の森田です。
今回私が担当させていただくDF_TALKでは前編後編と分けて、安定した旗のClothSimulation方法を試みようと思っております。(Mayaのバージョンは2014を使用しています。)
前編では旗を揺らすまでのアトリビュート調整、後編では旗の揺れを安定させる方法の予定でおります。

という事で、今回はこんな旗を用意しました。(簡素なモデルです!)

では、この旗をnCloth化し、画像の旗の根元にあたる部分(vtが選択されてる部分)をInputAtractをvalue1の値で止めます。(根元が動くようでしたら1以上の値を入れてみてください)

nCloth化からInputAtractまでの過程は、
≪セットアップ室≫nClothを使ったクロスシミュレーションセットアップ(初心者向け) をご参照ください。
nucleus, nClothの説明はMayaHelpをご参照ください。

では早速nCLothShapeのアトリビュートを調整していきたいのですが、このままだと無風で調整しづらいので風を吹かせながら調整していきます。
nucleusノードに風を司るアトリビュートがありますので、これらを調整し風を吹かせます。

・WindSpeed ・・・ 風力、値上げると風が強く吹く
・WindDirection(X, Y, Z) ・・・ World方向での風の向き、+X軸方向に風を吹かせたい場合は+値を、-X軸方向は-値をいれます。
WindDirectionの編集方を≪セットアップ室≫Maya Utilityノードのセットアップでの使い方 第1回で記載しております、よろしければご参照ください。

このアトリビュートで風の強さと方向を決めます。今回の旗は+X方向が下手になるので、WindSpeed値を上げ、WindDirecionX 1にして上手から下手に流れる風を吹かせます。
この段階でWindSpeed値を高くし強く風を吹かせてしまうと、旗がダイナミックにはためいてしまい安定させるまでの調整が大変なので、そこそこの風力で調整を行っていきます。
って事で今回は、こんな感じになりました。

あまりなびいてませんが、この程度の風があれば調整できると思います、やってみましょう。
nClothノードのdrag値を上げてみましょう。

デフォルト値は0.05ですが、0.5位上げBlastしてみました。

旗もいい感じに持ち上がってきて揺れだしました。
あとはnucleus,nClothのアトリビュートをちくちく弄って揺れっぷり調整していきます。

今回主に調整するアトリビュートをご紹介します。

■nucleusShape
・gravityDirection(Y) ・・・ World方向での重力の向きを示しますが、値を下げて重力を抑えたりします。
・spaceScale ・・・ Gravityを下げたことにより、旗の揺らめきが水中にいるが如くゆったりとした動きになってきたので、空間スケールを下げてゆれっぷりを調整しました。

■nClothShape
・Stretch Resistance, Compression Resistance, Bend Resistance, Bend Angle Dropoff ・・・これらのアトリビュートでは、主に旗の質感付けの調整をおこないます。旗の固さ、柔らかさ、皺の出かた等の調整はこれらでおこないました。

・Lift, Drag ・・・ 旗が風を受ける影響力の調整を行います。値をあげると風の影響力が強まり激しくはためいてきます。

アトリビュートを調整する際、Simulationしながら調整していくと作業が捗ります。
nDynamics > nSolver > Interctive Playback
こちらで再生していただくとアトリビュート調整しやすいかと思います。

そして今回、私が調整した結果の動画とアトリビュートがこちらです。
参考になるか分かりませんが、よろしければどうぞっ!

nucleus

nCloth

安定した旗のClothSimulationを試みる(前編)のアトリビュート調整は以上になります。
次回後編では、旗の揺れを制御し安定したClothSimulationが出来るよう試みようと思います。


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


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

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


Reconstruct 3D environment~pointcloudのmesh化~

$
0
0

どーもお久しぶりです。開発部ゴトー[@jackybian]です。
寒~い冬もようやく終わりを迎え徐々に春が近づいてきてますね。同時に花粉が飛び始め、未だ花粉症を認めていない自分ですが
今年はさてどうなることやら。そんなこと言ってるうちに花見の時期になるんでしょうけどね(゚∀゚)ノワーイ

さて今回は検証で撮ってみた点群データ(以下、PCと表記)のMesh化についてのお話です。
と言っても今回は流体計算したparticleのMesh化とかではなくレーザースキャナーで撮影された環境のMesh化です。
これはLightingで使うためですが、この手のものでよく見るのは建物や室内などの人工物で出来た環境が多いように思います。
今回スキャンしたのは樹木が生い茂る森の中です。つまり自然物で囲まれた環境ということになります。
ご経験のある方がいらっしゃれば想像に難くないと思いますが、多種多様な植物が入り混じり複雑極まりない環境となります。 
このようなPCデータをどのようにしてMesh化したかについてのお話をしようと思います。

PCデータって?

一般的にレーザースキャナーは放射状にレーザーを照射して環境に当たった位置をPointデータとして格納します。
従ってレーザーが何かに当たった箇所のその先の情報までは取得できません。
例えば木の幹であればレーザーが当たった側の情報はあれど裏側の情報は無いということになります。
そのため複数の場所からスキャンしたデータを繋ぎ合わせ、これらを補う手法をとりますがこうして得られたPointCloudデータもそのままMesh化に使える訳ではなく、精度の低い箇所やノイズによる不要な点が含まれています。
また精度良くスキャン出来た箇所でもMesh化するに当たってはそこまでのディテールがいらない場合もあります。
例えば地面には多くの植物が生えてますが傾斜レベルのディテールで良く、植物のディテールはむしろ必要ない場合など。
このように用途に応じて不要な点を除去するCleanUp作業が必要になります。
今回は葉の部分を除去して、木(幹・枝)の部分のみをMesh化することが目的です。

CleanUpについて

どのようにして不要な点を除去したかですが、大きく3つの段階に分けてCleanUpしました。
1. 全スキャンPointのうち必要な空間の点のみを残す
2. ノイズPointや離散的なPointの除去
3. 木(幹,枝)と葉のPointを分離
このうち、処理1は大したことはないのですが、処理2は多少、処理3はかなり難航しました。
処理1を終えた段階で11,422,743頂点のPLYファイル(ASCII:928MB)でした。
過去に他部署のスタッフが建物などの人工物からなる背景をMesh化した事例があるとのことで聞いたみたところ
MeshLabやMephisto Processという複数のツールを使ってMesh化する方法でした。
自分も今回初めてPointCloudのMesh化をやったので最初はこの方法で不要なPointの除去から始めてみたものの
View上でのカメラ操作、不要なPointの選択⇒削除といった何度も繰り返し行う操作が非常にやり辛く、Undo機能の非力さもあり
とてもコストのかかる作業でした。この時点でかなりヤバい臭いがプンプンでしたが一旦この問題は置いておき、
CleanUpが出来たことを前提にPCデータのMesh化のテストに移りました。
が、こちらも最初のテストで嫌~な予感はみごと的中!
かなり計算時間がかかる上、結果が予測しづらく長時間かけてMesh化したはいいものの結果が全然ついてこない!
このやり方してたらいつになったらデータが上がるのか?ってくらいで正直震えました((;゚Д゚))ガクブル
(まぁ初めて使うソフトだというのもあり本当はもっと良いやり方があった可能性は否めませんが…)
CleanUpもMesh化も相当キツイってことがわかったのでCleanUpだけでも使い慣れたXSIやMayaで出来ないかと画策したところ
処理2はある程度はいけたんですが、処理3やMesh化処理についてはPoint数の多さもありやはり厳しい状態でした。
やはりデータを分割してパート毎にチクチクやっていくしかないかぁ。。。と途方に暮れていた時です。
神からお告げがっ!! “Houdini~VDB~♪”

HoudiniのVDBによるMesh化

そこで早速Houdiniを起動して11,422,743頂点のPLYファイルを読み込んでみたところ、サクサクで操作感もいい感じ~♪

という訳でVDBを使ったMesh化に挑戦!(といっても今回Houdiniほぼ初体験ですのでその点ご容赦ください)

まずは軽いPLYデータでまずはテスト。

1. fileノードでPLYデータを読み込み
2. Transformノードで位置や大きさを調整
3. VDB from Particle Fluidノードを作成してTransformノードをコネクト
4. ノードのパラメータを設定してVDB化
※Paticle Separation, Voxel Scale, Influence Scaleで解像度を調整
 

5. Convert VDBノードを作成。VDB from Particle Fluidノードとコネクト
6. ノードのパラメータを設定してPolygon化

多少パラメータやMemory使用にコツがあるもののMeshLabでやるよりはるかに処理も高速!
データハンドリングもしやすくいい感じなのでMesh化はこれに決定!

最大の難関!? 木と葉のPoint分離

あとは棚上げしていた”木と葉の分離”処理ですが、ある程度自動で分離が出来ないかとフィルター系を試したのですが、
●密度や距離によるフィルター ⇒ 分離には情報が不十分
●Vertex Colorによるフィルター ⇒ 天候の影響もあり色にあまり統一性がない
の理由から自動で分離するのは困難でした。

ということで今回は手作業での分離というなんともアナログな方法で対応せざるを得ませんでした。
ただでさえ複雑な森の中というシチュエーションの中、悪天候とタイトな撮影スケジュールで十分なスキャンができず
●人の目で見ても枝と葉の境界が判断しづらい箇所が盛り沢山!
●木の本数も半端ない!
で兎にも角にもこの作業が一番骨の折れる作業でした(´Д⊂ヽ
今後は木(枝)や葉の特徴を利用するようなアルゴリズムである程度自動化したいなぁ。。
良いアイデアをお持ちの方は是非ご一報頂けたら幸いです!

Mesh化の最終結果と考察

面倒な分離作業を終え最終的な木のMesh化&葉のPC化した結果がこちら↓

今回”樹木に囲まれた自然環境”のMesh化をやってみて
●可能な限り多くの地点からスキャンを行い、密度・精度の高いPCデータを得ること
●CleanUpツール、Mesh化ツールの選定には十分検討を!(HoudiniのVDB利用方式はかなり良かったと思っています)
●”木と葉の分離処理”には自動化などの改善が望まれる
といったことが重要だと思います。
どこまでをMesh化するか?にも因りますが建物や室内のような人工的な環境に比べるとやはり大変でした。
もし同じような環境をMesh化する方に少しでもお役に立てば幸いです。

最後に

実際にはLightingに使うデータにするにはこの後にもまだ作業があります。
・Polygon Reduction
・HDR Projection
・Import PC(※葉の部分も位置や形の参照用にPCのままMayaへ)
などがありますがお時間がきたので今回はここまでにします。またいずれ何処かで。では(´∀`*)ノ マタ~
Special Thanks to M & H! :D

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

PySideのUIをCSSでアレンジしてみる

$
0
0

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

今回紹介させて頂くのは、PySideのUIをCSSでアレンジしてみるというものです。

CSSという言葉を出してしまったのですが、
実際にPySide内で動くのは、 QT用にカスタマイズされたもので言ってしまえば、
CSSのシンプル版のようなものです。

今話題のCSS3のようなアニメーションやエフェクトまでサポートするような
多機能なものではありませんが、色やサイズ、形状の調整など、
基本的な部分はスタイルシートで変更出来るので、知っておくと便利かと思います。
 


Qt Designer で UI(.uiファイル)を作成する

PySideでコーディングすることでもUIを作成出来るのですが、
今回は、Qt Designerで簡単なものを作って、デザインの方はコーディングして
調整する方針でいきます。

Qt Designerの詳しい使用方法については takさんの記事 に載っていますのでそちらをご参照下さい。

今回は、QPushButton 3つ、QLineEdit 1つ のシンプルなUIを作成しました。

 

名前を付けて保存で、test.ui という名前でファイルを保存します。
dftalkフォルダを作成して、保存したuiファイルを中を入れれば準備完了です。
 


pythonスクリプト内でuiファイル読み込む

uiファイルを表示するためにpythonスクリプトを作成します。

下記のコードを書いて、test.pyという名前でファイルを作成し、dftalkフォルダに入れましょう。

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

## 必要なモジュールをインポート
import os
import sys

## PySide系モジュール
from PySide import QtCore, QtGui
from PySide.QtUiTools import QUiLoader

import functools as fnt

## uiファイル名
uiFile = ‘test.ui’

#=============================================================================
## GUIの構築
class GUI(QtGui.QMainWindow):

def __init__(self, parent=None):
super(GUI, self).__init__(parent)
loader = QUiLoader()

#UIファイルをロード
self.ui = loader.load(uiFile)

## centralWidgetとして設定
self.setCentralWidget(self.ui)

## ウインドウのタイトルを指定
self.setWindowTitle(u’DF TALK’)

## ウインドウのサイズを指定
self.resize(200, 100)

#=============================================================================
## GUIの起動
def main():
app = QtGui.QApplication(sys.argv)
wnd = GUI()
wnd.show()
sys.exit(app.exec_())

if __name__ == ‘__main__’:
main()

#—————————————————————————–
# EOF
#—————————————————————————–

[/python]
 


Pythonスクリプトを実行してUIを表示する

作成したtest.pyを実行します。
デバッグしやすいようにpython実行用batファイルを作成したいと思います。
下記のコードを書いて、test.batという名前でdftalkフォルダに保存しましょう。

[code]

\python.exe %~dp0test.py
pause
[/code]

batファイルを実行すると、UIが表示されるかと思います。

 

 


デザインを変更する

では、UIが表示出来るようになったところで、デザインの変更の方をしていきたいと思います。

UIオブジェクトに対するスタイルシートの適用方法は、

.setStyleSheet(” { <パラメータ名>: <設定する値>; } “)

setStyleSheetの引数に指定するのは文字列で、構文はcssの書き方と同じです。
※<設定する値>の後ろのセミコロンを忘れやすいので注意しましょう

 

◆ ボタンの背景色と文字色を変更する

ボタンの背景色と文字色を変えてみます。

下記のコードを、ウインドウのサイズを指定している部分の下に追加して下さい。

[python num=33 highlight_lines="36,37,38,39,40,41,42,43,44,45,46,47,48,49,50"]

## ウインドウのサイズを指定
self.resize(200, 100)

#———————————————————————–
## デザイン変更

## ボタンの親オブジェクト
ui = self.ui

# ボタンの背景色と文字色を変更する ——————————————

## 各ボタンの背景色と文字色を変更
ui.redButton.setStyleSheet(‘ QPushButton{ background-color: red; color: white ;}’)
ui.blueButton.setStyleSheet(‘ QPushButton{ background-color: blue; color: white ;}’)
ui.yellowButton.setStyleSheet(‘QPushButton{ background-color: yellow; color: black ;}’)

[/python]

 

ボタンに色が適用されたかと思います。

 

◆ ボタンの枠線のデザインを変更して、丸みをつける

3つのボタン全てに対して同じ変更を適用したい場合は、ボタンが属する親UIオブジェクトに対して、
スタイルシートを設定することで一括でボタンのデザインに変更を加えることも出来ます。

下記のコードを、ボタンの背景色を変更している部分の下に追加します。

今回でいうと、self.uiが親オブジェクトにあたります。

[python num=39 highlight_lines="42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73"]

## ボタンの親オブジェクト
ui = self.ui

# ボタンの背景色と文字色を変更する ——————————————

## 各ボタンの背景色と文字色を変更
ui.redButton.setStyleSheet(‘ QPushButton{ background-color: red; color: white ;}’)
ui.blueButton.setStyleSheet(‘ QPushButton{ background-color: blue; color: white ;}’)
ui.yellowButton.setStyleSheet(‘QPushButton{ background-color: yellow; color: black ;}’)

## ボタンの枠線のデザインを変更して、丸みをつける ———————————

## ボタンの高さ
h = 30

## 最終的に設定する変数
style = ”

## ボタンの高さ
height = ‘height:%spx;’ % (h)
## 枠線の色と太さ
# border = ‘border: 2px solid gray;’
border = ‘border-style:solid; border-width: 2px; border-color:gray;’

## 枠線の丸み
borderRadius = ‘border-radius: %spx;’ % (h/2)

## ボタンのスタイルを作成
buttonStyle = ‘QPushButton{%s %s %s}’ % (height, border, borderRadius)

## ボタンのスタイルを追加
style += buttonStyle

## 上記のパラメータを設定
ui.setStyleSheet(style)

[/python]

 

これだけでもだいぶ雰囲気が変わったかと思います。

 

◆ マウスカーソルがボタンの上にのったときにボタンを強調する

マウスカーソルがボタンの上にのったときにボタンが強調されるようにしてみたいと思います。

UIオブジェクトのクラス名の右に 「:hover」 を追加することで
マウスカーソルがのった時のデザインを設定することが出来ます。

擬似クラス というそうです。

擬似クラスについては、下記のサイトで分かりやすく書かれていたためこちらをご参照下さい。

CSS脱初心者への道!疑似クラスと疑似要素を理解すると表現が広がるよ! (リンクを貼らせて頂きます。)

では、下記のコードを ui.setStyleSheet(style) と書かれている行の上に追加します。

[python num=72 highlight_lines="72,73,74,75,76,77,78,79,80"]

# マウスカーソルがボタンの上にのったときにボタンを強調する —————————-

# 枠線を太くして、オレンジ色に
border = ‘border-width: 4px; border-color:orange;’
# :hover で ボタンの上にカーソルがのった時に適用される
buttonHoverStyle = ‘QPushButton:hover{%s}’ % (border)

## スタイルを追加
style += buttonHoverStyle

## 上記のパラメータを設定
ui.setStyleSheet(style)

[/python]

 

マウスカーソルがボタンの上にのった際にオレンジ色の枠線がつくようにしてみました。

 

◆ textField(QLineEdit)上に表示されている文字に応じてスタイルを変更する

ボタンを押した際に、押されたボタンのラベルをtextFieldに追加し、
追加された文字に応じて、textFieldの色を変更してみたいと思います。

では、下記のコードを ui.setStyleSheet(style) と書かれている行の下に追加します。

[python num=83 highlight_lines="86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107"]

## 上記のパラメータを設定
ui.setStyleSheet(style)

# textField(QLineEdit)上に表示されている文字に応じてスタイルを変更する ———–

## ボタンをクリックした際に呼ばれる関数
def clickEvent(button):

## 引数に指定したボタンからボタンのラベルを取得し、textFieldに設定
self.ui.textField.setText(button.text())

# textFieldに”Red”が入力されている場合
style = ‘QLineEdit[text="Red"]{background-color:red; color:white} ‘
# textFieldに”Blue”が入力されている場合
style += ‘QLineEdit[text="Blue"]{background-color:blue; color:white} ‘
# textFieldに”Yellow”が入力されている場合
style += ‘QLineEdit[text="Yellow"]{background-color:yellow; color:black} ‘
## styleSheetに適用
self.ui.textField.setStyleSheet(style)

## ボタンにクリックイベント(シグナル)を設定
## 引数にボタン自身を指定
ui.redButton.clicked.connect(fnt.partial(clickEvent, ui.redButton))
ui.blueButton.clicked.connect(fnt.partial(clickEvent, ui.blueButton))
ui.yellowButton.clicked.connect(fnt.partial(clickEvent, ui.yellowButton))

[/python]

 
clickEvent関数内で、ボタンラベルの取得とtextFieldへの追加。
文字列に応じて異なる背景が設定されるようにしてみました。

下部では、clickEvent関数をボタンに関連付けています。

 

押されたボタンに応じて、textFieldの背景色が変更されたのが
分かりますでしょうか。

 


あとがき

長々とお付き合いいただきありがとうございました。
PySideのUIデザイン変更部分に関しては、他にも色々あるようなので、
今回紹介させて頂いたのが正攻法かどうかは分かりませんが、styleSheetだけでもこんな感じにデザインを
変更することが出来るということを知っていただければ幸いです。
今後のUI開発などで是非活用してください。では、おつかれさまでした。

 

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

≪セットアップ室≫関節を曲げたときの形について考える 第1回

$
0
0

はじめまして。セットアップ室の小山です。

普段キャラクターのセットアップをしていると「肘曲げたとき丸すぎない?」とか
「腕下ろすと肩が丸くなるなぁ」といったような間接の形に関する指摘を受ける ことがあります。
セットアップはキャラを動すためのコントローラを作るだけでなく関節を曲げたときの変形についても特に気をつける必要があります。
より良い変形をさせる方法としてはスキニングするjointの位置を調整したり、
基本となるjointとは別のjointを作成して変形を補助するといった工夫が必要です。
最終的な画の見栄えにも影響してくる部分なのでなんとか頑張りたいですね。

本稿は関節を曲げたときの形について人体構造をさらっと解説し、よりよい変形を目指すことを目的としています。
ということで「形について考える」に留めてテクニカルな話は触れな い方向でいこうかと考えております。


さて、何の形について考えるかということですが
まずは冒頭でも出てきた「肘」を題材として進めていこうとかと思います。

早速Top Viewで進めていきます。
簡易的な腕モデルとjointを用意しました。
jointは大体中心を通るように配置しています。

スキニングをして曲げて見ましょう

なにやらチューブが曲がった様な形になってしまいました。
肘が丸くなり間接も細くなっているようです。
これを腕に見えるようにするには骨と筋肉が作る形を再現する必要があります。
仮の骨モデルを作成して構造を見てみようと思います。
下図では上腕の骨(赤)肘の骨(青)筋肉(黄)を簡易的に配置しています。

肘を見てみると骨を無視して凹んでいることがわかります。
実際に肘を曲げたときにはこの骨が出てくるため丸くならないようにする必要があります。
また、この骨によって腕の後ろ側の筋肉(黄)も引っ張られるため、その挙動を意識して肘を作るとよいです。
あくまで関節を軸に回っているので尖っているからといって下図のように不必要に飛び出し過ぎないようにしましょう。


続いて腕の内側の形です。肘の裏側の部分です。こちら側の形を作っているのは筋肉です。
下図では簡易的に2本の筋肉で表現していますが実際はもっと複雑です。
とりあえず筋肉が交差している部分が内側を持ち上げているような感覚です。

ここまでを踏まえて変形させてみます。


最初の状態と比較してみます。

凹んでいたところを盛っただけと言えばそれだけですが最初よりは肘っぽく見えるでしょうか?
このような構造は肘に限らずその他の関節でも共通して言えることだと思います。
関節は筋肉が骨を引っ張ることによって曲がるものなので、筋肉が骨のどの部分を結んでいるのかを知っていれば
形を作るときの手がかりになります。
とりあえず肘のような1軸で曲がる関節は、曲げる側の筋肉とそれを元に戻す側の筋肉の二種類に注目すればわかりやすいのではないでしょうか。
だいぶ簡略化したので足りていない部分もありますがご参考になればと思います。

次回があればもう少し肘について書く予定です。今回はTopViewだけになってしまいましたので
他のアングルからの形も見ていけたらと思います。

ではまた。


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


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

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

肘の骨(青)人体の

キャッシュサイズを50%に減少させる方法 [入門]

$
0
0

はじめまして、開発部のVladimirです。
今回のDFTalkでは、最近取り組んでいたプロジェクトの中から、簡単で役に立ちそうな事を書いてみたいと思います。

はじめに

今回のプロジェクトは、DF独自のキャッシュファイルをこねこねし、できるだけ効率的な書き出しと読み込みに対応させる、といった内容でした。既存ライブラリーより良い物を作る事は難しいと思うかもしれませんが、そのライブラリーを特殊に設計することで、まだ改善出来る余地があります。

ここで主にmayaのalembicキャッシュと比較してみました。 alembicはデータロスを避けるように設計されています。
ただzlib等の圧縮の場合、読み込みスピードに大きな影響が出てしまうことが分かりました。

そのため、ロッシー圧縮の良い手法を考えてみました。

floatから符号なしshortへの変換

例として、DDM で計算された破壊のシーンを見てみましょう。 シミュレーションはかなり重いので、各ポイントの3Dのワールド位置をキャッシュするメリットがあります。

キャッシュされたデータはポイントの3D座標のarray、つまり、浮動少数点数(フロ-ト)のarrayです。

基本的なデスクトップパソコンが用いているfloatはIEEEの基準に従っています。

詳細を省略させていただきますが、興味のある方はコチラを参照してください。

IEEE float number type

このfloatは32ビットを用います。最初のビットはマイナス・プラス数を分割しています。
次の8ビットは指数部で、かなり大きい数字と小さい数字を示すことができます。
最後の23ビットは細かい数字を示しています。

こういった仕組みで、精度と示されているレンジが高い為、現在のパソコンが用いるfloatの基準タイプになっていました。

当然、科学的な応用に対しては、精度が高すぎると言うことはありえません。
しかし、ビジュアルや映像系の応用に対しても、同じことが言えるのでしょうか。

人間の視覚が認識できるディテールは科学実験で計測されている物ほど細かくはありません。

floatが示す範囲は必要最低限を満たしていれば、それ以上は省略できるbitsがあるのではないかと思います。

まず、floatデータはhalf floatにキャストすれば適切なのか検証してみます。

IEEE half float type

そのタイプは半分のスペースでfloatに比べると半分の精度になります。
データにある数値は1単位であれば、half floatの精度は0.0005になります。
1000単位以上の数値は0.5の精度になります!

普通のシーンにあるポイント座標は1000を超えないと仮定しても、その精度で質の差は目立ちます。
なぜなら、必要のない 1000以上の数値と細かすぎる数値はhalf float
に示されているので、CGや個人のニーズに対して16ビットが効率的に使われていません。

では、簡単にその16ビットを使用上データロスの少ない圧縮方法を見ていきましょう。
以下の要領で行います。

●まず、シーンのバウンディングボックスを計算します。
[cpp]
//p = array of xyz world coordinate
void computeBoundingBox(int nVertices, float &p, MFloatPoint &min, MFloatPoint &max) {
min.x = p[0];
min.y = p[1];
min.z = p[2];
max = min;

for (int i = 1; i < nVertices; i++) {
if (p[3*i] < min.x) min.x = p[3*i];
else if (p[3*i] > max.x) max.x = p[3*i];
if (p[3*i+1] < min.y) min.y = p[3*i+1];
else if (p[3*i+1] > max.y) max.y = p[3*i+1];
if (p[3*i+2] < min.z) min.z = p[3*i+2];
else if (p[3*i+2] > max.z) max.z = p[3*i+2];
}
}
[/cpp]

●そして、座標のデータをバウンディングボックスのなかでノーマライズして、65535(shortのため、2の16乗-1)をかけて、結果をキャストします。

[cpp]
void compress(int nVertices, float &p, MFloatPoint &min, MFloatPoint &max, unsigned short &compressedP) {
float invDelta[3] = {1.0f/(max.x-min.x), 1.0f/(max.y-min.y), 1.0f/(max.z-min.z)};
#pragma omp parallel for
for (int i = 0; i< nVertices; i++) {
p[3*i] = (p[3*i] - min.x)*invDelta[0];
p[3*i+1] = (p[3*i+1] - min.y)*invDelta[1];
p[3*i+2] = (p[3*i+2] - min.z)*invDelta[2];

//すべてのポイントデータは[0,1]の範囲に示されているようになります。
compressedP[3*i] = (unsigned short)(p[3*i]*65535);
compressedP[3*i+1] = (unsigned short)(p[3*i+1]*65535);
compressedP[3*i+2] = (unsigned short)(p[3*i+2]*65535);
}
}
[/cpp]

つまり、「0,1」の範囲が65535の解像度に分裂されているので、視覚的に認識できない差で元々のデータを半分のスペースで格納できます。
しかも、符号なしshort ⇔ floatの変換は軽いです!

同様に、ノーマライズされたデータを255にかけると符号なしcharに変換できます。精度が非常に悪いのは当然ですが、比較するため一応その結果を準備しておきました。

Animation compressed using floats Float (4 bytes)
Animation compressed using unsigned short Unsigned short (2 bytes)
Animation compressed using unsigned char Unsigned char(1 byte)

三つの動画のうち上の二つを比較するとほとんど差がみられませんでした。また、一番下の動画は精度が悪くなっていることが分かります。つまり、今回の2バイトへ圧縮する手法は、比較的良い精度を保つ事が分かりました。

おまけ

参考として、uncompressのコードも載せておきます。

[cpp]
void uncompress(int nVertices, float &p, MFloatPoint &min, MFloatPoint &max, unsigned short &compressedP) {
float delta[3] = {max.x-min.x, max.y-min.y, max.z-min.z};
#pragma omp parallel for
for (int i = 0; i < nVertices; i++) {
p[3*i] = (((float)compressedP[3*i]/65535.0f) * delta[0]) + min.x;
p[3*i+1] = (((float)compressedP[3*i+1]/65535.0f) * delta[1]) + min.y;
p[3*i+2] = (((float)compressedP[3*i+2]/65535.0f) * delta[2]) + min.z;
}
}
[/cpp]

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


≪セットアップ室≫ポリゴンで作られた髪の毛のセットアップ 第一回 ~仕様決め編~

$
0
0

はじめまして、セットアップ室の池田です。
髪の毛のセットアップについて、少し書きます。

●はじめに
自分自身が経験した髪の毛のセットアップパターンは大別して2つです。

今回は髪の毛がポリゴンオブジェクトの場合のセットアップ方法について書いていきます。

●まずは手作業で
髪の毛のセットアップの場合、オブジェクト数の多さがネックです。
ポリゴンでフサになっていても100本以上あったりします。hair shaderだと1000本単位が普通です。
何度も同じことを繰り返す必要があります。これはスクリプトの出番ですね。
ただ、一度は手で作らないと安心できません。手探りでスクリプトを書くと逆に時間がかかる場合もあります。
そこで計画を立てます。

=======================================================

カーブ1本(1フサ)だけ手作業でセットアップして仕様を決める

工程ごとにスクリプト化して全部の髪の毛をセットアップ

ツール化する

=======================================================

本稿では計画の中の、手作業で1フサ分のセットアップをして仕様を固めるまで
を書いていこうと思います。
次回があればスクリプト化していく工程に移り、回をかさねて完成に近づけていきたいな、と思います。

●頭部のオブジェクト、骨を用意
まず頭と髪の毛のモデルが無いと作れません。 これ以上ないぐらいシンプルにします。

1、顔オブジェクトと髪の毛オブジェクト(角みたいなの)を作ります。
2、頭の骨を作り”head_JT”と名づけます。
3、顔オブジェクトを骨でバインドします。(髪の毛オブジェクトはしません)

●髪の毛のジョイントを作成
髪の毛のポリゴンの中心を通るジョイントを作ってバインドします。

こんな感じです。
名前は”hairCrv1JT”,”hairCrv2JT”,”hairCrv3JT”…とします。

●ジョイントからIKスプラインを作る
先程の骨をIK Spline Handle ToolでIKスプラインをつくります。

名前は、
IKハンドル名…hairCrv1IK
カーブ名  …hairCrv
とします。

●カーブからmayaHairを作る
先程、出来たカーブを複製してmayaのnHairを作成します。

カーブを選んだ状態で以下をメニューから実行してHairを作ります。
nDynamics > nHair > Assign Hair System > New Hair System

なにか沢山出来てしまいましたね。

ひとまずこんな風にリネームと階層整理をします。

==========================================
新しい親グループ…mayaHair_GP
—-follicleの下にあるカーブ…inputCrv
—-follicle1
—-hairSystem1
—-nucleus1
—-”hairSystem1OutputCurves”の中にあったカーブ…outputCrv ==========================================

考え方として「入力」、「制御」、「出力」の3つに分けて考えると分かりやすいかもしれません。
==========================================
inputCrv… Hair入力カーブ

follicle1 … Hair 1本 を制御する
nucleus … Hair 全体 を制御する
hairSystem1 … Hair 全体 を制御する

outputCrv … Hair出力カーブ
==========================================

●IKスプラインとHairをつなげる
先程名前をつけたHairの出力カーブとIKスプラインカーブをつなげます。

outputCrvShape.worldSpace から hairCrvShape.create へとコネクトします。

ここで一度再生ボタンを押してみましょう。

このようにヘタッと髪の毛が動けば、ここまで成功です。

●コントローラーを作る
コントローラーとして使用するロケーターを用意します。

名前はinputCrv0,inputCrv1,inputCrv2…とし、カーブの頂点数分、用意します。

●重くなる原因
このコントローラーをカーブの頂点にリグを組んで繋げる訳ですが、方法はたくさんあります。
最も量産される箇所です。
ここでどれだけ軽量化できるかが大事になります。

ポイントは2つ
・ノード数の多さ
・各ノードの重さ
です。

今回、最終的に100本の髪をセットアップする予定です。
コントローラーはカーブ1本につき5つと仮定します。(実際は今回4ですがキリが良いので)
そのコントローラーの間には二つをつなぐ為のノードが作られます。仮に2つ、としましょう。

するとコントローラーとカーブ間にはいくつのノードが出来るでしょうか。
単純な計算式をたててみます。
カーブの数×コントローラーの数×つなぐためのノード数=最終的に作られるノード数
最初、つなぐためのノード数が2だと、
100 × 5 × 2 = 1000
これを6つになると、
100 × 5 × 6 = 3000
2000個も増えてしまいました。何の気なしに+3しただけで、最終結果が2000もの差が出てしまいました。
こういう倍々で増える箇所は慎重に決めないといけません。
さらに、ノードによって処理の重さは様々です。
通常のセットアップでは良く使う”pointConstraint”も、ここでは使用しません。
”pointConstraint”はノードはひとつにまとまってますが、実は内部でそこそこ複雑な処理をしています。
実質的に何個もノードを増やしたのと同じです。これが何千個もあるとかなり負担になります。

●コントローラーの位置を決める
カーブの頂点からコントローラーの位置を取得します。
”inputCrv”のカーブのシェイプ”inputCrvShape”のcontrolPointsと各コントローラーのtranslateをコネクトします。

これでコントローラーの位置が取得できました。

コネクションしたばかりですが、目的は達成したのでコネクションを切断します。

●コントローラーとHairをつなげる
次はコントローラーの機能を持たせます。
Hairの入力カーブはinputCrvでした。これを制御します。
各コントローラーのtranslateを、”inputCrvShape”のcontrolPointsにコネクトします。
先程と逆にです。

先ほど完全に位置を合わせたので、translateをコネクトしてもカーブの形が崩れません。

これでコントローラーから髪の毛オブジェクトまでが繋がりました。
一度セーブして、コントローラを動かしてから再生してみましょう。
コントローラーの動きに髪の毛が付いてきて、かつシミュレーションが出来れば成功です。

●頭のジョイントについてくるようにする
ある程度、形になってきました。しかし、まだ問題があります。
頭に動きに髪の毛がついてきません。

コントローラーを頭の動きに追従させる必要がありますね。
空グループを作り”InputCrv_GP”と名付け、頭の骨”head_JT”とparentConstraintします。
そしてコントローラーを”InputCrv_GP”の中に入れます。

※(さきほど、Constraintは重くなると書きましたが、それは何千個も増えた場合です。)
※(ジョイントの直下に入れてしまうやり方も間違ってはないのですが、込み入ってくると整理が付かなくなります。
DFセットアップ室的には良しとされていません)

実際に頭の骨”head_JT”だけ選んで、移動してみます。

失敗です。
コントローラーはついてきてますが、肝心の髪の毛がついてきません。
何故でしょう。
先程コントローラーのTranslateをコネクションしましたが、これはlocal値を取得しています。
親をどれだけ動かしても子のTranslate値は変わりません。
world値を取得しないといけません。

pointConstraintを使いたいですが、前述したとおり重くなるので使いませんし、そもそもカーブの頂点には出来ません。
そこで、matrix系のUtilityノードを使います。

matrix系のUtilityノードは、事前にmatrixNodesプラグインが読み込まれている必用があります。
もし読み込まれていなかったらPlug-in Managerで読み込んでおきましょう。
Window > Settings/Preferences > Plug-in Manager > matrixNodes.mllのLoadedにチェック

Node Editorを立ち上げてコントローラーとカーブのコネクションを表示します。
Tabキーを押して’deco’と入力して”decomposeMatrix”ノードをコントローラー分作ります。

次にコントローラーの”WorldMatrix”をdecoposeMatrixの”inputMatrix”につないでいきます。

次にdecoposeMatrixの’Output Translate’を、カーブの各頂点”ControlPoints”につないでいきます。

これでずれていた髪の毛の位置がバッチリ合いました。

matirix系のUtilityノードは軽く、コントローラーとカーブ間を1つのノードだけで繋ぐことが出来ました。

●コントローラーの初期値を0にする
もう少しで完成です。
コントローラーのTranslateの初期値が問題です。
現在の値はとても覚えやすい値とはいえません。
毎回Ctrl+Zで戻すとか、全ての値をメモしておくのは、ちょっと実作業ではしたくないですね。

そこで同じ位置にグループを作り、親にしてしまいます。

これで、どれだけ動かしてもすぐに初期位置に戻せます。

●スプラインIKジョイントを頭の動きに追従させる
コントローラーからスプラインIKのジョイントの位置は制御出来ています。
しかし軸がこのままだと頭についてきません。
今回のサンプルはフサの形では分かりづらいですが、頭の上で髪の毛がくるくる回転してしまいます。
これはスプラインIKジョイントである”hairCrv1JT”を、頭に追従する”InputCrv_GP”の子供にすることで解決します。

●実際に動かしてみる
ちゃんとできているか実際に動かしてみます。
テストする為にフレームレンジを増やしてから再生します。

Dynamics > Solvers > Interactive Playback
これで再生すると、オペレーションしながら再生が出来ます。

最後の工程の前にキャプチャーしたものなので、スプラインIKジョイントの軸が安定していません。ご了承ください。

●まとめ
これで1本の髪の毛がセットアップされました。
今回は「ノード数が膨大になるので軽くなるにはどうするか」という部分がポイントです。
現時点での自分のやり方なので、もっと効率的なやり方もたくさんあるでしょう。
次回は、今回の処理を複数本の髪の毛にかける為、スクリプト化していきます。

●おまけ
ところで、Hairといえば、DFでは「rtHair」という高速な Hair Simulation が可能となる自社のmayaプラグインがあります。

開発部の方々が作ったもので、最近実戦投入されました。
実際に高速化を実現し、なかなかの成果を上げてもらいました。
速さの違いを動画でごらんください。

3倍ぐらい早いでしょうか。
髪の毛の動きは初期値そのままです。
当然、値を変えて動き方を調整できます。

Hairシステム自体の重さは、リグをどう組んでも解決できませんから、「rtHair」があると助かります。

この動画の髪の毛セットアップも仕組みとしては、今回説明したものとほぼ同じです。

この「おまけ」はノウハウとかではなく「DFでこんなことやってます」という紹介です。
また機会があればセットアップ中に役に立ったインハウスのプラグインを紹介したいですね。
紹介だけじゃ我慢できない、実際にさわって見たい方、DFの募集要項のページを一度ご覧になってみてはどうでしょう??
それでは、またお目にかかれたら幸いです。お疲れ様でした。

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


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

PySideでリストをカスタマイズするぞ! ~応用編「delegate」~

$
0
0

みなさんこんにちは。TDの小野です。今回もPySideのお話。
前回、投稿してからかなり間が空いてしまいましたが、引き続きPySideのmodel、view、delegateについて、サンプルプログラムを用いながら解説していきます。

ちょっとおさらい

前回の「PySideでリストをカスタマイズするぞ! ~基礎編「model/viewアーキテクチャ」~」では、model、viewの仕組みを解説しながら、サンプルリストを作成しました。作成したリストがこれです。

前回作成したリスト
 
このときmodelは文字と色の情報を持っていました(下図左)。そして、viewに対して「文字はこれだよ」、「色はこれだよ」とそれらの情報を渡して、viewが表示していました(下図右)。
今回はviewに渡した情報を、好きなよ~にカスタマイズして表示する方法を解説します。

model、viewの概念
 
目標はあくまでこんなリストです。

リストのUIデザイン
 
各アイテムの中に文字だけじゃなくて、サムネイル画像なども入っていますね。さて、どうやって作りましょうか…。ここからがdelegateの出番です。

delegateの役割

上のデザインのように各アイテムの表示を細かくカスタマイズする際、活躍するのがdelegateです。また、テーブルやリストを編集する際にアイテムをダブルクリックして出てくるテキストボックスなどの表示や、編集後、入力されたデータをmodelに渡すのもdelegateの仕事です。QTableViewQListWidgetもデフォルトではQStyledItemDelegateというdelegateが内部でその役割を担っています。そこでオリジナルのdelegateを作ってリストの表示に使ってみましょう。

オリジナルのdelegateを作ってみる

前回のサンプルリストで使ったデータは以下のようなものでした。
[python]
data = [
{"name": "Lion", "color": [237,111,112]},
{“name”: “Monkey”, “color”: [127,197,195]}
]

[/python]

これから作るリストでは、modelが保持しているデータはこのままで、表示上だけ「Name: ○○」と表示されるものを作ってみます。まずは以下のようにQStyledItemDelegateを継承して、シンプルなオリジナルdelegateクラスを作成してみましょう。

[python]
class CustomListDelegate(QtGui.QStyledItemDelegate):

def __init__(self, parent=None):
super(CustomListDelegate, self).__init__(parent)

def paint(self, painter, option, index):
# indexからデータを取り出す
name = index.data(QtCore.Qt.DisplayRole)
color = index.data(QtCore.Qt.ForegroundRole)

# ペンを作って持たせる
pen = QtGui.QPen(color, 0.5, QtCore.Qt.SolidLine)
painter.setPen(pen)

# テキストを描く
painter.drawText(option.rect,
 QtCore.Qt.AlignVCenter|QtCore.Qt.AlignLeft,
 ”Name : ” + name)

[/python]

最低限必要なのはこのpaintメソッドです。このメソッドがdelegate内での描画を主に担当します。
paintメソッドは以下の3つの引数を取り、これらの情報を用いてテキストを表示させます。

  • painter (QtGui.QPainter) : お絵かきさんです。絵を描くのに特化したクラス。
  • option (QtGui.QStyleOptionViewItem) : viewの各アイテムの情報を持っているクラス。各アイテムの表示されているサイズや、そのアイテムが選択状態かどうかなどの情報を持っています。
  • index (QtCore.QModelIndex) : modelが持っている各アイテムの情報が入っています。

では上のコードの解説を。まずは、名前と色の情報を取得する必要があるので、indexからそれらを取得します。必要な情報のRole(役割)をdataメソッドに渡すと値が取得できます。前回作成したmodelでは、DisplayRoleとして名前情報、ForegroundRoleとして色情報を返すようにしているので、9行目、10行目のように取得します。

あとはお絵かきさんにペンを持たせて描かせるだけです。13行目で色や太さ、線の種類を指定して、ペンを作っています。そして、14行目でそのペンをpainterにセットしています。17行目が実際にテキストを描画しているところです。このdrawTextメソッドに文字列を渡してテキストを表示するのですが、このときに「Name : 」という接頭辞をつけています。描画の範囲を決めているのがoption.rect (17行目) です。ここにはQRectというクラスを渡すのですが、そのポジションや大きさを変えれば描画の位置を変更できます。

では、delegateができたので、ListViewにセットしましょう。

[python]
def main():
app = QtGui.QApplication(sys.argv)
data = [
{"name":"Lion","color":[237,111,112]},
{“name”:”Monkey”,”color”:[127,197,195]}
]
myListModel = CustomListModel(data = data)
myListView = QtGui.QListView()
myListView.setModel(myListModel)
# デリゲートを生成
myListDelegate = CustomListDelegate()
# 作ったdelegateをListViewにセット
myListView.setItemDelegate(myListDelegate)
myListView.show()
sys.exit(app.exec_())

[/python]

7行目で使っているCustomListModel前回作成したオリジナルmodelです。
11行目でオリジナルdelegateを作成し、myListViewのdelegateとして13行目でセットしています。

実行すると以下のように表示されます。

オリジナルdelegateを使用したリスト
 

もっとカスタマイズしてみる

では次に、選択したときに背景の色が変わるようにしてみます。また、各セルの高さも変えてみます。
アイテムが選択状態かどうかはpaintメソッドのoption引数が持っています。選択状態のときだけ、背景を描画するように変更しましょう。

[python]
def paint(self, painter, option, index):

# stateプロパティがセレクトかどうかチェック
if option.state & QtGui.QStyle.State_Selected:
# 背景を描く
bgBrush = QtGui.QBrush(QtGui.QColor(60,60,60))
bgPen = QtGui.QPen(QtGui.QColor(60,60,60), 0.5, QtCore.Qt.SolidLine)
painter.setPen(bgPen)
painter.setBrush(bgBrush)
painter.drawRect(option.rect)

# indexからデータを取り出す
name = index.data(QtCore.Qt.DisplayRole)
color = index.data(QtCore.Qt.ForegroundRole)

# ペンを作って持たせる
pen = QtGui.QPen(color, 0.5, QtCore.Qt.SolidLine)
painter.setPen(pen)

# テキストを書く
painter.drawText(option.rect,
 QtCore.Qt.AlignVCenter|QtCore.Qt.AlignLeft,
 ”Name : ” + name)

[/python]

4行目が選択状態を判断している部分で、選択状態であれば6から10行目で背景を描いています。
また、セルの高さを変更するためにはQStyledItemDelegatesizeHintメソッドをオーバーライドします。

[python]
class CustomListDelegate(QtGui.QStyledItemDelegate):

def __init__(self, parent=None):
“”"省略”"”

def paint(self, painter, option, index):
“”"省略”"”

def sizeHint(self, option, index):
return QtCore.QSize(100, 40)

[/python]

これを実行すると以下のようにアイテムの高さが高く、選択すると色が変わるリストが表示されます。

アイテムの高さが高く、選択状態のわかるリスト
 

画像を表示してみる

最後に、アイテム内に画像を表示してみましょう。まずはセル内で描画する位置をなんとなーく考えます。今回は下図のように左側に画像が来てその横にテキストが表示されるように作ってみます。

各セルの表示位置

まずはmodelに渡すデータに画像の情報を入れます。「thumbnail」というキーに画像ファイル名を入れておきます。

[python]
data = [
    {"name":"Lion", "color":[237,111,112], “thumbnail”:”lion.png”},
    {“name”:”Monkey”, “color”:[127,197,195], “thumbnail”:”monkey.png”}
]

[/python]

modelも少し書き換えないといけません。前回のCustomListModelを修正し、dataメソッドでthumbnail情報を返せるようにしておきます。

[python]
class CustomListModel(QtCore.QAbstractListModel):

# データを受け取りitemsに格納する
def __init__(self, parent = None, data = []):

super(CustomListModel, self).__init__(parent)
self.__items = data

# アイテムの数を返す
def rowCount(self, parent = QtCore.QModelIndex()):

return len(self.__items)

# Roleに合わせてデータを返す
def data(self, index, role = QtCore.Qt.DisplayRole):

if not index.isValid():
return None

if not 0 <= index.row() < len(self.__items):
return None

if role == QtCore.Qt.DisplayRole:
return self.__items[index.row()].get(“name”, “”)

elif role == QtCore.Qt.ForegroundRole:
color = self.__items[index.row()].get(“color”, [])
return QtGui.QColor(*color)

# Thumbnailキーの画像ファイル名を返す
elif role == QtCore.Qt.UserRole:
return self.__items[index.row()].get(“thumbnail”, “”)

else:
return None

# 各セルのインタラクション
def flags(self, index):
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled

[/python]

QtCore.Qt.UserRoleはPySideが用意している自由に使えるRoleです。これで、delegate内でindex.data(QtCore.Qt.UserRole)のように取得するとthumbnailの値が取得できます。
あとはdelegateのpaintメソッドで画像の表示位置やテキストの表示位置を決め、描画していきます。

[python]
class CustomListDelegate(QtGui.QStyledItemDelegate):

def __init__(self, parent=None):
“”"省略”"”

def paint(self, painter, option, index):
# 画像のサイズとマージンの定義
THUMB_WIDTH = 60
MARGIN = 5

# stateプロパティがセレクトかどうかチェック
if option.state & QtGui.QStyle.State_Selected:
“”"省略”"”

# indexからデータを取り出す
name = index.data(QtCore.Qt.DisplayRole)
color = index.data(QtCore.Qt.ForegroundRole)
# 画像ファイル名を取ってくる
thumbName = index.data(QtCore.Qt.UserRole)
# Pixmapオブジェクトに変換
thumbImage = QtGui.QPixmap(os.path.join(CURRENT_PATH, “images”, thumbName)).scaled(THUMB_WIDTH, THUMB_WIDTH)
# 画像の表示場所を指定
r = QtCore.QRect(option.rect.left(), option.rect.top(), THUMB_WIDTH, THUMB_WIDTH)
painter.drawPixmap(r, thumbImage)

# ペンを作って持たせる
“”"省略”"”

# テキストを書く
r = QtCore.QRect(option.rect.left()+THUMB_WIDTH+MARGIN,
option.rect.top(),
option.rect.width()-THUMB_WIDTH-MARGIN,
option.rect.height())
painter.drawText(r,
QtCore.Qt.AlignVCenter|QtCore.Qt.AlignLeft,
“Name : ” + name)

def sizeHint(self, option, index):
return QtCore.QSize(100, 60)

[/python]

まずは8行目で画像を表示させるサイズを定義しています。後でポジションの計算などいろいろな場所で使うので、このようにどこかに定義しておいたほうがいいです。そして、19行目でmodelインデックスのデータから画像ファイル名を取得し、次の行で、実ファイルパスに変換(スクリプトのフォルダ内にimagesというフォルダがあり、その中に画像がある状態です。)後、PySideで画像を扱うオブジェクトであるQPixmapを作っています。
画像の表示位置を指定するQRectを作成しているのが23行目です。その後painterdrawPixmapメソッドで画像を描いています。
テキストの表示位置も同様に変更しなければなりません。THUMB_WIDTHMARGINでどれだけ右にズラすかを計算しています。32行目はズラしたぶんだけ表示範囲を狭める計算をしています。

これを実行すると以下のようなリストが表示されます。

サムネイルのあるリスト
 
いかがでしょうか。少しは初めの手書きデザインに近づいてきたのではないでしょうか。
あとはpaintメソッド内で各データの位置の指定と描画の繰り返しです。根気あるのみです!

最終的に僕はこんな感じのリストを作りました。

カスタムリスト
 
ソースコードはこちら。→ダウンロード
ぜひ試してみてください。

おわりに

はじめは取っ掛かりにくいmodel、view、delegateですが、仕組みを理解しておくだけでも開発の柔軟性が格段に上がります。
また、このような概念は今流行のモバイルアプリやウェブアプリなどでもデータ管理の手法として取り入れられていますので、他の分野へも応用していけます。
delegate自体は今回解説した機能以外にもユーザーとのやり取りを担当したりと、奥の深いクラスです。そのあたりはまた機会があれば紹介していきたいと思います。

では、また次回。


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

爆発シミュレーションの制御

$
0
0

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

映像表現の中で比較的よく目にする爆発に関して、その形状や流れを細かく制御する方法を、私が過去に関わった
次の論文を取り上げて説明していきたいと思います。

Procedural Fluid Modeling of Explosion Phenomena Based on Physical Properties
(日本語に訳すと、物理特性に基づく爆発現象における流体の手続き的なモデリング手法、とでもなりましょうか)
この論文の著者のサイトからも、論文がダウンロードできます

この論文の方法を使うと、次のように制御された爆発を生成できます(1つ目の画像の上端のように、爆発の流れの指定をして制御した結果です)。


論文から引用(上:起爆から10フレーム目、下:起爆から55フレーム目(爆発が終了し、火炎から煙に変化))

この方法では、爆発が広がって最大になるまでの時間、起爆時の圧力の強さといった、爆発の移動速度や特徴に関する数個のパラメータをユーザが与えるだけで、比較的爆発らしくリアルな動きで進んでいく火炎を作成することができます。

また、以下のように爆発がどのような方向に伝播して行き、どのような形になるかも、ユーザが具体的に細かく自由に指定できます。


論文から引用(左:起爆から25レーム目、右:起爆から70フレーム目)

左図の隅にあるような制御パスというものを使って火炎の動きの軌道を指定して、ラクダの形状の爆発を生成できています。
ここでは、爆心はラクダの体の中心付近です。

以下のような結果動画も、公開されています(1:37あたりでラクダの例があります)。

この論文の著者のサイトには、解像度の高い格子を使った結果動画もあります。

今回の論文の内容そのものの計算時間は、流体シミュレーションの部分に比べてかなり少ないですし、この方法を使えば細かい制御の指定ができるので、欲しい形状や流れを持った爆発を得るための作業時間が格段に少なくて済みます。

そもそも、ラクダの形になるようなパラメータを探すには気の遠くなるような試行錯誤が必要ですし、ラクダの首のように大きくカーブするような爆発の流れを作り出すことは通常、かなり難しいです。
高速で大きな渦を伴って動く流体の制御の場合は特に、パラメータのちょっとした変更で大きく計算結果が変わってしまうので、この論文のようにパスを使って、より直接的に火炎の動きを指定して制御できるのは便利です。

論文のアイデアとしては簡単に言うと、流体の動きを爆発の物理的な特徴を考慮しながら、近似的に作り出すということです。
以下では、興味がある人のために、このアイデアについて紹介してみます。


流体系の研究に興味がある人のために、もう少し論文のアイデアや技術的な説明をしてみます。
あくまで、考え方や研究のモチベーション、手法のエッセンスについて主に書いてみたつもりです。
考え方の説明の仕方等、ニュアンスが実際の論文とは異なるかもしれないので、詳細は論文自体も確認して頂けると幸いです!

爆発のシミュレーションについて

爆発は通常はCG用の流体シミュレーションで、爆発を表現するような速度場を計算して、その速度場に火炎を沿わせて移動させることで生成します。火炎のような爆発現象における流体を、格子法では密度として定義し、計算をします。以後、密度を移動させるというような表現が出てきますが、一般的なイメージとしてのどのくらい濃いかという意味よりは、単に火炎のような具体的に移動する流体という風に、密度については考えて下さい。
また、流体シミュレーションの中でも、渦やうねるような流れの表現が作りやすいためグリッドを使った格子法が、爆発には一般的によく使われていると思います。格子法の詳細については、以前の記事をご覧ください。

今回の論文では特に、圧縮性の爆発について取り上げています。
圧縮性の爆発とは、爆薬などが起爆して高い圧力が中心に生じて気体が膨張し、それによって、火炎が周囲に放射状に広がり(伝播し)、そして、圧力が収まって伝播が収束するまでのフェーズについての現象です。非圧縮性の爆発は、収束後に、火炎や煙が高温であるために上昇していく次のフェーズの現象(キノコ型のものを含む)で、これについては別の機会に説明できればと思います。

爆発の制御における問題

制御の際に主に問題となる事柄を、簡単に説明します。
どの様な問題があると思いますか?
爆発ではその物理的な特徴や流れが、問題の原因となることが多いのです。

●制御をすると爆発らしく見えなくなってしまう
具体的には、火炎の伝播速度が時間の経過と共に変化していくという特徴を爆発は持っています。ですので、この特徴が制御の影響で変えられてしまうと、結果として特徴が弱まってあまり爆発らしく見えなくなってしまうのです。

●流れや圧力の状態が複雑で制御が難しい
爆発のシミュレーションではその性質上、大きな渦や圧力、あるいは流れの干渉などがたくさん発生しているので、基本的には流れが複雑になっています。なので、特定の部分を制御してもその複雑さゆえに制御の効果があまり出ないこともしばしばあります。

●格子法の特性上、細かい制御が難しい
例えば、カーブしたりするような動きの表現は、単に外力と言った速度場で望む方向に曲げればよいと言うわけにはいかず、曲げた時点で渦が発生して流れが変わったりと、制御がとても難しいのが現状です。

いずれにしても、上のような色々な問題から制御がとても難しいのが、爆発のシミュレーションの課題と言えると思います。そして、このような問題がありつつも、爆発の制御を扱う論文は過去にあまりなかったことも事実です。
これらの問題に対して、次で説明するようなアイデアを使って爆発を制御するのが、今回紹介する論文です。

→ この節で説明した問題の詳細については、末尾の「補足」爆発の制御が難しい理由(論文の研究背景)を参照下さい。


論文のメインアイデア

この論文で最も特徴的な点は、爆発における火炎の流れを”手続き的”に作り出だすというアイデアです。流体シミュレーションだけでなくそれに加えて流れを近似する計算も使って、ものが流れて移動していく状態を作ります。

このアイデアを使うことで、爆発の制御における問題に共通する、意図通りに流体が動いてくれずに制御が難しいという問題に取り組んでいます。

論文のタイトルにあるように端的に言うと、どのように爆発の流体が広がっていくかを時間(フレーム)ごとにモデリングしていくという考え方をとっています。
流体をモデリングする?
ちょっと聞きなれないとは思いますが、基本的には流体を手続き的な方法で具体的にどんどん作り出していくと言う意味です。また、すでに説明したように、流体を制御パスに沿って作り出していきます。

ここでいう手続き的な方法とは、通常の流体シミュレーションのように密度を速度でそのまま動かす純粋な格子法とは異なる、別の方法のことを指しています。手続き的とは、爆発の火炎(密度)が移動するという状態遷移を、速度を含めた爆発の流れの特徴も含めながら、間接的な計算で近似的に作り出してしまうと言う意味です。
また、ここでは、通常イメージする関数的という意味とは異なるニュアンスで、手続き的という表現をしている事に注意下さい。

結果的に、速度によって密度が移動したときの流れに近い、一連の状態を制御パスに沿ってフレーム毎に作り出すことが出来ます。もちろん、このように作り出した密度の移動自体は、純粋な格子法のみによって作り出す流れと同じ精度とはいきませんが、手続き的な方法をとることで、もっと大きな強制力によって密度が移動した状態を生成します。
なので、密度(火炎)の移動を、より意図通りに制御できるようになります。しかも、手続き的に計算された速度や密度の場は、爆発の火炎の伝播に関する物理的な特徴を考慮しています。その特徴とは主に、火炎の伝播速度が時間の経過と共に変化していくことです。

さらに、手続き的な処理の最後に格子法も利用し、格子法で流体らしい密度(火炎)の挙動、渦や流れがうねるような特徴も加わります。しかしながら、ここでの格子法はあくまで、このような特徴を出すためのもので、密度の主たる動きそのものは、密度と速度の場の配置によって、すでに手続き的に決められ、作り出されていることが重要です。

ですので、手続き的な方法で制御パスに沿って密度の移動を作成(モデリング)しつつも、爆発の物理的な特徴と格子法も同時に組み合わせることで、結果的に制御しつつ、同時に物理的なリアリズムも出来るだけ維持した爆発を作り出しています。このような方法をとる事で、比較的意図するように爆発を制御できるようになるので、作業の試行錯誤が格段に少なくて済むようになります。


メインアイデアを使った具体的な処理

流れを近似する計算も使って、ものが流れて移動していく状態をつくるというメインアイデアを、図で表現してみると以下のようになると思います。メインアイデアと流体シミュレーション(格子法)だけの方法の2つを比較しています。

格子法で密度を速度で移動させるという処理を、密度の配置 → 速度の配置 → 格子法、のようにして代わりに近似しています。

具体的に説明すると、図の右端(2.格子法のみ)は、速度場に沿って密度(火炎)を流れさせて、密度を移動させていくという格子法ですが、複雑な流れや大きな圧力や渦の影響で、意図通りに密度が移動してくれないといったことが決して少なくありません。
→ 詳細は「補足」爆発の制御が難しい理由(論文の研究背景)

一方、図の左(1.密度の移動を手続き的に生成)のような論文の方法では、速度によって密度がどのように移動するかを事前に決める形で、計算に基づいて移動先に密度を配置します。密度の移動を表現する密度場を計算(格子法を使った流体計算という意味ではなく)する訳です。これが手続き的な処理にあたる部分です。そして、その密度が沿っていく軌道が、茶色い太線の制御パスになります。

ここで行っている重要なことは、事前計算と配置で、意図したように密度の移動が行われることを基本的に保証させて、尚かつ、結果的に火炎の伝播速度が時間の経過と共に変化していくという特徴についても、できるだけ確保することです。
イメージしづらいでしょうか?
密度が移動して欲しいように進むよう、事前計算と配置の処理をうまく利用している訳です。
こうすることで、爆発の複雑な流れに大きく影響されずに、比較的意図するように密度をパス上に制御して動かせるようになります。

また、図の左(1.密度の移動を手続き的に生成)の速度場についても、密度を実際に移動させるいう方法(手続き的ではない)をとったときに、移動のために必要であると予測される速度として、あくまで計算で作り出しているだけである、ということに注意してもらえればよいと思います。
さらに論文のメインアイデアで、格子法はあくまで、流体らしい挙動、渦や流れがうねるような特徴を出すためで、密度の主たる動きそのものは、密度と速度の場の配置によってすでに決められている、というように説明しました。
ようするに、上で説明した密度の配置 → 速度の配置 → 格子法の部分の、特に、密度の配置 → 速度の配置のところが密度の主たる動きを決めているという意味なのです。

この論文の方法では、速度によって密度がどのように移動するかは、物理的な特徴を使って求めています。また、密度(火炎)の伝播速度の変化を、爆発の火炎の伝播に関する物理的な特徴を考慮しながら計算しています。この速度についても図の左(1.密度の移動を手続き的に生成)から分かるように、密度と同様の方法で上の図のように、制御パスに沿って速度場として配置して作り出します。

よって、密度と速度の両方を爆発の物理的な特徴を使って計算して作り出すことで、火炎の伝播速度が時間の経過と共に変化していくという特徴を実現しています。結果的に、爆発らしい特徴を持ちつつ、また、その爆発らしい特徴があまり失われることなくパス上を移動していく密度を手続き的に作り出せます。ですので、どのように密度を移動させたいかという意図をできるだけ反映しながら、制御できることになるのです。


爆発の制御の全体像

メインアイデアで説明した方法を使って、設定した爆発の収束までの時間に、指定した形状になる爆発を作り出します。ステップごとに、制御パス上に手続き的に流れを作り、また、爆発の特徴も追加されながら処理が行われます。

処理の開始後は、1フレームごとに以下の図のような5つの処理を繰り返します(以下のような、フレーム毎の処理の全体像を論文から引用させてもらいます)。

Process 1, 2は、メインアイデアを使った具体的な処理に対応している部分で、密度の移動を手続き的に生成しています。
また、Process 5は密度の移動を手続き的に生成するときに、格子法を使って流体らしい挙動を作り出している部分に当たります。

さらに、Process 3, 4については基本的には、爆発の物理的な特徴をさらに加えるために、
・圧力強度から圧力場・温度場を生成
・爆轟現象を考慮した、非常に高い圧力も表現した大きな流れの生成
・燃焼を考慮した大きな渦の生成
といったことを行っている部分です。

ユーザ指定と爆発の物理的な特徴の作成

この論文では基本的に、圧力強度曲線というものを使って、爆発の物理的な特徴を作り出します。この特徴が手続き的に流れを作るときにも利用される訳です。

圧力強度曲線において、爆発が伝播して収束するまでの時間、圧力の初期強度、圧力の減衰の度合いという数個のパラメータをユーザが与えることで、手続き的な密度(火炎)の移動の生成のときに用いる伝播速度の時間変化を、自動算出します。これによって比較的爆発らしい、リアルな火炎の動き(流れ)を作成することができます。

さらに言うと、この曲線が一旦決まると、密度や速度の時間ごとの特徴がそれぞれ導き出されるので、基本的にはこの圧力強度曲線だけをユーザが指定してあげればよいことになります。また、これらのパラメータを自由に設定しても、いぜんとして爆発らしいリアリズムは基本的に維持されることになります。

実際には、パラメータを決めてあげた圧力強度曲線が、密度伝播曲線、密度濃度曲線および圧力伝播曲線という、爆発の伝播を特徴付ける曲線を導出することになります。また、これらの3つの曲線は自動で計算されます。
これらの曲線を利用して、爆発の制御の全体像で説明したように、制御しながら爆発を生成していきます。

また、すでに説明したように、爆発がどういった流れで周囲に伝播し、どのような形に徐々に変化していくかも、すなわち、動きや形状もユーザが具体的に細かく指定できます。この指定は、爆心から爆発が収束する到達点までの流れの軌道を表現する制御パスというものを使って行います。全体としては放射状の爆発の形状を作りながら、かつ、部分的に飛び出ているようなものを指定したいときは、飛び出ている部分だけを別途指定して簡単に表現できます。

また、先ほどのラクダの例では体の部分については、爆心位置を指定するだけで、自動的に爆心からラクダの体の表面各所へと、火炎を移動させるような複数の制御パスを生成できます。大きい単位の流れの火炎が、ラクダの体の表面へと伝播していくような表現をしたい場合は、制御パス上を移動していく密度のかたまりの大きさについても自由に指定できます。

以上のような圧力強度曲線と制御パスの指定の入力のみで基本的には、制御パスに沿って制御されて火炎を動かすことが出来て、意図するような形状で収束するような爆発を作成できます。

まとめ

以上のように、この論文の基本的な考え方について主に紹介してみました。

また、この論文の方法では、設定した時間に指定した形状になるだけでなく、火炎の伝播速度が時間の経過と共に変化していくという特徴を考慮できているので、火炎が伝播していく過程における、火炎の伝播タイミング(途中経過)についても、爆発らしいリアリズムを可能な限り維持したものになります。このタイミングも、ユーザ指定された圧力強度曲線に従って決めることが出来ます。
以上のように、
●制御をすると爆発らしく見えなくなってしまう
●流れや圧力の状態が複雑で制御が難しい
●格子法の特性上、細かい制御が難しい
というそれぞれの問題に対して、
○流れ自体は制御パスで3次元的にどのように動いていくか自由に指定できつつも、同時に、制御パス上を手続き的に生成されていく火炎の流れと伝播速度は、爆発の持つ特徴に基本的に従う(制御しても爆発らしさを保つ)
○手続き的に流れを生成するので、爆発では流れや圧力の状態が複雑であるにも関わらず、あまり影響を受けずに流れを作れる
○部分的に大きく飛び出し、しかもカーブしているような爆発の流れも、制御パスに従って意図するように手続き的に生成できる
というように、1つの手法で解決策を提案できているのではと思います。
→ 問題そのものの詳細は「補足」爆発の制御が難しい理由(論文の研究背景)

本論文では、物理シミュレーションをベースにした手法との比較も行っています。

論文から引用(左:本論文の手法、右:物理シミュレーションをベースにした既存手法)

この論文の方法では、制御パスが極端にカーブする場合には部分的に流体が飛び出てしまうことがあるので、爆発で勢いよく飛び出る高速の挙動との兼ね合いも考慮して微調整できれば、尚良いと思います。その具体案として例えば、制御パスの形状特徴(曲率など)に応じた圧力の制御といったものも論文では挙げられています。

今回の論文の内容にあたる爆発の制御部分の計算コストは、格子法の部分に比べてぜんぜん小さいですし、格子法についても一般的なStable Fluidsが使われています。
この方法に対して、自社開発しているアップレゾ手法(流体シミュレーションのアップレゾ(高解像度化)について)も適用できるかもしれませんし、いろいろと応用することで、さらにリアルな爆発を制御して作り出せるようになるかもしれないですね!

また、今回取り上げた方法の詳細についてさらに興味がある人は、論文をぜひとも見てみて下さい!



「補足」爆発の制御が難しい理由(論文の研究背景)

爆発を制御する上での、いくつかの大きな問題についてもう少し詳しく説明します。

まず一点は、
●制御をすると爆発らしく見えなくなってしまう、ということが挙げられます。

例を挙げると、物理ベースの流体シミュレーションで外力や圧力を使って流れをいろいろと変えて爆発を制御することによって、爆発の火炎が伝播していく過程が不自然に見えてしまうことがよくあります。
なぜなら、爆発は伝播して収束するまでの間に、火炎が伝播する距離が時間ごとに変化していくという独特の特徴があるからです。

制御をすることで爆発らしい動きを乱してしまうのです。

言い換えれば、火炎の伝播速度が時間の経過と共に変化していくという特徴を爆発は持っています。ですので、この特徴が制御の影響で変更されてしまうと、特徴が弱まってあまり爆発らしく見えなくなってしまうのです。したがって、爆発を制御したくても、リアルに見えるかどうか(物理的リアリズムを維持できているかどうか)とのトレードオフに悩まされてしまうことになります。

この特徴とは具体的には、爆発は起爆直後、大きな圧力で短時間で全体の伝播距離の大半を高速で移動し、その後、残りの時間で、それまでと比べて低速で伝播するといったものです(とはいっても、スピードはものすごく速いですが!)
この伝播の仕方は爆発を強く特徴付けるので、爆発を制御するときにこの特徴を維持できるかがとても重要になってきます。

物理的リアリズムを維持するという観点では、水のような流体の場合は、制御して流れるスピードや形状を変えても、基本的には水には変わりなくそこまで不自然には見えないので、制御と言う意味では爆発とは性質が異なると思います。
少なくとも、時間ごとの移動距離の変化は、決まってないですよね(一方で、水の表面の特徴を維持したりと言った、水特有の制御の難しさはもちろんありますが、、、!)。

とにかく、制御をすることで上のような爆発らしい動きを乱してしまうことになる、それが大きな問題の1つです。

●流れや圧力の状態が複雑で制御が難しい、と言う問題もあります。

特に、爆発のシミュレーションではその性質上、爆発が起こっている間は大きな圧力や燃焼が多く全体に存在していて、大きな渦や流れの干渉などが沢山発生しているので、基本的には流れがとても複雑になっています。
なので、特定の部分を制御しても制御の効果が伝わらず、なかなかそのまま制御の効果が出ないため、制御がやりにくいというのも爆発における大きな悩みの種です。

●格子法の特性上細かい制御が難しい、これもよく問題となります。
演出意図により、収束時に特定の形状になったり、途中で一部が飛び出たり、さらにはカーブしたりするというような爆発を表現したいこともあります。
しかしながら、例えばカーブしたりするような動きの表現は、流体を単純に外力と言った速度場である方向に曲げればよいと言う訳にはいかず、曲げた時点で渦が発生してその時点で流れが変わってしまったりするので、制御がとても難しいのが現状です。
放射状の爆発の一部分が飛び出すような表現についても、同じように流れが変わってしまうことが多々あります。

以上で説明したような様々な問題をできるだけ解決することで、物理的なリアルさを可能な限り維持しながらも爆発の制御をすることを目指し、提案されたのが今回取り上げた論文の手法なのです。


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

御来場御礼【プロダクションモデリングワークフロー 背景編】UV展開支援スクリプトのおまけ付き

$
0
0

みなさん、こんにちは。
TDの野澤です。

先日福岡にて、【プロダクションモデリングワークフロー 背景編】に呼んで頂きました。
http://www.borndigital.co.jp/seminar/3596.html

当日は、非常に多くの方にお越し頂き、大盛り上がりのイベントとなりました。
改めて、御礼申し上げます。

北田さんからは、如何に無駄なくビルをモデリングするか秘伝のテクニックが紹介されました。ロジカルな考え方がとても参考になったと思います。
鈴木さんからは、遠景のモデリングを超スピードで作成する極意や、キットバッシュと呼ばれる、一度作ったオブジェクトを細かく切り離して組み上げていくモデリング術が紹介されました。
どちらも、非常に貴重なテクニックで生で見れた人は本当にラッキーだったと思います!
ボーンさんのサブスユーザーの方には後日セミナーの模様のビデオが配信されるそうですので、要注目ですね。
今回私の方からは、背景モデリングがテーマだった事もあって、自分の専門分野から外れていましたが、
弊社で取り入れているモデリングデータのチェックツールの紹介等をさせて頂きました。
個人的にはモデラーさんが如何に楽にデータ整理出来るかがポイントになってくると思います。直感的に作ったモデルデータをそのまま下流に流してしまって思わぬトラブルになる事は良くあります。

また、セミナー後に来場者の方からお聞きしましたが、ゲーム、CG業界の中でTA/TDの需要が高まっているものの、人材不足が深刻なようです。
今後教育機関とも連携を取ってTA/TDの育成などにも協力していけたら嬉しいです。

また今回のセミナーの打ち合わせの中で、一緒に登壇した北田さんからこんなツールが探したけど中々見つからないという話題から新たに作成したtransferAndOffsetUVs.pyというツールの御紹介をしました。
このツールは、選択したオブジェクト群に対して、UVを転送し、且つタイル状にUVの位置をオフセットする事が出来ます。
スクリプトは、こちらからダウンロード出来ます。名前をつけてリンク先を保存を選択してください。
ダウンロードしたファイルは、PythonスクリプトをMayaが認識できる場所に配置してください。

使い方

下記のコマンドを入力し、GUIを起動してください。
[python]
import transferAndOffsetUVs as uv
reload(uv)
a = uv.Main()
a.gui()
[/python]

これで下図の様な、GUIが立ち上がります。

これで準備完了です。

簡単なシーンで試してみたいと思います。
下図の様に、polygonのplaneを25枚用意し、コピー元となるUVの位置を好きな位置に移動させます。

次に、calcボタンを押してみてください。
最初に選択したオブジェクトのUVの面積を調べて、0-1のUV空間の中に何個配置できるかを計算し、
GUIのUV欄に最適な数値を入力してくれます。
後は、modeの所で、U方向(横方向)優先か、V方向(縦方向)優先化を選び、
directionの所で、+方向か、-方向かを選びます。
後は、runと押すだけです。

まずは、そのまま実行してみる

実行結果はこちらです。

25枚のplaneを選択していたので、0-1の空間をオーバーしてしまいましたが、等間隔にオフセットする事が出来ましたね。

0-1空間に収める

では、一度Undoで元に戻って、今度はAdjustにチェックしてから実行してみましょう。
実行結果はこちらです。

今度は、UV全体が若干縮小され、0-1空間に収まるようになりました。
配置的には、左詰めの状態になっていますね。

余白を埋める

再度Undoで元に戻り、今度はFitにチェックを入れて実行してみましょう。
実行結果はこちらです。

今度は、0-1の空間内に完全にFitするように縦横の比率も変化しました。

overlapさせる

背景のUV展開をする時に、UVスペースを節約する為に、UVレイアウトをわざと重ねる(overlap)ことがありますね。
そうしたいときは、overlapにチェックをいれ、max shell countの所に、UVシェルの数を入れましょう。
例えば、3 x 4でUVを配置したい時は、Uに3、Vに4、max shell countを12にしてから実行してください。
実行結果はこちらです。

今回は、指定した数分しかUVシェルが作られず、重なっている事が分かります。
UVの重なり方は、選択した順になります。

UVの位置をシャッフルさせる

もしも、UVの重なり方をランダムにしたい場合には、UIのShuffleにチェックし実行してみてください。
実行結果はこちらです。

今度は最初に選択したオブジェクト以外は、ランダムに配置されています。
汚し具合などが異なるテクスチャを用意した上で、好みの結果になるまでトライアンドエラーできますね。
現在は、同一のトポロジーのオブジェクト間でしか動作しませんが、
時間があったら非同一トポロジー間でも動作するようにしてみようと思います。
是非ご活用ください。
少しでもお役に立てば幸いです。

お知らせ


最後にちょっとだけお知らせをさせてください。
デジタル・フロンティアでは一緒に働いてくれるTDを大大大募集中です。
デザイナーの制作補助の為のツール作成や、新しいソフトの導入検証、パイプライン改善など非常にやりがいのある仕事です。
テクニカルディレクターの仕事内容に関して、過去の記事ですがこれらを参考にしてみてください。

テクニカルディレクターというお仕事
アシスタント テクニカルディレクターってどんなお仕事?


プログラミングにまだ自信がないという方でも、どんどん指導育成していきます。
事前に機密保持契約を結んで頂ければ会社見学も可能です。
ご応募をお待ちしております。

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


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

MayaとLocatorとViewport2.0

$
0
0

こんにちは。
開発部の齊藤です

今回は、カスタムLocatorのViewport2.0対応版のサンプル解説しながら
Viewport1.0とViewport2.0で何が変わったのか見ていこうと思います

Maya2015からViewportは2.0が標準となって、Maya2014まで標準だったものはLegacyという扱いになりました
何が変わったのでしょうか?
Locatorからすると、描画の流れが変わります。
Viewport2.0対応していないロケーターは、Viewport2.0上では表示されません。
それは困るので、今回は簡単なサンプルを作りながらViewport2.0対応するにはどこを変更すればいいのか見ていきます

以下の文章では、Viewport1.0と2.0を下記のように定義して記述します

  • Maya2014まで標準だったViewport: Viewport1.0
  • Maya2015から標準のViewport: Viewport2.0


Viewport2.0で変わる事

Viewport1.0と2.0では、大きく2つ変わります。
1つは描画の流れ、もう1つがinitializePlugin, uninitializePlugin関数の内容です

■ 描画の流れの違い

まずはじめに、描画の流れがどのように変わったのかを見ていきます
各関数の詳しいことは後述します。
まずはViewport1.0の、Locator作成から描画までの流れです

次にViewport2.0の描画です

Viewport2.0では、新たにMUserDataクラスとMPxDrawOverrideクラスの2つが加わります
MPxDrawOverrideクラスは描画の処理担当し、MUserDataは描画に必要な情報を格納します
描画の処理は、Viewport1.0ではMPxLocatorのdraw関数1つで処理が終わっていました。
しかしViewport2.0では、描画の事前準備をMPxDrawOverrideのprepareForDraw関数が、実行を同クラスのdraw関数と2つに分かれます
Viewport1.0と2.0では描画する流れが違う為、両方に処理を書く必要があります。

■ initializePlugin, uninitializePluginの違い

次に、initializePluginの登録です。
下記がViewport1.0のコードです

[cpp]
plugin.registerNode(ノードのタイプ名,
ノードのID,
ノード作成関数,
ノード初期化関数,
MPxNode::kLocatorNode);
[/cpp]

次に、Viewport2.0対応のコードです

[cpp]
plugin.registerNode(ノードのタイプ名,
ノードのID,
ノードを作成する関数,
ノードを初期化する関数,
MPxNode::kLocatorNode,
Viewport2.0用の分類文字列);

MHWRender::MDrawRegistry::registerDrawOverrideCreator(Viewport2.0用の分類文字列,
Viewport2.0用の描画クラスを示す固有ID文字列,
Viewport2.0用の描画クラスを作成する関数);
[/cpp]

Locatorを登録するregisterNodeの引数に、”Viewport2.0用の分類文字列”が追加され、
Viewport2.0用の描画クラスの登録が別途必要になります
“Viewport2.0用の分類文字列”によって、ロケータと描画は紐付けられ、Viewport2.0描画時にはこの登録した描画クラスが呼ばれるようになります

登録するものが増えるということは、アンロード時に削除する処理も増える
ということで、uninitializePlugin関数の内容も変わります
viewport1.0の時はLocatorのみ

[cpp]
plugin.deregisterNode(ノードのID);
[/cpp]

viewport2.0では、登録した描画クラスも消す必要があります

[cpp]
MHWRender::MDrawRegistry::deregisterDrawOverrideCreator(Viewport2.0用の分類文字列,
Viewport2.0用の描画クラスを示す固有ID文字列);
plugin.deregisterNode(ノードのID);
[/cpp]


サンプルコード

■ 準備

コードは下記をダウンロードして下さい

    • zipファイルの中に、VisualStudio2010のプロジェクトファイルとソースコードが入っています

■ ビルド

  1. dfCustomLocator.slnを開きます
  2. Configurationを目的のMayaのバージョンに合わせます
  3. ビルド
  4. Mayaバージョン/dfCustomLocator.mll が出来ていることを確認します
    (Maya2015なら、Maya2015/dfCustomLocator.mll)

■ 動かしてみる

  1. 環境変数: MAYA_PLUG_IN_PATHをビルドした、mllがある場所に設定する
  2. Mayaを起動
  3. scriptEditorで、下記のMELを実行する
  4. [code]
    loadPlugin "dfCustomLocator";
    createNode "DFCustomLocator";
    [/code]

Viewport1.0と2.0両方とも描画されれば大丈夫です
Wireframe, Shade mode描画で、表示が切り替わります


解説

プラグインがロードされてからの流れに沿って見ていきます

■ initializePlugin

まずは登録をプラグインをロードした時に呼ばれる部分です

[cpp]
MStatus initializePlugin(MObject obj)
{
/* プラグインロード時に呼ばれる関数 */
MStatus status;
// プラグイン情報
MFnPlugin plugin(obj, “DigitalFrontier.Inc”, “1.0″, “DigitalFrontier.Inc” );
status = plugin.registerNode(DFCustomLocator::typeName,
DFCustomLocator::typeId,
DFCustomLocator::creator,
DFCustomLocator::initialize,
MPxNode::kLocatorNode,
&DFCustomLocator::drawDbClassification);
if(!status){
std::cout << “Failed registerNode” << std::endl;
return status;
}
status = MHWRender::MDrawRegistry::registerDrawOverrideCreator(DFCustomLocator::drawDbClassification,
DFCustomLocator::drawRegistrantId,
LocatorDrawOverride::Creator);
if(!status){
std::cout << “Failed registerDrawOverride” << std::endl;
return status;
}
return status;
}
[/cpp]

DFCustomLocator固有の情報として下記があります

  • ノードタイプ名: typeName
  • ノードID: typeId
  • Viewport2.0用の分類文字列: drawDbClassification
  • Viewport2.0用の描画クラス登録ID: drawRegistrantId

それぞれ下記のように定義しています

[cpp]
MTypeId DFCustomLocator::typeId(0×70200);
MString DFCustomLocator::typeName(“DFCustomLocator”);
MString DFCustomLocator::drawDbClassification(“drawdb/geometry/DFCustomLocator”);
MString DFCustomLocator::drawRegistrantId(“DFCustomLocatorNodePlugin”);
[/cpp]

重要なのが、分類文字列と呼ばれるものを定義しているdrawDbClassification変数です。Locator登録とDrawOverride登録の2箇所で登場しています。
前述の通り、これによりLocatorとDrawOverrideは紐付けられ、Locator描画時に同じ文字列を持ったDrawOverrideが使われるようになります。
この分類文字列は必ず、”drawdb/geometry/”から始まる必要があります。

■ DrawOverrideのCreator, supportedDrawAPIs, boundingBox

[cpp]
MHWRender::MPxDrawOverride* LocatorDrawOverride::Creator(const MObject& obj)
{
/* Viewport2.0描画の初回に呼ばれる
*/
return new LocatorDrawOverride(obj);
}
[/cpp]

CreatorはViewport2.0描画の初回のみ呼ばれる関数です
素直にCreatorの名前の通り、LocatorDrawOverrideをインスタンスして返します

[cpp]
MHWRender::DrawAPI LocatorDrawOverride::supportedDrawAPIs() const
{
/* サポートするAPIを返す
*/
// 今回はOpenGLのみ対応なのでOpenGLのみを返す
return MHWRender::kOpenGL;
}
[/cpp]

supportedDrawAPIsは、LocatorDrawOverrideがサポートAPIを返します
OpenGLなのか、DirectXなのか。Viewportの描画設定は、設定で変更可能です
今回はOpenGLのみ対応なのでkOpenGLで返していますので、DirectX描画に変更すると描画されなくなります

[cpp]
MBoundingBox LocatorDrawOverride::boundingBox( const MDagPath& objPath, const MDagPath& cameraPath) const
{
/* BoundingBoxを返す
このBoundingBoxを用いて、描画するかしないかを決める
*/
return MBoundingBox(MPoint(-1.0f, -1.0f, -1.0f),
MPoint(1.0f, 1.0f, 1.0f));
}
[/cpp]

BoundingBoxのサイズを返します。描画するかしないかに用いられます。
描画サイズが変わるようなものの場合、ここを工夫してあげる必要があります

■ prepareForDraw

いよいよ、描画の前に呼ばれるprepareForDraw関数です。

[cpp]
MUserData* LocatorDrawOverride::prepareForDraw(const MDagPath& objPath, const MDagPath& cameraPath,
const MHWRender::MFrameContext& frameContext, MUserData* oldData)
{
LocatorDrawUserData* draw_data = dynamic_cast(oldData);
if(draw_data == NULL)
{
draw_data = new LocatorDrawUserData();
}
// ワイヤーフレーム表示かどうか
draw_data->is_wireframe = frameContext.getDisplayStyle() & MHWRender::MDrawContext::kWireFrame;
// ロケータは選択されているかどうか
const MHWRender::DisplayStatus displayStatus = MHWRender::MGeometryUtilities::displayStatus(objPath);
draw_data->is_selected = MHWRender::kActive == displayStatus || MHWRender::kLead == displayStatus;

return draw_data;
}
[/cpp]

Viewport1.0との違いが、この描画の準備と実行が分かれていることです
Viewport2.0では、描画メソッドでDGからデータを取得することは、Mayaが不安定になるという理由からここを分けています。

描画に必要なデータは、MUserDataを継承したクラスに格納して返します
この返したデータがそのまま、draw関数の引数に渡されます

関数の引数にあるMUserDataは、前回のMUserDataです
ただ初回は、前回の画無い為、新しく作ってあげる必要が有ります

今回はこの関数内で描画に必要な情報として、
ワイヤーフレーム表示状態か否かと、ロケータが選択されているか否かを判別し、それぞれ変数に入れています
判別に使うのも、Viewport2.0では1.0と違い、M3dViewを用いずMHWRenderを用いて判別します

■ draw , display

いよいよ実際の描画処理です

[cpp]
void LocatorDrawOverride::draw(const MHWRender::MDrawContext& context, const MUserData* userdata)
{
MStatus status;
const LocatorDrawUserData* draw_data = dynamic_cast(userdata);
const MMatrix transform = context.getMatrix(MHWRender::MFrameContext::kWorldViewMtx, &status);
if (status != MStatus::kSuccess)
{
return;
}
const MMatrix projection = context.getMatrix(MHWRender::MFrameContext::kProjectionMtx, &status);
if (status != MStatus::kSuccess)
{
return;
}

glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadMatrixd(transform.matrix[0]);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadMatrixd(projection.matrix[0]);
glPushAttrib(GL_ALL_ATTRIB_BITS);

DFCustomLocator::display(draw_data);

glPopAttrib();
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
[/cpp]

Viewport1.0と違いは、

  • M3dViewのbeginGLとengGLが無い
  • WorldMatrixとProjectionMatrix両方とも取得してセットしてあげる必要がある

というところです。
今回Viewport1.0と両立する為、drawにはViewport2.0で描画するのに必要な準備を書き
実際の描画は、DFCusotmeLocator::display関数内に書いています
こうすることで、Viewport1.0の時はViewport1.0で描画をするのに必要な処理を書いて
DFCusotmeLocator::display関数内を呼び出すことで、目的の描画が出来ます。

最後に

駆け足でしたが、なんとなくViewport2.0におけるLocator描画の流れを掴めましたでしょうか。
今回は、OpenGLのみの対応で書きましたが、DirectXでの書き方も知りたい場合はdevkitのfootPrintNodeプラグインに方法が書かれています
これがLocatorを書いたり、サンプルを読み解く一助になればと思います
ではまた。


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

Viewing all 131 articles
Browse latest View live