この記事は2014年のJavaFX Advent Calendarの2日目の記事です。
前日の記事はYuichi SakurabaさんのJavaFX Night でバインドの発表をしてきたです。
明日の記事はboochnichさんです。

JavaFX8で3D周りの機能が正式に追加され、JavaFX単体で3Dを扱うアプリケーションが作られるようになったのでMMDモデルを表示することに挑戦してみました。
今まで自分は3Dプログラミングをほとんどやったことがなかったのでいろいろと探りながらの挑戦になりましたが、その過程で感じたことや思ったことについて書いていきます。


MMDモデルとは

最近では個人制作によるモデル以外に、アニメ制作会社やメディア系の学校による公式MMDモデルが配布されるようになるくらいには知名度のある3Dモデルフォーマットになったと個人的に感じているMMDモデルですが、元は樋口氏によって開発されたMikuMikuDance専用の3Dモデルフォーマットです。現在ではPMD形式とより拡張されたPMX形式の2種類が存在し、PMX形式については仕様が公開されているため今回はPMX形式のモデルデータを読み込み表示するプログラムを作って見ました。

動作画面

動作画面 動作画面

これが実際にモデルを表示しているところです。左で表示をちょっとだけコントロールすることができます。右では読み込まれたモデルの情報を確認できます。本当はJava8 Demoの3D Viewerのようにマウスクリックでグリグリ動かせるようにしたかったのですがそこまでは時間とかの都合で実装できませんでした……。ソースコードはGitHubに上げてあるのでいろいろとアドバイスなどもらえると嬉しいです。JARファイルはこちらからダウンロードできます。


プログラムを書いていく上で思ったこととか感じた事

ここから実際に書いていく上でどうだったかについて述べていきたいと思います。

資料が少ない

JavaFX 3Dですが、日本語で書かれた資料が少ないと感じました。球や立方体などの簡単な形状についてはいろいろと情報があるのですが、複雑な形状のモデルを扱うとなるとほとんど情報がなく、Stack Overflowなど海外の情報サイトなどをあたりながら理解を深めていきました。

TriangleMesh

特に厄介だったのはTriangleMeshの扱いです。TriangleMeshは三角メッシュによる3D形状を表現するクラスなのですが、このTriangleMeshの使い方についての情報が少なく非常に苦労しました。今更ですが最初にデモの3D Viewerのソースを読めば良かったんじゃないかと思ってます。TriangleMeshは主に3つの要素から成り立っています。1つ目は頂点情報の配列、2つ目がテクスチャ座標情報の配列、そして3つ目は面情報の配列です。それぞれgetPoints()、getTexCoords()、getFaces()で取得でき、ObservableArrayの拡張クラスが返ってきます。ここで取得したObservableArrayの拡張クラスに対して座標や座標インデックスを追加していくことで3Dモデルを構築できます。
getPoints()で取得できるのはObservableFloatArray型のオブジェクトで、1つの点についてx座標、y座標、z座標の順に格納していく必要が有ります。そのため、このオブジェクトに格納されるデータ数は3の倍数となります。注意点として、JavaFX 3Dはy座標が正の方向が下向きになっているので、何もしていないと構築したモデルが上下逆向きになってしまいます。
getTexCoords()で取得できるのはObservableFloatArray型のオブジェクトで、1つのテクスチャ座標についてx座標、y座標の順に格納していく必要が有ります。そのため、このオブジェクトに格納されるデータ数は2の倍数となります。
getFaces()で取得できるのはObservableFaceArray型のオブジェクトで、1つの三角面について3つの座標インデックスとテクスチャインデックスが必要になります。格納順序は座標1、テクスチャ座標1、座標2、テクスチャ座標2、座標3、テクスチャ座標3となります。そのため、このオブジェクトに格納されるデータ数は6の倍数となります。
テキストだけで書くと分かりづらいので図にしてみます。

仮想的な状態 仮想的な状態
実際の状態 実際の状態

図にしても微妙に分かりづらい気がしますがこんな感じです。これら3つの要素以外にもスムージングの設定などがあります。TriangleMesh関連は今後もいろいろと記事を書いていこうと思います。

SceneとSubScene

JavaFXアプリケーションでは起動時にSceneを作成してステージにセットするのが普通だと思いますが、この時のSceneは2D用であることがほとんどだと思います。深度バッファが無効なため、そのまま3Dモデルを表示しようとすると酷いことになります。

深度バッファが無効の時 深度バッファが無効の時

ちょっとホラーっぽいですね。そのため、SubSceneを深度バッファを有効にして作成し、適当なNode内に貼り付けそこに3Dモデルを表示することになります。この時にアンチエイリアスについても設定できます。最初から3Dのみで画面を構築するならSceneを3D用に生成するのもありだと思いますが、コントローラーなどを置こうとするとSceneとSubSceneで分けるほうが良いと思います。

シェーダーが固定

JavaFX 3DではPhongシェーダーしか利用できません。そのため、デフォルトでトゥーンシェーダー寄りのMMDでの表示結果とはやや異なった雰囲気になります。アンビエントライトの設定などを行うとそれっぽくはなりますが、調整が難しかったです。シェーダーを導入しようという計画はあったそうですが、前回の関Java報告記事で書いたように見送られたそうなので非常に残念です。Materialを自分で実装すればPhongシェーダー以外も使えるのかもしれませんが試してないのでわかりません。

3Dプログラミングの知識がないと引っかかる

ある程度簡単に3Dが扱えるとは思いましたが、それでも3Dプログラミングの知識がない状態だと所々で躓きました。事前に3Dプログラミングについて勉強してから触るほうがすんなり書けるんじゃないかと思います。座標系が独特であるとかJavaFX 3D特有の問題を除けば。

まとめ

JavaFX 3Dを一通り触ってみて感じたのは、少し癖はありますが慣れればそれなりに書きやすいということです。ただ、3Dプログラミングの知識がゼロだと分りにくい場所があるのと、座標系の扱いが一般的でないので事前知識や工夫が必要な部分が面倒でした。
今回作ったMMD表示アプリケーションですが、拡張してモーションなどにも対応させたいと思います。IKやボーンの処理が非常に辛いものになりそうな気がしますが。

この記事を皮切りに、JavaFXや3D関係の記事を書いていきたいと思います。