nmi.jp Twitter → @tkihira
生WebGL入門:初音ミクの美麗3Dモデルを表示する(前編) 生WebGL入門:初音ミクの美麗3Dモデルを表示する(後編)

生WebGL入門:初音ミクの美麗3Dモデルを表示する(中編)


2014-12-04
Takuo Kihira

前の記事では、大変長いコードを書いて、やっとポリゴンを1枚だけ表示させることに成功しました。次に目指すは、ミクさんモデルの表示です。

前の記事はこちら→ 生WebGL入門:初音ミクの美麗3Dモデルを表示する(前編)

モデルデータの読み込み、表示

まずは前の記事で準備で用意した、ミクさんモデルのデータをJavaScriptで読み込みます。前の準備ではmiku.objmiku.mtlの2つのファイルが出力されました。なんとなく眺めればわかるように、OBJファイルの方は頂点などのデータ、MTLファイルの方が色などのデータとなっています。まず、OBJファイルのデータを読み込んで、その内容を出力してみましょう。

OBJファイルのフォーマット

OBJファイルのフォーマットは、ネットで少し調べればこちらのサイト等で詳しく説明されていますが、基本的には頂点データ、テクスチャデータ、法線データ、ポリゴン定義の集まりです。

v 頂点x座標 y座標 z座標
v 頂点x座標 y座標 z座標
…
vt テクスチャx座標 y座標
vt テクスチャx座標 y座標
…
vn 法線ベクトルx成分 y成分 z成分(今回のファイルにはなし)
vn 法線ベクトルx成分 y成分 z成分
…
f 頂点座標index/テクスチャ座標index/法線ベクトルindex (最低3個以上、今回のファイルは全部3個)
f 頂点座標index/テクスチャ座標index/法線ベクトルindex
…

頂点座標、テクスチャ座標、法線ベクトルはそれぞれ登場した順番に番号が1から振られ、その番号が「f」のポリゴン定義で使用されます。fの指定で、頂点座標以外は省略することが出来ます(今回は法線データがありません)。今回のポリゴン定義(fによる定義)の要素はすべて3個(=すべて三角形1個)になっていますが、4個以上ある場合は次のように解釈されます。

前の記事では三角形を1個だけ表示しましたが、今回はこのミクさんのモデルのOBJファイルに含まれる22961個全ての三角形を出力するのが目的です。では実際にOBJファイルのパーサーを書いて、三角形のデータを読みこんでみましょう。

OBJファイルパーサー

OBJファイルの読み込みのソースコードは、objparser.jsのobjParse関数で実装しました。

https://github.com/tkihira/webgl-miku-sample/blob/e5198627565a11f8f863876387651a7989906615/objparser.js

基本はXHRで取ってきたファイルを行ごとに分解し、その行の情報を単純にJavaScriptのオブジェクトに格納しているだけの簡単なコードです。この単純なパーサーではもしかしたら読み込めないOBJファイルがあるかもしれませんが、今回読もうと思っているミクさんのデータは問題なく読み込めました。

なお、今後使うことも考えて vt や usemtl などのデータも取得するようにしています。

モデルデータの加工

上のソースコードのcreateGLObject関数で、パースしたモデルデータからWebGLで使えるように加工しています。最終的にすべきことは、今回読み込んだモデルのデータをTypedArrayに変換し、Vertex Shaderに送り込んでやることです。ところが、今回のミクさんのOBJファイルには頂点の座標データ(v)はあるのですが、法線ベクトル(vn)のデータはありません。法線ベクトルのデータが無いと光源と面の角度に応じた明るさの変化を表現できなくなり、下記のようなのっぺらぼうのモデルになってしまいます。

そこで、既に存在するポリゴン三角形の頂点座標のデータから法線ベクトルを近似的に生成することにしましょう。とりあえずは、三角形の面に対して垂直なベクトルを法線ベクトルとすればよさそうです。三角形の各頂点がわかれば、それに垂直なベクトルはベクトルの外積(cross)によって求めることが出来ます。

glMatrixの行列計算ライブラリには三次元のベクトルの外積もあるので、それを使って法線ベクトルを定義します。前の記事のポリゴンと同じく、今回も同じ三角形の3頂点全てに同じ法線ベクトルをセットすることにします。これで、すべての頂点について座標データと法線ベクトルを準備することが出来ました。

モデルの表示

これで全ての準備が終わりました。createGLObject 関数で用意した頂点座標データと法線ベクトルを、 Vertex Shader に正しく流し込むだけでミクさんが表示できるようになります。ここまでのソースコードはこちらです。

https://github.com/tkihira/webgl-miku-sample/tree/e5198627565a11f8f863876387651a7989906615

miku.js の loadBuffer 関数にて、前回ハードコーディングで渡していた頂点座標データと法線ベクトルデータを createGLObject の出力に変えています。そして送り込む頂点の数も、前回の3個から22961枚×3個の68883個と大幅に増えています。しかし変更はこれだけで、ソースコードの内容は前回とほとんど変わりません。このコードを実行した結果がこちらになります。

[iframe src="http://nmi.jp/webgl/miku/miku_polygon/" width="550" height="550"]

この通り、ミクさんがブラウザ上に登場しました!どうでしょう、一気に3Dっぽくなったと思いませんか?

Depth Test

ミクさんは無事に表示されたのですが、なぜ後ろのポリゴンが表示されていないのでしょうか。頂点データを送るときには前後関係などを考えずに一括で送っているにも関わらず、出力結果ではうまい具合に背面の画素は描画されず、綺麗に隠れています。実は、前の記事にてさらっと差し込んだ次のコードのおかげで、後ろを描画しないことが実現出来ているのです。

[code]
gl.enable(gl.DEPTH_TEST);
[/code]

WebGLはCanvasにミクさんを描画している裏で、Depth Bufferと呼ばれるメモリ領域にピクセルごとのZ値を保存しています。WebGLは、上のコードでDepth Test機能をオンにしておけば、いざピクセルを書き込もう!という直前のタイミングでその位置のDepth Bufferをチェックします。もしそのピクセル位置のDepth Bufferに自分のZ値よりも後ろ側のZ値が書き込まれていた場合には、WebGLはCanvasにピクセルを描画し、Depth Bufferに新しいZ値を書き込みます。もしそのピクセル位置のDepth Bufferに既に自分より手前の位置のZ値が書かれていた場合には、WebGLはCanvasに何も書き込みません。

このようにWebGLではピクセル単位でどちらが前かを確認しているので、極めて正確に前後関係を描画することが可能になっています。もしDepth Testをオフにしたままミクさんを表示しようとすると、(一応閲覧注意)極めて壊れた状態でミクさんが描画されてしまいます。

モデルのスムージング

とりあえずミクさんが表示出来るようになりましたが、上のミクさんはいかにもポリゴンポリゴンしていて、まるでバーチャファイター1のような、3D黎明期のゲームのようです。これはこれで味のある表現だと思いますが、しかしやはり今回のように女の子を出力するときにはもっと丸みのある表現にしてあげたいところですよね。

法線ベクトルと光のあたり方

なぜモデルがこんなにカクカクしているかというと、同じポリゴンの頂点すべてに同一の法線ベクトルが設定されており、複数のポリゴンが交わる頂点であってもポリゴンごとに別の法線データを持ってしまっているためです。それにより光が当たっときにポリゴンごとに光源と面の角度に応じた明るさの変化が非連続で変わってしまい、結果としてスムーズに繋がっていないように見えてしまいます。すなわちこれを解決するためには、法線ベクトルをピクセルごとに連続的に変化させてやればよいということになります。連続的に法線が変化すれば明るさの変化が連続的になり、丸みがあるように見えるようになります。

今までは1つのポリゴンの3つの頂点に同じ法線ベクトルのデータを入れていたために、Fragment Shaderには同じ法線ベクトルのデータが届いており、ポリゴン1枚のどの場所でも同じ明るさとなっていました。3頂点それぞれに別の法線ベクトルを入れると、WebGLの内部機構がそのベクトルデータを連続的に変化させて、ピクセルごとに最適な法線ベクトルをFragment Shaderに送ってくれます。

なので、やらなければいけないのは、頂点ごとに法線ベクトルを同じ方向に設定するだけです。頂点ごとに法線ベクトルを足し算して、最後に正規化(長さを1にする)してやれば、各頂点ごとの平均の法線ベクトルが得られます。

それをVertex Shaderに送る法線ベクトルのデータとして設定すれば、スムーズな出力を得ることが出来るようになります。

スムージングした出力

以上の修正を施すためには、createGLObject の関数を書き換えるだけですみます。ソースコードのdiffはこちらになります。

https://github.com/tkihira/webgl-miku-sample/commit/cf6edff8ce750c134bae47c4a6f22903fe80368b

この変更を元に出力すると、ついに丸みを持ったミクさんが登場します!一気に可愛くなりましたね!

[iframe src="http://nmi.jp/webgl/miku/miku_smooth/" width="550" height="550"]

ここまでの変更のソースコードはこちらです。

https://github.com/tkihira/webgl-miku-sample/tree/cf6edff8ce750c134bae47c4a6f22903fe80368b

さて、こうなると当然の欲求として、後はミクさんに色を付けたくなりますよね。次の記事で、ミクさんに色をつけて綺麗に出力してあげましょう!この長い記事も、次の記事で最後となります。ぜひ引き続きお付き合いください!

次の記事はこちら→ 生WebGL入門:初音ミクの美麗3Dモデルを表示する(後編)