WebGL で MMD を表示してみるテスト

もう何番煎じという感じだけど、それなりに見えるようになった。3D プログラミングの知識ゼロで始めたので、検索しながら色々勉強になるのが楽しい。

MMD はソース公開されてないので、シェーダーの調節が手探り状態。

有志によって解析された PMD データ形式の「マテリアル」の部分↓を見て、各値をどのように足したり掛けたりするかを想像して、GLSL に落とし込む。

 float diffuse_color[3]; // dr, dg, db // 減衰色
 float alpha;
 float specularity;
 float specular_color[3]; // sr, sg, sb // 光沢色
 float mirror_color[3]; // mr, mg, mb // 環境色(ambient)
 BYTE toon_index; // toon??.bmp // 0.bmp:0xFF, 1(01).bmp:0x00 ・・・ 10.bmp:0x09
 BYTE edge_flag; // 輪郭、影
 DWORD face_vert_count; // 面頂点数 // インデックスに変換する場合は、材質0から順に加算
 char texture_file_name[20]; // テクスチャファイル名またはスフィアファイル名 // 20バイトぎりぎりまで使える(終端の0x00は無くても動く)
http://blog.goo.ne.jp/torisu_tetosuki/e/ea0bb1b1d4c6ad98a93edbfe359dac32

WebGL じゃなくて OpenGL を使った MMDAgent というオープンソースソフトもあって、大変参考になるのだけど、OpenGL の glMaterialfv という便利機能が WebGL には無いので、そのあたりの処理は自作しないといけない。

輪郭線の部分はまだかなり違ってる(一番上のスクリーンショット参照)ので置いといて、色使いの部分のフラグメントシェーダーはこんな感じになった。

color = vec3(1.0, 1.0, 1.0);
if (uUseTexture) {
  color *= texture2D(uTexture, vTextureCoord).rgb;
}
if (uUseSphereMap) {
  vec2 sphereCoord = 0.5 * (1.0 + vec2(1.0, -1.0) * norm.xy);
  if (uIsSphereMapAdditive) {
    color += texture2D(uSphereMap, sphereCoord).rgb;
  } else {
    color *= texture2D(uSphereMap, sphereCoord).rgb;
  }
}

vec3 halfAngle = normalize(uLightDirection + cameraDirection);
float specularWeight = pow( max(0.0, dot(halfAngle, norm)) , uShininess );
vec3 specular = specularWeight * uSpecularColor;

color *= uAmbientColor + uLightColor * (uDiffuseColor + specular);
color = clamp(color, 0.0, 1.0);

vec2 toonCoord = 0.5 * (1.0 - (vec2(0.0, dot( uLightDirection, norm ))));
color *= texture2D(uToon, toonCoord).rgb;

まったく自分の想像でしかないので、違ってたら教えて下さい。

アルゴリズム的には大きく分けて3段階。

まずテクスチャーのあるマテリアルについてはテクスチャーを貼る。これは普通。普通のテクスチャーの他に、加算スフィアマップと乗算スフィアマップというのがあることもある。

次に、ambient、specular、diffuse の調整。

color *= uAmbientColor + uLightColor * (uDiffuseColor + specular);

uAmbientColor というのは上のデータ形式でいうと mirror_color というやつ。

uLightColor は MMD のこれ↓の RGB のところ。

uDiffuseColor はそのまま diffuse_color

specular は specular_color と specularity (shininess) から教科書通りに計算(表面の法線がカメラの方向と光源の方向のちょうど真ん中だと一番光沢が出る)。ただし、MMD とは微妙な光沢の違いが出てしまうので、もうちょっと改良できるならしたいところ。shininess を何かで割る感じかな?

ここで一旦色の上限を1に制限して、一番最後にトゥーンを掛ける。この部分は MMDAgent を参考にした。

vec2 toonCoord = 0.5 * (1.0 - (vec2(0.0, dot( uLightDirection, norm ))));

表面の法線が光源の方向を向いてると「明るい部分」の色、そうじゃなければ「暗い部分」の色になるという意味。「明るい部分」と「暗い部分」を連続的にではなくて段階的に(discrete)切り替えるのがトゥーンシェーディング。(↓を参考に)


輪郭線だけちゃんと出来るようになったらソースを公開しようと思う。最終的には物理演算まで行きたいところだけど、どうなるか…

何枚かスクリーンショットを貼っときます。

↓まずは標準モデル。輪郭以外の色はほぼ同じ。左腕の光沢が微妙に違う。

↓メタルモデル。乗算スフィアマップの確認にいい。髪の色だけちょっと薄い理由は不明。

↓ホメ春香。輪郭線の確認にいい。輪郭線の付け方はいくつかあるみたいだけど、その中でも「裏面を黒くする」という方針で合ってることが、スカートの裏地が黒いことでわかる。自分の方法だと裏面でないのにベタっと黒くなってしまう箇所(スカートのひだ)があるので何かがおかしい。

↓このアングルの髪の輪郭も太くなりすぎる。

↓顔の法線が逆になってる等特殊な作り方をしてあることで有名な Lat 式。まだ全然だめ。少なくともこれが表示できるようになりたい。

http://f.hatena.ne.jp/edvakf/20111003024903 (グロ注意)