‘MMD’ タグのついている投稿

three.jsで遊んでみる(14)

 プログラム,
 公開日:2013年3月8日 / 更新日:2013年10月24日

この回で、
拘束条件とボーン位置合わせな剛体に関して
ナンかうまく行かない感じのことを書きましたが、
どうすれば改善できるかが少し見えて来ました。

それは、
拘束条件の BT_CONSTRAINT_STOP_ERP パラメータの調整です。
以前試みた時は、極端な値でやっていたせいか
そのことに気づかなかったようです。
ERP というのは Error Reduction Parameter の略で、
シミュレーションのステップごとに発生する計算誤差を
減少させるためのパラメータのようです。
現在値から目標値への収束速度に関わっているのではないかと思い、
これを調整してみたら、
データ側を変更しなくてもスカートの挙動が以前より良くなった感じです。

このパラメータの設定可能範囲は 0~1 のようです。
拘束条件で軸がロックされている状態で
最大の 1 に設定すれば誤差ゼロで即収束するのかと思い、
やってみたんですが、
完全に追従させることはできませんでした。

むしろ、
大きくするとかえっておかしくなってしまう感じです。
今回いろいろやってみた感じではデフォルトの 0.2 に対して
0.4 ~ 0.5 くらいが良さそうな感じでした。
モーションの激しさとか重力とかに応じて、
その都度調整するようにするといいのかもしれません。

ちなみに、
BT_CONSTRAINT_STOP_ERP の他に
BT_CONSTRAINT_ERP というのがありますが、
いまいち違いは分かってません。
ただ 6DofSpring な拘束条件では使われてないようです。

ときに、
今回はもう一つ。

three.js での Bone Skinning なアニメーションにおける
キーの補間は基本的に線形ですが、
MMDでは3次ベジェです。
やるのをすっかり忘れてました。
VMDファイルにカメラやライトのモーションがあったことも・・・(^_^;)

ということで独自の実装に変更する作業を行いました。
MMDでは4つの制御点による3次ベジェ(Cubic Bezier)曲線を
利用して2つのキーの補間をしています。
数式はこんな感じです。
MMDでの3次ベジェ補間では、
4つの制御点のうち、始点は(0,0)、終点は(1,1)相当に固定されています。
ちょうど、CSSにおけるtransition-timing-functionと同様です。

曲線の始点が t=0 終点が t=1 として、
曲線の点は x = B(t), y= B(t) と表されますが、
補間では y=F(x) の形にする必要があります。
つまり、媒介変数 t を消去した式にする必要があります。

が・・・。
実際にやってみるとこれが難しい。
というか3次方程式ってどうやって解くの? って感じ。
もはや自分の数学スキルでは無理ゲーなので(^_^;)
どうやってるんだろって思って、
こちらこちらのソースを参照してみたら、
ニュートン法や2分法で求めてました。
どうやら数値計算的手法でないとうまく解けないっぽい。
道理で難しいわけだ(^_^;)

今回、
独自の実装に変更してしまうので、
three.jsでのJSONなデータ形式との互換性を取る必要はないのですが、
とりあえず後方互換の形にしておきました。
キーにMMDの補間情報を追加しただけですけどね。

あと、
補間時の注意点として
2つのキーをK0,K1とした場合に、
K1側に付加された補完情報を使うようにします。
実は当初K0側でやっててハマりました(^_^;)
微妙に挙動が変になったりしました。

ということで、
スカートの挙動を改善&ベジェ補間を導入なミクさんはこちらです。

諸般の事情でちょっとローディングに時間がかかりますが、しばらく待ってみてください。なお、ChromeなどのWebGL対応のブラウザで見てください。PCパワーもそれなりに必要となるかもしれません。あまりにも時間がかかり過ぎるようならば、リロードして再度試してみてください。

mytest17

three.jsで遊んでみる(13)

 プログラム,
 公開日:2013年3月3日 / 更新日:2013年10月10日

★2013年5月8日訂正
この回にはかつて、
シャドウマップと面のカリングに関する内容が書いてあったのですが、
なんか間違っていたので削除しました(^_^;)

デプスマップを作る際にカリング無しでやってみると、
床に落ちる影がうまく行くように見えていたため、
この方法で良いと思い込んでしまっていました。

正しくは、
こちらのページで解説されているように、
「背面ポリゴンを描画するようにしているのは,影の領域がオブジェクトの(光源に対する)背面から始まるようにするためです.こうしないと影の領域がオブジェクトの光の当たっている面と重なってしまい,影が日向の領域にはみ出てしまうことがあります」
ということのようです。

こちらのページの図も参照すると理解しやすい感じです。

要するにおもての面をカリングしてデプスマップを作るのが正解、
ということのようです。
three.js でのデフォルトのままで良かったのですね。

ただ、
何故か結果が変なんですよねー。
ミクさんのモデルは、スカートや袖の内側とか
ポリゴンが貼られてない所があるため、
これが影響しているくさいんですが、
どうもうまい解決法が思いつかない・・・嗚呼

前回の続きです。
セルフシャドウがオカシイ感じの件ですが、
改善できました。

アレのせいかも、
と薄々感じてはいたのですが、
やはりアレのせいでした(^_^;)

順を追って書いてみます。

さて、
ミクさんのモデルは、スカートや袖の内側とか
ポリゴンが貼られてない所があるため、
普通にレンダリングするとその部分が透けてしまいます。
その状況はこちらで確認できます。
ただ、こちらで書いたように
edge処理による裏面描画で自動的にうまく行くようになっています。

ところがシャドウマップではこのことが問題となっていました。
光源から視た深度マップを作成する際、
モデルの裏面は除外(Culling)して処理します。
視点から見えない裏面を対象にするのは無駄なので当然な処理なわけですが、
ミクさんのモデルの場合はポリゴンが貼ってなくて
透けてしまう部分があるため
不自然な深度マップが出来てしまっていたのです。

そこで、
THREE.ShadowMapPlugin を改造して、
オブジェクト単位にCullingを無効にできるようにしました。
本来は対象にしなくても良い「面」についても描画することになるため、
処理的には冗長になってしまうのですが、仕方ありません。
以下の様な感じに改造しました。

ちなみに、

material.side = THREE.DoubleSide;

とやるとモデルのマテリアルを両面ポリゴン化することができるので、
これをやればCulling無効と同じになるはずと思って
やってみたのですがうまく行きません。
調べてみたらShadowMapPluginでは深度マップを生成するために、
専用のマテリアルが使用されていました。
(普通に考えれば、こういう実装になるよね)
つまりモデル側のマテリアルは無視されることになるので、
両面ポリゴン化しても効果はないわけです。
いいアイデアと思ったんですが、
そうは問屋がおろさない、ですね(^_^;)

ということで、
改善されたセルフシャドウなミクさんはこちらです。

諸般の事情でちょっとローディングに時間がかかりますが、しばらく待ってみてください。なお、ChromeなどのWebGL対応のブラウザで見てください。PCパワーもそれなりに必要となるかもしれません。あまりにも時間がかかり過ぎるようならば、リロードして再度試してみてください。

three.jsで遊んでみる(12)

 プログラム,
 公開日:2013年2月24日 / 更新日:2013年10月24日

前回、ちょっと書いてましたが、
physi.jsを使わない実装に整理&最適化してみました。
ソース的にはなんかスッキリできたように思います。
あとシェーダーのコードは外部ファイルを読み込むように変えました。
この方が修正とかしたい時に扱いやすいので。
(JavaScriptにはheredocが無いのが激しく残念・・・)
なお、
処理的には前回と基本的には変わりませんが、
PAUSEできるようにしてみました。こちらです。

諸般の事情でちょっとローディングに時間がかかりますが、しばらく待ってみてください。なお、ChromeなどのWebGL対応のブラウザで見てください。PCパワーもそれなりに必要となるかもしれません。あまりにも時間がかかり過ぎるようならば、リロードして再度試してみてください。

mytest15

次は、
セルフシャドウのオカシイ感じをどうにかしたいかも。
今まで見て見ぬふりして来ましたが・・・(^_^;)
three.jsの機能をまんま使っているんですが、
うまく使いこなせてないのかもしれません。

three.jsで遊んでみる(11)

 プログラム,
 公開日:2013年2月15日 / 更新日:2013年10月24日

うまく行かずに半ば諦めて、
これで〆にしますとか前々回で書きましたが、
やり方を変えてみたら良い感じになってくれました。
微妙にまだうまく行ってない気もしますが(^_^;)
とりあえずは目標に達せたかと思ってます。

それでは順を追って書いてみます。

まずは、
physi.jsを web worker を使わないバージョンに
改造することから始めました。
workerによって別スレッドで動かすと、
性能的には恩恵を受けられるのですが、
主スレッドとの同期が取れなくなります。
これが問題なのかも? と思ったのです。
完了を待ってやるようにすれば同期は取れるのですが、
もはや別スレッドにする意味が無くなります。
workerを使うとデバッグもやりにくい感じなので、
思い切って改造することにした次第です。
一方で使い勝手は変えたくなかったので、
イベントによるメッセージ送受信を
関数呼び出しの形に単純に置き換えました。
内部的には冗長的なのが気になるけど、
作業量が少なくて済むのでこれで良しとしました。

さて、
これでやってみたけど殆ど変わらず・・・。
たぶんそんなことになるだろうと予想してたけど(^_^;)

やはり、
物理演算の結果のワールド座標系をボーンに適用するために、
ローカル座標系へ変換することに無理がありそうです。
素直にワールド座標系で適用することにしました。
ただ残念ながらthree.jsのレンダラでは、
シーンの座標変換とレンダリングを一つの関数で行なっているため、
ワールド座標変換後に処理を入れることができない仕様です。
座標変換とレンダリングは別関数にしておいた方が、
使い勝手的に良いんじゃないかと個人的は思ってたりします。
しょうがないのでthree.jsに改造を入れました。
以下のように、
座標変換後かつレンダリング直前にコールバックできるようにしました。

この施策の結果、
だいぶ良い感じになってくれました。
はじめから素直にワールド座標系でやっておけば、
前のように苦労せずに済んだかも・・・嗚呼(^_^;)

ところで、
質量ゼロなstatic剛体をユーザーが動かす場合は、
kinematic剛体にするべきなことを知りました。
ここにそんなことが書いてあったのに、
すっかり見過ごしてました(^_^;)

さて、
でもまだなんかオカシイ感じ。
一つは、拘束条件で軸をロックしているはずなのに
何故か引き伸ばされるように動いてしまうことです。
例えるならAとBが握手していて、
AがBを引っ張ったら即座にBは引き寄せられるはずなのに、
何故かゴムのように伸びた後に引き寄せる感じなのです。
海賊なゴム人間をなんか思い浮かべてしまいます(^_^;)
※Aをkinematic剛体、Bをdynamic剛体とした場合。

拘束条件というのはこんな感じになっています。
で、以下のような設定ができるようになっています。

Lowerlimit == Upperlimit -> この軸はロックされます。
Lowerlimit > Upperlimit -> この軸は自由に動けます。
Lowerlimit < Upperlimit -> この軸の可動域は制限されます。

ところで、
物理エンジンBulletのサンプルを実行すると、
剛体をクリックして動かせる操作ができます。
クリックした点をアンカーとして、
剛体をゴムのように引きずることができます。
一時的に拘束条件を設定してやってるんだろうと思い、
何かヒントがないかとソースを探ってみました。
そしたら以下のようなコードが見受けられました。

この中で、
BT_CONSTRAINT_STOP_CFM、BT_CONSTRAINT_STOP_ERP
に注目しました。
他に、BT_CONSTRAINT_CFM、BT_CONSTRAINT_ERP というのがあります。
ここによると、
CFM は Constraint Force Mixing の略で、
拘束の強さを決定するパラメータ、
ERP は Error Reduction Parameter の略で、
シミュレーションのステップごとに発生する計算誤差を減少させるためのパラメータだそうです。
ロボット工学とかで使われるようなパラメータのようです。

これがなんか影響しているのではと思い、
いろいろ設定を変えてみましたが、
相変わらずゴムのように伸び縮みするばかりで、
ロックして完全に追従させることはできませんでした。
上記ソースでは、拘束条件が
6DofConstraintなのでバネの要素は無いはずなのですが・・・。
片方がkinematic、片方がdynamicの場合は、
拘束条件のパラメータ操作だけでアンカーにロックさせることは、
現状ではできないのかもしれません。

そんなこんなで悩みつつ、
関連情報をググってたら、「MikuMikuDroid」というのを見つけました。
MMDをアンドロイド端末で再現するというものです。
なかなか評判も良いみたいです。
しかもソースが公開されているではありませんか!!!
さっそくsvnでチェックアウトして参照してみました。
いや~、これは大いに参考になります。
もっと早くこれに出会っていたらラクだったのに、
と激しく思いました(^_^;)
3D描画はGLES2.0を使ってスクラッチで書かれているようです。
スゴイっす。
JNIを使ってBulletをnativeなコードで動かせるのは、
性能的にちょっと羨ましい気がします。

で、
ソースを読んで、一つの疑問が氷解しました。
MMDの剛体には、
「静的」、「動的」、「動的&ボーン位置合わせ」
の3種類がありますが、
3つ目のがよくわからないまま、
「動的」と同じ扱いにしていました。
物理演算の結果をボーンに適用する際、
剛体の位置と回転値をボーンに設定しますが、
回転値のみを設定して位置はボーンのままにするのが
「動的&ボーン位置合わせ」
ということが分かりました。
まさに文字通りでした(^_^;)
実はこの処理が、
拘束条件のアンカーにロックさせることに相当するのです。
拘束条件のパラメータ調整ではどうにもゴムのように伸縮してしまうので、
無理やり合わせてしまえ、なのだと自分は理解しました。
ロックして追従させるにはこの手法しか無いのかもしれませんね。
一方、
「動的&ボーン位置合わせ」というのは、
dynamic剛体にボーンの位置を適用することでもあります。
dynamic剛体の位置を直接変更するのは本来はやるべきではないのでしょうが、
これしか手がなさそうな感じです。
ただこれをやった場合に、
物理エンジン側に通知とかの処置が必要なのかもしれませんが、
よく分かっていません。

さて、
そんなこんなでだいぶ改善されてきたのですが、
スカートの挙動がいまだ変な感じなんです。
どうにもうまく行かなくて苦労したのですが、
いろいろ試行錯誤した結果、結局データを調整することで対処しました。
スカートの物理演算は、8つの箱型の剛体から成っています。
うち前部の2つが「動的&ボーン位置合わせ」で、
残りは「動的」で設定されているのを、
スカート全てを「動的&ボーン位置合わせ」にしました。
これでそれっぽく動作するようになりました。
(実はまだちょっと変なところがあるけど(^_^;))
こうしないとうまく行ってくれないのは、
「動的&ボーン位置合わせ」に対する処理が
十分にうまく行えてないせいなのかもしれません。
今後の課題ですね。

気づいたら
なんか長文になってしまっていました(^_^;)
ということで、
ミクさんに物理演算を適用した結果はこちらです。
画面の右上の「rigid body」をチェックすると剛体が表示されます。

諸般の事情でちょっとローディングに時間がかかりますが、しばらく待ってみてください。なお、ChromeなどのWebGL対応のブラウザで見てください。PCパワーもそれなりに必要となるかもしれません。あまりにも時間がかかり過ぎるようならば、リロードして再度試してみてください。

mytest14

その他、思ったことや、
対処したことなどを備忘のために列挙しておきます。

  • C++なBulletのJavaScript版のammo.jsを使うわけですが、
    ammoのオブジェクトをnewした後に不用になったら、
    明示的に Ammo.destroy() しなければなりません!!!
    C++の流儀に従うことになるわけですな・・・(^_^;)
    正直メンドイので、初期化や生成、追加系の操作では後始末はやってません。
    ただ設定系や更新系は対処しておかないと、
    その内メモリ不足でハングアップします(^_^;)

  • 重力のデフォルト値は (0,-10,0) だが、
    このままだと不自然な感じなので (0, -9.81*10, 0) とした。

  • 剛体はある一定時間変化が無いとスリープするようになっている。
    明示的に起こすようにしても良いのだが、
    面倒なので setSleepingThresholds(0,0) とやって、
    スリープしないようにした。

  • 座標系が、本家MMDは左手、OpenGL、Bulletでは右手なので、
    自分はあらかじめデータを左手系から右手系に変換しているが、
    MikuMikuDroidではカメラ行列やカリングの向きを調整するなどして、
    データは左手系のままで使っている模様。
    物理演算系のパラメータもBulletにそのまま渡しているようなのだが、
    左手から右手系へ変換してないようにみえる。ナゼこれでうまく行っているのかは不明。

  • ここによると、kinematic剛体にした場合は setActivationState(DISABLE_DEACTIVATION) すべきなことが書いてあるが、これをやるとナゼか剛体が固定されてしまう。MotionState に関係があるようなのだが、いまだ使い方がよくわかってない(^_^;)

  • physi.jsを改造して使っているが、今回のような場合はmesh前提な仕様だと扱いにくかったり、冗長的になったりしてしまう部分があるので、将来的には剛体の描画と物理演算は分離させ、physi.jsを使わない実装に変更したい。

参考までに、
左手系から右手系への変換は次のようにやってます。

頂点や法線: (x,y,z) -> (x,y,-z)
オイラー角: (x,y,z) -> (-x,-y,z)
クォータニオン: (x,y,z,w) -> (-x,-y,z,w)
あと三角形の頂点並び順も逆にする。

three.jsで遊んでみる(10)

 プログラム,
 公開日:2012年12月9日 / 更新日:2013年9月13日

ミクさんの足が床にめり込んだりして、
IKが何か変だった件ですが、
バグってました(^_^;)

端的に言うと、
対象がベクトルなので回転な行列を使うべき所を、
位置&回転な行列を使ってました(^_^;)

要するに、
対象が「向き」なので「位置」な情報は要らないわけです。

まぁ、
最終的にはワールド行列でうまく行けるように整理できたけど。

ともかくも、
だいたい期待したようなIKになってくれました。
まだ微妙に違和感があるような気がしないでも無いですが、
少なくとも床にめり込まなくなったので良しとしましょう(^_^;)

ということで直しておきました。
過去のこちらの投稿からどうぞ。