nmi.jp Twitter → @tkihira
生WebGL入門:初音ミクの美麗3Dモデルを表示する(中編) embona - ブラウザで動くBonanzaを作ってみた(その1)

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


2014-12-04
Takuo Kihira

前の記事で、3Dモデルのミクさんの描画に成功しました。ただ現状はモデルの外形を表示出来ただけなので、せっかくここまで表示出来たからには色を塗ってみたいところです。今回の記事では、色を塗りテクスチャを貼ってみます。また、最後に別のモデルを表示して、テクスチャの威力についてもご紹介したいと思います。

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

MTLデータの読み込み、表示

その1の記事で準備した時、OBJファイルと共にMTLファイル が生成されたことを覚えていらっしゃるでしょうか。前の記事で紹介したとおり、OBJファイルはポリゴンのデータを定義するものでしたが、色の情報は一切ありませんでした。それらのデータは、MTL(マテリアル)ファイルに定義されています。というわけで、今回は色をつけるためにマテリアルファイルを扱います。

マテリアルファイルのフォーマット

OBJファイルと同じく、マテリアルファイルもテキストファイルです。フォーマットは大体以下のような形になっております。

newmtl マテリアル名
Ns 光沢率
Ka 環境反射光R要素 G要素 B要素
Kd 拡散反射光R要素 G要素 B要素
Ks 鏡面反射光R要素 G要素 B要素
d 透過率
illum 照明モデルフラグ(1で反射光有効、2で無効)
map_Kd テクスチャファイル名

簡単に説明します。まずマテリアル名ですが、これはOBJファイルで「usemtl」と指定された場合に対応する名前となっています。Ns(光沢率)は、光に対してどの程度ピカピカさせるかの値です。大体5〜200くらいですが、高くなればなるほどツヤツヤしてプラスチックっぽい(というかCGっぽい)光沢になるようです。Ka環境光反射率、Kd拡散光反射率、Ks鏡面光反射率は、光の反射の時にどのような色がつくかの情報です。map_Kdはテクスチャファイル名を指定します。こういった情報をマテリアルと呼びます。

マテリアルファイルに上記の情報が全部ないこともあれば、他の情報が入っていることもあります。今回は最低限必要な物だけを選択的に読み込むことにします。具体的には、今回の記事ではnewmtl、Kd、Ks、Ns、map_Kdのみでもしっかりしたモデルが表示出来ました。不足を感じたら必要に応じて読み込むとよいでしょう。

パーサーは objparser.js の中に書きました。それほど複雑ではないですね。

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

とりあえず下記のように、読み込みたい情報は読み込めています。

マテリアルファイルとOBJファイルの関係

マテリアルファイルに書いてある色の定義が、OBJファイルとどのように紐づいているのか確認してみましょう。OBJファイルの中では、"usemtl" という構文でどのマテリアル定義を使うのか指定しています。この構文が影響するのはポリゴンで、この構文より後の "f" で定義されているポリゴンで、そのマテリアルの定義を使う指定となっています。

マテリアル情報は、1つのマテリアル定義に大量のポリゴンが結びつく形が一般的です。なので、attributeを利用して各頂点ごとにShaderに送るよりも、uniformで定数としてShaderに送るほうが望ましいことが多いでしょう。前回までは、1回のdrawArrays関数で全ての頂点データを送っていましたが、今回はマテリアルの定義ごとにuniformを新たに設定し、そのマテリアル定義を利用する頂点情報のみを送るようにしました。

objparser.jsにて、それらの設計を念頭において createGLObject を書き直しました。とりあえずテクスチャは後回しにし、まずはKs、Kdなどの情報を利用して色をつけてみましょう。

光のあたり方と色との関係

前回までの記事では、正面からの光に対して法線ベクトルの内積を取るという適当なやり方で色をつけていました。今回は拡散反射光、鏡面反射光などのデータがありますので、それらのデータを利用してしっかりと色をつけることにしましょう。

まずKd(拡散反射光)の扱い方です。拡散反射光は、光源から物体に光が当たった後、それが拡散されて指向性を失い、物体の持つ色を全方向に均等に発する光のことです。なので、人間の視点とは関係なく、単純に光源ベクトルと物体の法線ベクトルとの内積で定義されます。今までのミクさんは、拡散反射光として白色が物体の色として発散されていたことになりますね。

一方、Ks(鏡面反射光)は視線に関係する光となります。入射光が物体で反射した際、視線に届く光を鏡面反射光と呼びます。太陽の光が物体に反射して目に入りまぶしい、という経験はどなたもあると思いますが、その光のことです。鏡面反射光は当然ながら、視線からずれる分だけ小さくなっていきます。数学的には実は定義がいろいろあるのですが、直感的にわかりやすい定義としては光源ベクトルから法線ベクトルを対象として反射ベクトルを生成し、その反射ベクトルと視線ベクトルの内積で表すことが出来ます。

これをGLSLで表現するにはどうすればいいでしょう。反射ベクトルの計算が面倒そうに見えるところですが、GLSLには入射ベクトルと法線ベクトルから反射光ベクトルを計算するreflectという関数がありまして、それを利用すると簡単にかけます。

これらの計算はVertex Shaderでやった方が処理速度的に良いのですが、今回はFragment Shaderで処理し、全ピクセルで計算することでより正確に色を反映してみます。Fragment Shaderのコード的には次のようになります。

vec3 n = normalize(e_normal); // 正規化された法線ベクトル
vec3 light_v = normalize(vec3(1, 1, 1)); // 正規化された入射光ベクトル
float diffuse = max(dot(light_v, n), 0.0); // 拡散光は入射光ベクトルと法線との内積
float specular = pow(max(dot(-normalize(eye_v), reflect(-light_v, n)), 0.0), nscolor); // 鏡面光は視線ベクトルと反射光ベクトルの内積
float l = 0.5; // 環境光の強さ
gl_FragColor = vec4(kdcolor * diffuse + kscolor * specular, 1.0) + vec4(kdcolor, 1.0) * vec4(l, l, l, 1.0);

視線ベクトルは、このピクセルの位置から視点の位置(0, 0, 0)へのベクトルとなるため、ピクセルの位置にマイナスを掛けたものを視線ベクトルとして利用出来ます。ここでは、varyingでVertex ShaderからFragment Shaderに位置情報を送ることで視線ベクトルを生成しています。

光沢率Nsは、鏡面反射光の計算で得られた補正値を何乗するか、という値になります。何乗というと掛けあわせて大きくなるイメージかもしれませんが、補正値は内積の結果で0〜1の間になりますので、Ns値が大きいと計算結果が1から外れれば外れるほど最終的に小さな値に収束する結果になります。すなわちNs値が大きいと視線ベクトルと反射ベクトルがずれた時に鏡面反射光の強さを抑え込む効果が大きくなり、結果として鏡面反射光が鋭くなります。今回のマテリアルファイルにはNsの指定があるのでその指定に沿っていますが、もし指定がない場合は5あたりの値を使うとよいでしょう。

今回のマテリアルファイルでは、環境反射光Kaは全てゼロになっていました。環境反射光はその物体自身が持っている色であり、それはだいたい拡散反射光と同じ色であると考えて差し支えありませんので、拡散反射光を環境反射光に代用しています。

結果

これらの修正を入れたソースコードはこちらになります。

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

実行すると、以下のように色のついたミクさんが表示されます!

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

色がつくとますます可愛くなりました。光の反射などがうまく表現されているのがわかりますね!

テクスチャ

さて、最後に残ったのはテクスチャです。上のミクさんは、残念ながら目が描画されておらず、画竜点睛を欠いています。ミクさんの目はテクスチャで表現されているので、目のテクスチャを貼ってあげましょう。とはいえ、OBJファイルにしっかりとテクスチャ頂点の定義がされているので、ポリゴンにテクスチャを貼るのはそんなに大変ではありません。

テクスチャを貼る

テクスチャは、用意されたテクスチャ画像の一部をポリゴンに貼っていくことで実現されます。objファイルの中でvtの定義がされていましたが、それが頂点ごとに「テクスチャ画像のどこに対応しているか」という情報になります。テクスチャ画像を[0-1]という座標で指定するのですが、今回のOBJファイルではテクスチャのリピートを前提にしているようで1以上の数字が散見されます。

まずは createGLObject関数の中でテクスチャ頂点情報を作成し、テクスチャに必要な画像を動的に読みこむようにします。別にimgloader.jsというファイルを作成し、指定されたテクスチャ画像を読み込みます。今回読み込むファイルはeyeM2.bmpの1つだけですね。

ただ問題がありまして、WebGLでテクスチャを読み込むときには、画像の大きさが2のべき乗サイズになっていないと、リピートが出来ない等の不都合が起きることがあります。例えば256x256、512x512、1024x1024といった感じですね。今回のeyeM2.bmpのサイズは106x106とべき乗サイズではないので、Canvas 2DのAPIを利用してべき乗サイズ(128x128)に拡大し、それをテクスチャとして使うようにしています。

テクスチャをWebGLにロードするために miku.js 内のloadBuffer内部でテクスチャオブジェクトを事前に作り、それを保存しています。WebGLではテクスチャを作るのは簡単で、引数にimgやcanvasだけでなくvideo要素まで(!)指定することが出来ます。準備したテクスチャはuniform情報としてVertex Shaderに送信しますが、細かいところはソースを参照してください。

テクスチャがあった場合のFragment Shaderのコードは次のようにしました。

gl_FragColor.rgb = (kdcolor * texcolor.rgb) * (diffuse + l) + kscolor * specular;
gl_FragColor.a = texcolor.a;

テクスチャの色と拡散光を掛けあわせ、鏡面光はそのまま利用している感じです。例えば掛け算ではなくて足し算を使うなど、ここは好みである程度変えて自分好みの形で表現するのも良いと思います。

結果

さて、上記の変更で完成したコードはこちらになります。

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

実行すると、この通り!ミクさんの目が描画されるようになりました。

これで、今回の目的であった「3Dモデルを表示する」という目標は達成しました!

テクスチャを豊富に使ったモデルを表示する

このミクさんは小さな目のテクスチャが1枚あるだけのモデルで、テクスチャの威力はそこまでありません。逆に言うとテクスチャを使わなくてもかなりのところまで表現出来るということでもあるのですが、せっかくなのでテクスチャをふんだんに使用したモデルの威力をご紹介したいと思います。

最後にご紹介するのは、「Tda式Appendミク」モデルです。このモデルもMMDのモデルですので、最初にご紹介したやり方と同じやり方でOBJファイルMTLファイルを取得することが出来ます。マテリアルファイルを見ていただければわかる通り、このモデルは全身全てがテクスチャで表現されております。テクスチャの威力をご紹介するには最適だろうと考えて、このAppendミクさんのモデルを選択しました。

このAppendミクさんを表示するためには、いくつかの修正が必要です。まずテクスチャデータですが、TGA形式というフォーマットでテクスチャデータが用意されているので、それをJavaScript側でデコードしてやる必要があります。幸いgithubでTGAデコーダのプロジェクトがありましたので、今回はそれを利用しました。imgloader.js をちょっと書き換えるだけですみます。

これだけの設定でもそこそこきちんと表示されるのですが、テクスチャデータにアルファ値があるので、せっかくなのでWebGLにてアルファのブレンドモードを設定して表示しております。本来WebGLとアルファの相性は大変悪く、アルファ値を綺麗に扱うのはなかなか面倒です。今回はしかしモデルデータの構造に助けられ、特にいじることなく綺麗に表示されているので助かりました。

それらの修正を加えたコードはこちらになります。

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

そして、このコード上でAppendミクさんを表示したのがこちらです。

いかがでしょうか。ため息が出るほど綺麗なモデルですね。このモデルのポリゴン数は33411で、あにまさ式ミクのモデルのポリゴン数(22961)と比べて格段に大きいわけではありませんが、テクスチャを効果的に使うとここまで表現力があがるのかとビックリされたのではないでしょうか。

生WebGLで3Dモデルを表示する

あにまさ式ミクにせよ、Tda式Appendミクにせよ、モデルを制作された方々が素晴らしいのはもちろんのことですが、それらのモデルをWebGLを使い自分の手で綺麗に表現できるのは素晴らしい体験だと思います。

Three.jsなどを使うと、今回の作業などはAPIをちょいちょいっと呼ぶだけで出来ることでしょう。ほとんどの人にとっては明らかにそちらの方が良い解になるのは間違いありません。しかし、最初にも言いましたとおり、背景でどのような処理が行われているのかを知っているのと知らないとでは、難しいことをしようとすればするほど大きな差が生まれます。今回の記事が、WebGLの背景に興味のある方々の刺激になれば幸いです。

今更ですが、今回のソースはgithubで公開しています。全部を同じディレクトリに入れて index.html を開けば表示されるはずです。是非ご参照ください。

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

最後に…

ここまで読んだガチWebGLな仕事に興味のある方、是非 Tombo Inc. までご連絡ください!