MMD on WebGL やっと Lat 式を表示できた

MikuMikuStudio を作ってる chototsumoushin さんに、「culling がおかしいんじゃね?」的な助言をいただいたので、頑張ってみた。

↓デモ。10月20日現在、Lat 式になってる。たぶんあとで標準モデルに戻す。「コッチミンナ」目をオンにしてあるので、どのアングルを向いてもこっちを見てくれる。

MacChrome で確認したら酷いことになってた… あとで直す。
Mac でもちゃんと見られるようになった。pow(0.0, 0.0) が GLSL で未定義なので、0.0だったとこを0.001にした。


ここから本題。

アルファブレンディングについて

この前シェーダーがほぼ完成したと書いた時は↓こんな感じ。

前やってたことは、まず全部の材質を drawElements して、次に cullFace(FRONT) して(各頂点を法線方向にちょっとずらして)エッジを描画するという感じだった。ただし、これだけだと材質の裏面が黒くならないので、gl_FrontFacing で裏面を判断してそのときも黒くしてたんだけど、こんなヘンテコなことするのはおかしいよなーと思ってた。

今回は、まず cullFace(BACK) して材質を drawElement して、次に cullFace(FRONT) してエッジを同じように描画するようにした。これだけで裏面も黒くなる。こんな簡単なことに気づかなかったなんて…

これで↓こうなる。Lat 式は顔の作りが特殊で、法線が後ろ向いてるらしくてどうたらこうたら(よく知らない)のため、顎が黒くなってたっぽい。

次に、MikuMikuStudio (MMDLoaderJME) のコードで setBlendMode(BlendMode.Alpha) というのがあったので、blending というのをやってみる。これについてはこのへんを参照。

要はこれでアルファブレンディングというのをしないと、gl_FragColor にせっかくセットしたアルファの効果が発揮されなくて、常に最前面の面のみが見えてる状態になってるらしい。

WebGL には BlendMode などというのは無くて、

gl.enable(gl.BLEND);
gl.blendFunc(...);

という感じでやる。上の Wiki の blendFunc の例を見ながら

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

とすると…

おお!顔が出てきた。

…しかし、よく見ると顔に影が無い。もっと(照明を落として)よく見ると、

影になる部分が逆に明るくなってる。

実は Lat 式の顔には影ができないようになってて。影に見える部分は黒っぽい透明の材質が貼られているだけ。

黒っぽい透明なのになんで白くなるのか…と数時間悩んだところ、WebGLgl.blendFuncSeparate というのを発見。

なるほどそういうことか!ってことでやってみたら、

大成功。

つまり、色成分とアルファ成分を別々にブレンドするということだった。

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// これは↓こういうこと
// ピクセルの色(rgba) = 下層の面の色(rgba) * (1 - 上層のアルファ) + 上層の面の色(rgba) * 上層のアルファ
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA, gl.DST_ALPHA);
// これは↓こういうこと
// ピクセルの色(rgb) = 下層の面の色(rgb) * (1 - 上層のアルファ) + 上層の面の色(rgb) * 上層のアルファ
// ピクセルのアルファ = 下層の面のアルファ + 上層の面のアルファ

そりゃこうじゃないとおかしいね。