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

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

 プログラム,
 公開日:2013年6月5日 / 更新日:2014年8月27日

★2014年4月8日更新
画角に対する対応が不十分だった点と、
計算が間違っていた点について修正しました。

今回は、
輪郭線を見直してみました。

輪郭線を描画するには
Sobelフィルターによる手法や、
頂点を法線方向に少し引き伸ばして裏面描画による手法、
とかがあります。

MMDでは後者の手法を用います。
袖やスカートの内側などのポリゴンが貼られてない部分が
自動的に指定色で描画されることになるので都合が良いのです。

fragmentシェーダでは指定された輪郭色を出力するだけなので、
以降の説明ではvertexシェーダについてのみ、要点を記述します。
thickWeightは輪郭線の太さを調整するためのパラメータです。
GLSLなコードは以下のようになります。
あらかじめ gl.cullFace( gl.FRONT ) しておくことに注意してください。

上記の実装では、
モデルのobject座標系で頂点を引き伸ばすので、
視点から遠いと輪郭線は細く、近いと太くスケールされます。

一つのやり方としてこれはこれでアリかと思いますが、
視点からの距離に関係なく輪郭線の太さを固定してみたくなったので、
今回やってみた次第です。

ということで、
投影変換後のclip座標系で引き伸ばせば、
たぶんどうにかなるだろうとやってみたんですが、
なんかうまく行かない(^_^;)

ということでググってみたら
こちらを見つけました。
輪郭線描画は「outline shader」って言うようですね。
投影変換後の法線のxとyを使い、
頂点のZ値を乗算することで太さを固定させるようです。
ふむ、なるほど。
GLSLなコードは以下のようになります

なお、
offsetを求める計算は、
行列演算なのでprojectionMatrixの
要素[0][1]と[1][0]も計算に含めるべきなのですが、
要素の値が常にゼロになるので省略できます。

がしかし、
モデルを視点から遠近移動させてみると
何故か輪郭線の太さが固定されない。
う~む、何か間違っているのか・・・?

さらにググって調べていたら、こちらを見つけました。
eye座標系でのZ値で太さ補正しないとダメでした(^_^;)
clip座標系でのZ値だと変化がリニアにならないっぽい。

ということでGLSLコードは以下のようになります。

よく調べたら、eye座標系での -z は
clip座標系での w と一致することに気が付きました。
ということで更に以下のように最適化できそうです。

おや?
はじめの z が w になっただけですね。
z を w と間違ったのだろうか?
ん~、これで正しいのかちょっと自信が無くなって来ました(^_^;)
合ってると信じましょう(^_^;)

ところで、
gl_Position は同次座標系での位置 (x,y,z,w) を示しており、
-w ≦ x ≦ w, -w ≦ y ≦ w, -w ≦ z ≦ w という関係になっています。
これを w で割ると、
-1 ≦ x’ ≦ 1, -1 ≦ y’ ≦ 1, -1 ≦ z’ ≦ 1 となり、
1辺の大きさが2の立方体な3次元空間に整理されます。
ちなみにこれを正規化デバイス座標系
(NDC: Normalized Device Coordinates)と呼びます。
clip座標系において w で掛け算しておけば、
NDCにおいて w で割り算されるので、変化ないことになります。
つまり太さ固定という観点ではこれで合っていそうな気がします。

残念ながら、これでもまだ不十分でした
画角(fov)が変わると太さも変化してしまうことが分かりました。
Perspectiveカメラの画角を fov、aspect比を a とします。
さらに tan(fov/2) = V とおくと、
ProjectionMatrix[0][0] = 1/aV
ProjectionMatrix[1][1] = 1/V
となります。
細かい計算については割愛しますが、
こちらこちらを参考にしています。
したがって画角によらず太さを固定するには、
以下のようにやれば良さそうです。

さらに以下のように整理できます。

ようやく最終形に辿りつけたようです(^_^;)

さて、
最後に thickWeight の決め方ですが・・・

thickWeight は次のように決めます。
スクリーンあるいはビューポートの高さを VH ピクセルとした場合に、
thickWeight = thickness * 0.5/VH;
となります。thickness(太さ)の単位はピクセルです。
アスペクト比は変換行列で考慮されているので高さでOKなはず。

とか以前は書いていたりしましたが、
これは間違っていたようです(^_^;)
NDCにおける大きさ2の高さに対してVHが対応するので、
thickWeight = thickness * 2/VH;
が正しいことになります。
なんか思い違いして逆数の0.5にしてしまっていたようです。
以前と比べたら4倍も違うことになってしまいますね。
なんで気づかなかったんだろ(^_^;)

ということで、
輪郭線の太さを固定させた結果はこちらです。
太さは2ピクセルに設定してみました。
輪郭線の太さが変わらないので、
モデルを視点に対して遠近移動させると、
相対的に輪郭線の太さが変わったように感じますが、
それは錯覚です(^_^;)

ChromeなどのWebGL対応のブラウザで見てください。PCパワーもそれなりに必要となるかもしれません。あまりにも時間がかかり過ぎるようならば、リロードして再度試してみてください。

mytest23

ところで、
いまだに十分うまく行ってない影の件ですが、
bias値というのをいじってみたら少し改善したような気がします。
このパラメータの存在には気づいていたのですが、
影響はないんじゃないかと思い込んでしまっていました。

カリング無しで深度マップを作るのは、
裏面描画にくらべて描画量が増えることもあり、
好ましくないのではと思っていましたが、
こちらを見るとそうでもないようです。
「両面シャドウ」とか言うっぽい。
実はbiasの方がより重要だったのかも・・・。
う~む、まだ理解不足ですね。

ちなみに、
biasを掛けないとこちらにあるようなモアレみたいのが出てしまうようです。
ただ適正なbias値を求める手法は調べても見つからなかったので、
(そもそも無いのかも?)
手動で調整するしかないようです。

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

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

いままでずっと three.js r51 をべースに作業を進めてましたが、
気がつけばバージョンは r58 まで進んでいました。

毎月中旬くらいに更新されている感じなコトに気づいていましたが、
その都度ベースを更新するがメンドーだったので、
サボってました(^_^;)

three.jsのGitHubの更新ログを見てみると
だいぶ変わって来ているっぽいので、
この際 r58 ベースに更新することにしました。

今回、r51 から r58 にベースを更新する作業をしてみて
個人的に気づいた点は以下のような感じ。

  • ソースのフォルダ構成が変更されている。
  • 基本クラスのAPIに調整が入っている。
  • いくつかのシェーダーは独立したソースに整理された。
  • シェーダーのソースコードに独自の#defineを導入できるようになった。

4番目については、
以前の投稿で独自に改造しましたが、
r58ではより汎用的に使えるようになってたので、
それに対応するように直しました。

2番目については、
Matrix4 に属していたメソッドが Vector3 へ移ったりとか、
メソッド名が変更され機能も微妙に異なっていたりとかして、
それに合わせるための変更にちょっと手間取りました。
内容が最適化されてたり、
実装上の問題点とかが改善されてたりもするようなので、
これはこれで歓迎しておいて良さそうです。
いくつかの旧APIはまだ使えるようになってますが、
console.warning()とかでメッセージが出力されたりします(^_^;)
まだ移行過程な感じなのかもしれません。

前回、PMXやVMDファイルを直接扱えるようにしたわけですが、
今回テストしてて大きなバグを見つけてしまいました(^_^;)
モーションファイルであるVMDには、
フレーム番号を基本にキーをソートしている場合と、
ターゲット名を基本にキーをソートしている場合の2つがあるようです。
この対応が不十分でした。
前回はたまたまフレーム番号でソートしてあったVMDファイルを
テストに使っていたため、この問題に気づきませんでした。

それから、
Shift-JIS から UNICODE への変換について。
前回はnativeな機能で出来るだけ実現しようとしたわけですが、
非同期処理を短時間に多発させるのは
ブラウザに負荷が掛かるような気がしてきたので
こちらの変換ライブラリを利用させてもらうことにしました。
うん、これ便利かも。
素直にはじめからこれを使っておけば良かったかも(^_^)

ところで、
前回の課題であった影の件ですが、
うまく行ってません(^_^;)
色々やってはみたんですが・・・。
う~ん、本家MMDはどうやって解決してるんだろ。
袖やスカートの内側のポリゴンがちゃんと貼られていればなぁ・・・
と愚痴ってみたり(^_^;)
深度マップを作るには基本的に裏面描画でいいんですが、
そうすると袖やスカートの影が抜けてしまう・・・。
しょうがないので以前やったように、、
カリング無しで深度マップを作るようにしました。
一見うまく行っているように見えますが、
副作用で影にならないような所にも陰影が出てしまっています。
地面に近い部分の髪の毛とかが顕著かも。

あと、
描画負荷を少し抑えるために
深度マップ用のバッファの大きさを
2048×2048 から 1024×1024 へ減らしました。
これでも解像度的には問題無さそうなので。

ということでr58ベースに更新した結果はこちらです。
前回と基本的には変わってませんが、少し負荷が軽くなっているかも。

ChromeなどのWebGL対応のブラウザで見てください。PCパワーもそれなりに必要となるかもしれません。あまりにも時間がかかり過ぎるようならば、リロードして再度試してみてください。

mytest22

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

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

今回は、
MMDなデータファイル(PMXとかVMD)を直接扱えるようにしてみました。

three.jsのskinningアニメーションなサンプル
データをjson形式で扱っていたというのを発端に、
Blender向けのjsonエクスポータを研究するなどして
作業を進めてきたという経緯もあり、
(参考までに関連投稿はこちら
いままでずっと
独自作成のコンバータで出力したjsonなデータファイルでやってました。

jsonはテキストファイルなので、
内容をすぐに変更できたりしてデバッグ的に便利なこともあったのですが、
新しい要素に対応するたびにコンバータを変更しなければいけなかったりで、
ワリと手間暇かかっていた感じでした。

コンバータでは複数のモデル対応を想定してなかったこともあり、
いちいち変更するのもいい加減メンドーになって来たので(^_^;)
直接扱えるようにした次第です。

MMDなモデルデータにはPMDとPMXがありますが、
PMXを扱うようにしました。
PMXはPMDから変換できるということと、
形式の詳細が公開されているというのが理由です。
両方の形式に対応するのがメンドーというのも大きいけど(^_^;)
ただし、
PMX固有の拡張要素にはあまり対応してません。
現状は、PMDの代わりっぽく使っている感じになっています。

バイナリを扱うことになるわけですが、
DataViewを利用するとワリとあっさり行けますね。
苦労したのが文字コード変換。
UTF16やUTF8は自前でどうにかしたのですが、
SHIFT-JISがちょっと厄介でした。
変換ライブラリとか公開されているのがあるので
素直にそれを使えば良いのですが、
nativeな変換関数とか無いのが何か腑に落ちないので(^_^;)
ググって見つけたこちらの手法でやってみることにしました。
正直ちょっと回りくどいのがアレですが、
しかも非同期にならざるをえないので扱いにくいのですが、
敢えてこのやり方にこだわってみました(^_^;)

そんなこんなで、
ローダー系やモデル構築の処理に大幅に手を入れるハメになりましたが、
複数のモデルに対応できるようになりました。
ただ、Chromeのタスクマネージャとかで見てみると
なんかメモリの使用量が大きく増えてる感じなのがちょっと気になる。
自前でバイナリをデコードするようになったからかもしれないんですけどね。

ということで3体ほど表示させたみたのがこちらです。
流石に処理が重くなってたりします。
その旨ご了承ください(^_^;)

ChromeなどのWebGL対応のブラウザで見てください。PCパワーもそれなりに必要となるかもしれません。あまりにも時間がかかり過ぎるようならば、リロードして再度試してみてください。

mytest21

ところで、
影の処理がまだ不十分だったりします。

シャドウマップに関しては、
以前に直ったようなことを書いたのですが、別な問題が起こっていました。
シャドウマップでは光源を仮想視点としてみた時の、
ポリゴンの「背面」描画によって深度マップを作成するのですが、
モデルの袖やスカートの内側にはポリゴンが貼られてなかったりする関係で
不自然な影となってしまっています。
「前面」描画でやると一見直ったようにみえるのですが、
本来影にならない所にも影が落ちてしまっていました。
袖やスカートに対して特別な処理をすれば
改善できることは分かっているのですが、
データ側からはどれが袖やスカートなのかが判別できなかったりします。
でも本家MMDではうまく出来ているので、
何か見落としているのかもしれません。

また本来なら、
カメラのパラメータによって設定される視錐台の
全体を覆うように深度マップを作るべきですが、
(より厳密には、視錐台内に影を落とす可能性のある全てのオブジェクトを覆うように)
現状では、
メインとなるモデルを中心に手動で設定した範囲内にしか影が落ちてません。
(その反面、影の解像度が上がってたりするのですが・・・)

これらは今後の課題ですね。

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

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

今回は、
MMDのモーションファイルであるVMDでサポートしている
カメラおよびライトのモーションに対応してみました。

実装は割りとあっさり行けたのですが、
テストするのに良さげなデータを見つけるのにちょっと手間取りました。

結局、
PMDEditorに付いているPmmSplitterプラグインを使って、
MMDに同梱されているPMMファイルから
カメラとライトのモーションを抽出することで対処しました。

また、
ライトのモーションに対応した関係で、
今回ライティングを調整しました。
Ambientライトを少し暗くして、
DirectionalライトのIntensityを上げてみました。
そのため今までのに比べると少しメリハリが強くなっていると思います。

ということで、
カメラ&ライトモーションに対応したミクさんはこちらです。

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

mytest20

ところで、
VMDファイルでは、
以下の様なモーションをサポートしているわけですが、

  1. ボーンSkinning
  2. 表情モーフ
  3. カメラ
  4. ライト

上記4種類が全て入っているようなVMDは無くて、
以下のどちらかのパターンな感じっぽい気がします。

  1. ボーンSkinning、表情モーフ(モデルに依存する)
  2. カメラ、ライト(モデルに依存しない)

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

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

MMDでサポートされてるけど
まだ対応してなかったスフィアマップをやってみました。

スフィアマップというのは、
テクスチャの貼り方の技法の一つで、
環境マッピングとか呼ばれてます。
擬似的な周囲環境の映り込みを再現する手法で、
金属のような光沢とかを表現できたりします。

環境マッピングの実装方式には、
球状マップ(sphere map)やキューブマップ(cube map)があります。
three.jsのサンプルである
こちらこちらでその結果をみることができます。

three.jsのサンプルで見れたりするくらいなので、
割りとアッサリ導入できると思ってたんですが、
webGLレンダラーではスフィアマップはまだ実装されてませんでした・・・。
canvasレンダラーでは実装されているのですが、
webGLレンダラーではキューブマップのみとなってました。

ということで、
自前で実装することにしました。
どうやればいいかググってみたら、
これ(その1)これ(その2)とかみつかりました。
実現手法は一つでは無いようですね。
どっちがいいんだろ。
2つともやってみることにしました。

なお、
MMDのスフィアマップではマッピング時の演算として
乗算および加算をサポートしています。
今回は乗算のみの対応となっていますが、
fragment shader の最後の乗算を加算にするだけのことですね。たぶん。

GLSLなコードは以下の様な感じになりそうです。

その1)

その2)

つづいて、three.jsを改造します。

three.jsで使われているGLSLなコードをダンプしてみると
こんな感じになっています。
レンダラーやマテリアルのプロパティに応じて、
例えば

#define USE_SKINNING

とかが、
GLSLのソースコードに付加される仕組みになっています。
ただ、現状の実装では
three.jsが認識しているプロパティでないとうまく行きません。
そこで、マテリアルに

material.vertexShaderPrefix
material.fragmentShaderPrefix

というプロパティを追加して対応することにしました、
改造したtheee.jsのコードは以下のようになります。
独自の #define とかをコードの先頭に追加できるようにしてしまうわけです。

さて、
実際にやってみた感じでは、
その1)でも、その2)でもそれなりにイイ感じになったのですが、
MMDのスフィアマップ用に使用しているテクスチャとの相性が良さそうなのは
その1)っぽい気がしましたので、そちらを採用してみました。
ということで、
メタルっぽくなったミクさんはこちらです。

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

mytest18