‘three.js’ タグのついている投稿

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

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

前回からだいぶ日が経ってしまいましたが、
ミクさんに物理演算を適用すべくチマチマやっておりました。

ただ残念ながら期待したようにはなってくれてません。
よく分からなかったり、うまく行ってくれなかったり、
自分の力不足な所が大きいと思うのですが、
JavaScriptでは荷が重いのかもしれません。
しばらく動作させるとハングアップしたりします・・・(^_^;)

これ以上はうまく行きそうにない感じなので、
中途半端ではありますが、この辺りで〆にしようと思います。
それでも、それなりに得たものがないこともないので、
それをまとめておこうと思います。

前回でも触れましたが、
three.jsで物理演算をやるには、
BulletのJavaScript版であるammo.jsを使いますが、
three.jsからは直接ammo.jsを使いません。
physi.jsを仲介してその機能を利用する形になります。
ammo.jsはWebWorkerを利用して別スレッドで動作するようになっています。
physi.jsおよびphysijs_worker.jsによって、
スレッド間のイベントによる
メッセージ送受信で機能する仕組みになっています。
物理演算は別スレッドでやって、
どうにか性能を稼ごうという仕様なのだと思います。

three.jsでの使い勝手を損なわないように、
physi.jsはScene,Material,Meshを拡張する形で実装されています。
Meshは物理演算で言うところの剛体に対応しており、
Box,Sphere,Capsuleなどの形状をサポートしています。
注意点としては、Meshを生成しないと剛体を設定できないことです。
つまりphysi.jsでは、見える物体と剛体は形状が一致しているという
前提に立っています。

しかし実際には、まずMeshが先にあって、
そこへ抽象的な剛体を設定するという順になると思います。
これはちょっと困りました。
ミクさんはまずモデルの形状があって、
そこへボーンや剛体などが設定されているからです。

そこでシーンを分けることで対応することにしました。
つまり通常のシーンにはモデルを登録し、
もう一つの物理演算なシーンには剛体Meshを登録し、
演算結果を2つのシーン間で相互に反映させるというやり方です。

続いて、
MMDなファイル内にある物理演算な情報が、
物理エンジン側の何に対応しているかを調べました。
MMDで言う所のrigidはその名の通り剛体を示しており、
jointはconstraint(拘束条件)に対応していることが分かりました。
こちらのBulletに関する日本語のページを参考にしました。
なお、MMDは拘束条件として6DofSpringというのを採用していますが、
physi.jsではサポートしてなかったので6Dofで代用しています。
(ammo.jsはBulletの機能を全て網羅しているはずなので、physi.jsを改造すれば6DofSpringも対応可能だとは思います)
問題なのは「衝突フィルタリング」です。
現状のphysi.jsではサポートしてないので要改造となります。
以下のようにしました。

衝突フィルタリングでは group と mask の2つのパラメータを使いますが、
MMD側の数値を変換する必要があります。
そのまま渡してもうまく行ってくれないので、かなり悩まされました(^_^;)
mask はそのままでよいのですが、
MMD側のgroupはビット番号なので、以下のようにシフト演算が必要です。
physics.group = 1 << mmd.group; さて、そんなこんなで どうにかパラメータをセットアップするところまでは行けたのですが、 その後が大変でした。 ときに、 剛体には大きく分けて3種類あります。

・動的(動きのある)剛体

正の質量。毎フレームごとにワールド変換行列が更新される。

・静的剛体

質量ゼロ。衝突はするが、動かない。

・Kinematic剛体

質量ゼロ。ユーザーが動かすことができる。動的オブジェクトを押したりすることはできるが、オブジェクトからは影響を受けない。つまり一方通行。

MMD側も剛体の種類は3種類ですが、
「静的」、「動的」、「動的&ボーン位置合わせ」
ということになっています。
しかし3番目のがよくわかっていません。
質量ゼロなkinematicとは矛盾するので、これには該当しないと思われます。
結局どうすべきか分からなかったので単に「動的」扱いとしています。

物理演算を適用する手順は以下の様な感じになると思っています。

  1. 頂点モーフ処理。
  2. ボーンによるアニメーション処理。
  3. IK処理。
  4. 対応するボーンから取得した位置と回転を静的剛体Meshに反映して物理演算側に通知。
  5. 物理演算の結果、動的剛体Meshの位置と回転を対応するボーンに反映させる。

注意すべきは、剛体は全てグローバルな座標系で扱われることです。
全ての剛体Meshはシーン直下にぶら下がっています。
というかそうしないと物理演算の対象とならないのです。
親子関係とかないので剛体Meshの位置と回転はグローバル座標系となります。
剛体が位置する座標と剛体の重心点は一致するようになっていることにも注意が必要です。
さて、剛体にはそれぞれ対応するボーンが設定されていますが、
ボーンは親子関係を持ち、それぞれにローカル座標系が存在します。
さらにボーンはバインドポーズを基準とした変位量でアニメーションします。
バインドポーズとはモデルの初期の形状のことを指しています。
ミクさんで言えば、両腋を45度くらい開いて立っている状態に該当します。
バインドポーズ時のボーンの回転量は全てゼロとして設定されていますが、
対応する剛体は個々にグローバル座標系における位置と回転値を持っています。
そんなわけで、色々と変換が面倒そうなのでめまいを起こしそうです(^_^;)

物理演算で必要な手順をもう少し詳しくまとめてみます。
モデルを登録する側をシーンA、剛体を登録する側をシーンBとします。

  1. シーンAに属するボーンから取得した位置と回転値を変換して、
    シーンBに属する静的剛体Meshに適用する。

  2. 変更したことをマークして物理演算の更新処理を呼び出す。
  3. 物理演算が更新されたイベントを受けて、
    シーンBに属する動的剛体Meshの位置と回転値を変換して、
    シーンAに属するボーンに適用する。

という流れになります。

この際の「変換」がなんかややこしくて、
正直、自分のやり方はなんか怪しい感じがしています(^_^;)
また、本来なら物理演算の更新処理を呼び出したら、
その結果を待ってから次のステップへ進むべきなのですが、
これだとWorkerを利用して別スレッドにしている意味がなくなってしまうので、
シーン間での変更のやり取りは行いますが、
同期はとってなかったりします(^_^;)
そんなわけでシーンA側とシーンB側でタイミングがあっていません。

さて、
そんなこんなで激しく中途半端な感じではありますが、
ミクさんに物理演算を適用した結果はこれです。
なお、ワイヤーフレームで表示されているのが剛体です。
なんかそれっぽいことをやっているという
雰囲気だけでも感じてもらえれば幸いです(^_^;)

※2013.2.6追記
現状では物理演算の結果をミクさんにうまく反映させることが出来ていません。
また、PhysijsはWorkerを使わない版に改造しています。

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

mytest13

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

 プログラム
 公開日:2012年10月30日 / 更新日:2016年11月10日

前回の続きです。

今回はちょっと趣向を変えた内容となっています。

さて、
three.jsでは様々な形状をgeometryという形で
簡単に生成できるようになっています。
例えばこちらで見られるように、
平面、直方体、球、円柱、
四面体、八面体、二十面体、
ドーナツ形、結び目形など。

ただ、
デフォルトではカプセル形状が無かったので、今回やってみた次第です。
ここで言うカプセルとは、
球を両手でつかんで真ん中から引き伸ばした形状と思ってください。
あるいは円柱の両端に半球をくっつけた形状と言ったほうが分かりやすいかな。

three.jsには、geometryとgeometryあるいはgeometryとmeshを合成できる
THREE.GeometryUtils.merge() という関数が用意されているので、
これを使います。

まず円柱geometryを作ります。
次に上半球と下半球を作り、かつ位置を調整して、
それぞれ順に円柱に合成すればカプセル形状が出来ます。
位置を調整する場合はmesh化しないと出来ないので注意が必要です。

んで、今回作った関数は以下の通りです。
なお、Y軸方向に伸びた形状となるので、
XやZ軸方向に伸びた形状にしたい場合は回転とかしてください。

実際の結果はこんな感じになります。

mytest11

ときに、
ミクさんに物理演算を適用する件ですが、
ほとんど進展はなかったりします。
かな~りチマチマやってたりするので、
時間かかりそうな雰囲気です(^_^;)
とりあえず着手はしているので、経過ぽいのを書いてみます。

MMDではBulletというオープンソースな物理演算エンジンを使っています。
C++で実装されていますが、
これをJavaScriptに直接移植されたものがammo.jsです。
あっさりと「直接移植」ってなってますが、
どうやってC++をJavaScriptへ変換してるんだと思って調べてみたところ、
LLVMという仮想マシンの中間言語を経由して変換しているようです。
つまり、C++ ⇒ LLVMコード ⇒ JavaScript ということのようです。
基本的に、なんでも変換できるようです。凄いですね。
こちらの記事とか興味深いです。
JavaScriptでサポートされてないスレッドとかも可能なのかなぁ。

それはともかくとして、
ammo.jsを利用することになるわけですが、
さいわいthree.jsにはammo.jsを取り扱い易くしてくれる
physijsというプラグインがありますので、
これを使うのが早そうです。

そんなわけでphysijsのソースをちょっと眺めたりしたのですが、
・・・よくわからなかったり・・・(^_^;)
やはりBulletの機能を知らないと、どうもうまくないようです。
ということで、まずはBulletの勉強とかをした方がよさそう。

というのが現況です(^_^;)

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

 プログラム,
 公開日:2012年10月19日 / 更新日:2014年8月27日

前回の続きです。

今回は、toon shading や edge 処理とかをやります。
いよいよシェーダーをいじります。

WebGLというかOpenGLでは、
GLSLというシェーダー言語でプログラミングします。
だいぶ昔、DirectX9の頃に
HLSLをちょっと使ったことはあるのですが、
GLSLは初めてだったりします。

ときに、
シェーダーには大きく分けて、
vertex shader と fragment shader の2つがあります。
後者は pixel shader とか呼ばれたりすることもあります。
それぞれ頂点、ピクセルを対象として演算を行います。

ところで、
前回までのテストでは、
three.js で用意されている MeshPhongMaterial、
いわゆる Phong シェーディングを使っていました。
そこで、
これによって生成されるGLSLコードを改造して、
ShaderMaterialを継承するという形で
toon shading や edge 処理を実装することにしました。
クラス名は MMDMaterial としました。

さて、
toon shading は cel shading とか「アニメ塗り」とか言われる技法です。
光源処理による連続的な陰影を、
テクスチャマップを応用して段階的なものにしてしまうことです。
技術的にはこちらとか参考になります。
MMDでは toon マップ用のテクスチャがプリセットで10個用意されています。

toon は拡散光(Diffuse)に応じた処理をします。
MeshPhongMaterialなGLSLコードでは拡散光の処理は以下の様な感じに、
fragment shader で処理されます。
法線と光線方向との内積から、陰影が計算されます。
※注意:実際のコードとは異なります

これに toon shading を導入すると以下のようになります。
toonMap なテクスチャはあらかじめセットされているものとします。
法線と光線方向との内積から、
テクスチャのV方向を参照し乗算することで段階的な陰影にするわけです。
U方向は参照しませんが、このような手法を使うと、
toon と 輪郭強調(輪郭抽出風味)を同時に出来たりしてお得かも(^_^)

上のような処理を、設定されているlightの数だけ行なって、
(ただし ambient light は除きます)
合計した結果がtoonな拡散光成分となります。

次に edge ですが、これは輪郭線を表現する処理です。
輪郭線を描くにはいくつか手法があるようです。
例えば、いったんオフスクリーンでレンダリングし、
こういった感じのフィルターで輪郭線を抽出して
後処理で合成するといった感じです。
後処理な手法は別途バッファが必要になったりするので、
もっと簡便な手法を採用します。
以下の様に描画を2回に分けて行います。

  1. 普通に描画。
  2. vertex shader では頂点を法線方向に少し引き伸ばし、
    fragment shader では裏面を特定色で描画する。

これだけでそれっぽい輪郭線が表現できます。うまく考えたものですね。
今まで袖の内側やスカートの内側が描画されていなかったのは、
両面ポリゴン化し忘れているわけではなく、実は
edge 処理による裏面描画によって自動的にうまく行くようになってたりします。

MMDでは引き伸ばす量を「エッジ太さ」として
0~2くらいに設定しているようです。
おそらく単位はピクセルで、
輪郭線の太さを固定する手法を用いているものと思われます。
しかし今回は単純にローカル座標系で処理するので、
視点からの距離に応じて太さがスケールされます。
先の数値のままだと伸ばし過ぎることになりそうですので、
重みを掛けて処理します。
やってみた感じでは 0.02 ~ 0.03 程度にするのが良さそうです。
ちなみに通常描画で頂点を引き伸ばすと、
デブっちょな感じのミクさんになったりして面白いです(^_^;)
なお、
視点からの距離に関わらず輪郭線の太さを固定する方法は
まだよく分からないので、後々どうにかする予定です(^_^;)

ということで、
実装するには描画を2回に分けて行う必要があります。
マルチパスな描画ということになります。
mesh全体をシェーダーを切り替えて2回描画すればいいのですが、
これは処理コスト的に問題となります。
まずシェーダーを切り替えるということは、
関連付けられているmaterialも切り替えることになってしまいますので、
meshを再設定するようなことになってしまいます。
またmesh単位で描画を2回行うのもコストが気になります。
というのも SkinnedMesh の場合は、
内部的には複数のmaterialから構成されることになるからです。
前回でも書きましたが、
頂点や面情報で構成されたmeshがレンダリングされる際、
各要素は属するmaterial単位に分割されて処理されます。
materialに関連する情報を切り替えるのはコストがかかるので、
同じmaterialを使うのであれば、切り替えずに描画したいわけです。

ただ残念ながら、
現状の three.js ではこのようなマルチパス描画をサポートしていません。
オフスクリーンなバッファを使う後処理なマルチパスなら、
Composerという機能が用意されているのですが・・・。
仕方ないので three.js 本体を改造することにしました。
具体的には WebGLRenderer 内の renderBuffer() を以下のようにします。
※注意:実際のコードとは異なります。

改造のポイントは、renderMesh を複数回行えるようにして、かつ
前後でmaterialに登録された関数をコールバックできるようにしたことです。
フラグなパラメータ(uniform)を設定するようにすれば、
シェーダー内で if によって処理を切り分けることができるようになります。
つまり、シェーダーそのものを切り替えずに済むようになります。
ちなみに、HLSLでは言語レベルでpassを設定できるので、
もっとスマートに記述できると思います。

ついでに、
シェーダーのプログラムをセットする setProgram() にも手を入れました。
setProgram() ではシェーダーのパラメータ(uniform)を WebGL へ渡す際に、
material がどんなクラスのインスタンスなのかをチェックして、
それに応じたパラメータをセットアップするような作りになっています。
そのためこのタイミングにおいては、
three.js が認識してないパラメータをセットアップすることができません。
そこで以下のように、
material に登録された関数をコールバックして、
カスタマイズしたパラメータをセットアップできるように改造しました。

ということで toon shading と edge 処理が実現できました。
ただ edge はコントラストが強くなるため、ジャギーが気になります。そこで
three.js で用意されているFXAAなアンチエイリアスを後処理で施しました。

あと今回、
material 単位で影の濃さを調整できるようにもしたので、
セルフシャドウを有効にしました。

さて、
なんか文章が長くなりましたが、お待たせしました(^_^;)
bone skinning + IK + morph + toon + edge + self shadow なミクさんの結果はこれです。

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

mytest10

あ、そうそう、あと一言。
今回、GLSLなコードを組み込んだわけですが、
JavaScript には heredoc がないんで、
ソース行を文字列化した配列をjoinするしか方法がないのが
なんか激しくメンドイです。
python みたいな heredoc があればラクなんだけど・・・。

さて、
残りは物理演算。なんか手強そうです(^_^;)
いまだ完全にはうまく行ってないIKもどうにかしたいですね。

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

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

前回の続きです。

頂点モーフによる表情アニメを実現すべく、
シェーダーとかいじってみたりしてました。
だいたい期待した通りな感じになってくれたのですが、
なぜかShadowMapがおかしくなる・・・。

そんな折、
ふとthree.jsのGitHubを覗いてみると、
リビジョンが r51 に上がっている。
しかも skinning + morph に対応したっぽいことが書いてある。
それをどうにかしようと色々いじっていた苦労がぁ・・・(^_^;)

マメにthree.jsのGitHubをチェックする必要がありそうですね。
というか、GitHubで更新あったら通知してもらえると嬉しいかも。
どうやればいいんだろ?

それはともかく、
確かにExampleにこんなのが増えてます。
こちらでは変化の加減を調整できたりしますね。

ということで、シェーダーとかいじらなくても、
MMDなファイルから取り出したmorph情報を
キーフレームにしたがって駆動するだけで良いことになります。
morph頂点の情報はthree.jsでは morphTargets という名前になっています。
中身は頂点データの配列です。
x,y,z なので頂点数×3個の値が並ぶことになります。
さらにこれが表情の数だけ存在するので、
データ量はわりと大きくなることが想像できます。

ところで、
MMDでは変化する頂点のデータだけを保持しています。
具体的にはmorphする頂点の番号と位置オフセット量です。
three.jsで扱うには全頂点の数だけ列挙しなければなりませんが、
ミクさんのモデルは頂点数が9000くらいあるので、
ローディング時間を考えるとファイルサイズが気になります。

そこで、
MMDのmorphデータを素直にjson化して、
それを独自ローダーで morphTargets に展開する手法にしました。

ということで、
bone skinning + IK + morph なミクさんの結果はこれです。

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

mytest9

ちなみに、
頂点や面情報で構成されたmeshがレンダリングされる際、
各要素は属するmaterial単位に分割されて処理されます。
materialに関連する情報を切り替えるのはコストがかかるので、
最適化するためにこのような処理が行われているのだと思います。

これに関連して、morphについて注意すべきことがあります。
それは、頂点morphはmaterial毎に同時に最大8つまでしか行えないことです。
しかも、法線morphも行う場合は頂点、法線それぞれ4つまでとなります。
この制限を超えるような場合は、
重み値のより小さいものを対象外とすることで処理されるようです。

ところで、
morphに関するシェーダーのコードは以下のようになっています。

MMDではオフセット値として扱っているので、
頂点値ではなく、オフセット値として渡せば
計算を減らせると思って、
当初は以下のようにやってました。

morphはこれでうまく行ってくれたのですが、
なぜか ShadowMap がおかしなことになってしまうのです。
原因は不明ですが、余計なことはせずに、
three.jsでの流儀にしたがって、
morphオフセット値は素直に頂点に加算した方が良さそうです(^_^;)

次回は、
toon shading や edge 処理とかをやる予定。

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

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

前回の続きです。

次は頂点モーフによる表情アニメでもやろうとかと思ったのですが、
BoneによるSkinningと頂点モーフが
現状のthree.jsでは共存できないことが分かりました。
シェーダー関連をいじってどうにかしないといけない感じです。
なんか手間ヒマ掛かりそうなので、IKを先にやってみることにしました。
基本的にはBoneのマトリクス操作だけで済むはずですので、
three.jsに対する追加や修正はそんなに大きくならないと思われます。

ときに、
IKとは Inverse Kinematics、逆運動学とか言うそうです。
前回やったBoneによるSkinningは、言わばFK。
Forward Kinematics、順運動学とか言うっぽいようです。

IKという言葉とか、それがどういうモノなのか、
というのはそれなりに知ってはいましたが、
実際にやってみるのは初めてだったりします。

さて、まずは
MMDなファイル内のIK情報を変換してJSONへ追加する作業を行いました。
three.jsオリジナルのJSONLoaderでは、
MMDなIK情報は当然サポートしてないのでLoaderも独自に作りました。
といっても、中身はJSONLoaderとほぼ同じだったりしますが。

つづいて、
IKはどうやってプログラムすればいいのかググって情報収集しました。
そしたら既にWebGLでMMDをやられている方を発見しました!
しかもイチからスクラッチで作ってしまったようです。凄いっす!!
ソースも公開されているので参考になりますね。
アルゴリズム的にはこちらがとても参考になります。
サンプルプログラムが公開されているのもありがたい。
手法としてはCCD-IKとかParticle-IKとかがあるようです。
MMDのIK情報には演算の繰り返し数というのがあるので、
CCD-IKを採用すれば良さそうです。
それからソースコード的にはこちらこちらの第8回が参考になります。

ということでIKやってみました。結果はこれです。

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

mytest8

なお、スポットライトはミクさんを追いかけるようにしてあります。

とりあえず、だいぶそれっぽく踊ってくれるようになりました(^_^)
でも、まだ十分にうまく行ってない感があります。
特に足もとが床にめり込んでしまうのが気になりますね。
IKターゲットを追いかければ、
本来はそういうことにはならないはずだと思うのですが・・・。
床にめり込まないようにするのは、
別途処置が必要なのかもしれません。とりあえず保留としておきます。

あと、
IK解決はそれなりに計算量が多いようです。
ブラウザでプロファイルをとってみると処理時間は多めなようです。
ベクトルや行列演算などを繰り返すので、しょうがない感じではあるのですが
差がある一定以内になったら計算を打ち切る等の
対策をやった方がいいかもしれませんね。
あるいは、Workerスレッドを使って並列にやらせる手もありそうですが、
どこかで同期を取る必要があるので手間かかるかもしれないですね。

次回は表情モーフに挑戦する予定です。
ついにシェーダー周りをいじくることになりそうです(^_^;)