2ntブログ

【Ray-MMD1.5.2】アルファマップ画像を使って透過する部分を指定して、動画中でモデルのモーフから透過度を操作する方法

前置き

先日「Ray-MMD1.5.2でアルファマップにもAPPLY_SCALE機能を追加して更にモデルのモーフで動的に変化させる方法」という記事を書いたのだが、どうにも書き方が悪く、本人にしか理解できない悪文になってしまっていた。
というわけでリベンジ。まともな文章を書こう。

やりたいこと

長い髪をもつモデルがあったとする。訳あって髪の途中から先端にかけて、グラデーション状に透明にしていきたいと思ったとする。しかも、動画の一部分でのみ。
モデルの参照しているテクスチャ画像を書き換えるのでは実現不可能だ。これではグラデ状に透過できても動画中に変えられない。
動画中に透明度を変えるときたら材質を透過するモーフだろう。しかしこいつではグラデ状に透過することができない。
さあ困った。ここでRay-MMDの登場だ。AlphaMapの機能を使えば実現できる。
ただそのためには色々といじらねばならないことがある。今回はその解説をしていこう。

Ray-MMDとMaterialなるもの

Ray-MMDというのは光の反射について物理的な計算をすることで、画像を描画してくれるスゴイヤツである。
さて、いま光があたっている物体があったとして、これはどのように光の反射をするのだろうか?
物体のマテリアル設定がそれを決める。
そしてray-mmd-1.5.2/Materials/ に入っているfxファイルがその設定ファイルである。適当なテキストエディタでもって開いてやると、やれ #define だの const float3 だのという意味不明な文字の羅列が目に入るだろう。今回はそれを弄ってやることになる。

今回は必要な部分のみをかいつまんで説明するが、全貌を詳しく知りたい人は「ray-mmd 日本語ドキュメント」の「8_1_マテリアル」とか「8_2_マテリアル_パラメータ」とかを読むといい。

ALPHA MAP

「RGB」とはつまり「赤緑青」のことだが、それに一文字加えて「RGBA」という形で色を表記することがある。
このAというのはAlpha(アルファ)のことで、透明度を表すという事になっている。
Ray-MMDにはアルファ値を画像データに基づいて決定してくれる機能がある。
この画像のことをアルファマップ画像と呼ぶ。

今回は、アルファマップはモデルの髪材質が参照しているテクスチャ画像を加工して作成しよう。
適当な画像編集ソフトで透過したい部分を透明にし、境界をグラデ状に滑らかにしてやる。
ファイル名はhair_alpha.png、ray-mmd-1.5.2/Materials/_MaterialMap/ 下に保存したとする。

マテリアル設定ファイルの編集

マテリアル設定ファイルは ray-mmd-1.5.2/Materials/ にある。
material_2.0.fx とか、material_common_2.0.fxsub とかあるが、今回は髪を弄りたいのでHairフォルダ内の material_hair.fx を編集することにしよう。

まずは material_hair.fx を複製し、名前はmaterial_hair_dynamic_alpha.fx とでもしておこう。
このfxファイルを適当なテキストエディタで開く。
スクロールしていけば
#define ALPHA_MAP_FROM 3
#define ALPHA_MAP_UV_FLIP 0
#define ALPHA_MAP_SWIZZLE 3
#define ALPHA_MAP_FILE "alpha.png"

const float alpha = 1.0;
const float alphaMapLoopNum = 1.0;

という部分が見つかるはず。これが適用された材質の透明度を指定する部分だ。

#define ALPHA_MAP_FROM 3 は材質のテクスチャ画像のアルファ値をそのまま使用する設定だ。

これを#define ALPHA_MAP_FROM 1 に書き換えると、#define ALPHA_MAP_FILE "alpha.png" の部分で指定した画像ファイルからアルファ値を取ってくるようになる。

"alpha.png" を作成した画像ファイルのパスに書き換えよう。ray-mmd-1.5.2/Materials/_MaterialMap/hair_alpha.png を今回用意した画像とすると、"../_MaterialMap/hair_alpha.png" と書き換えることになる。この意味がわからないなら、「相対パス」で検索すると良いだろう。

#define ALPHA_MAP_FROM 1
#define ALPHA_MAP_UV_FLIP 0
#define ALPHA_MAP_SWIZZLE 3
#define ALPHA_MAP_FILE "../_MaterialMap/hair_alpha.png"

const float alpha = 1.0;
const float alphaMapLoopNum = 1.0;


このmaterial_hair_dynamic_alpha.fxを髪に適用すれば、グラデ状に髪を透過させることができる。だがこれではまだ髪のテクスチャを直接弄ったときと何も変わらない(それどころかテクスチャで直に透過したときには発生しない問題まで発生してしまうことになる)。

そこで次に const float alpha = 1.0; の値で透過度を制御できるようにしよう。


APPLY SCALE 不在問題

しかしここで大きな問題にぶち当たる。
変数の値でマップ画像から取ってきた値を制御するには 〇〇_MAP_APPLY_SCALE 1 とすれば良いと思ったら、AlbedoやNormalにはある Apply Scale が Alpha に限って用意されていないのである。
ならばどうすれば良いのか。幸いにして難しくない処理である。追加してやればよいのだ。

material_common_2.0.fxsub を書き換える

fxファイルの最下部は
#include "../material_common_2.0.fxsub"
となっている。
これはざっくりいうと「ひとつ上の階層にある material_common_2.0.fxsub というファイルの全文をここにコピペしてください」みたいな命令だ。
ALPHA_MAP_APPLY_SCALE が存在しないのはこのfxsubに処理が書かれていないからだ。というわけでこいつに追加してやる。
別に直接書き換えてもいいが、気になる人は material_common_2.0.fxsub を複製して別名に書き換えたやつを編集してもいい。その時はfxファイルの#include以降も合わせて書き換えることを忘れずに。

では対象のfxsubファイルの877行目を開こう。
ここはAlpha値を扱う GetAlpha関数の中で、ALPHA_MAP_FROM が0以外のときに実行される部分だ。
そこに以下のコードを貼り付ける。
#ifdef ALPHA_MAP_APPLY_SCALE
alphaValue=alpha*(alphaValue-1)+1;
#endif
#ifdef というのは「もしこれが定義されていたら」を条件とした条件分岐、#endif までの部分がその範囲となる。
alphaValue というのがある地点でのマップ画像のアルファ値で、
alpha はfxファイルで指定した値である。

alpha*(alphaValue-1)+1 はすこし複雑だ。
まず alphaValue は[0,1]の範囲の数値となる(不透明な部分が1、透明な部分が0で、値は実数なので小数で表される)。
これを-1することで[-1,0]の範囲に変換される。
次にalphaをかけると、これが[-alpha,0]になる。最後に+1で[1-alpha,1]がalphaValueの範囲になる。
つまり、alpha=0のとき[1,1]ですべて不透明扱いになる。
alpha=0.5のとき[0.5,1]となりマップの透明度の適用が半分になる。
alpha=1のとき[0,1]でアルファマップ通りの適用がなされることになる。
ちなみにalpha>1でもちゃんと動作する。

ちなみに、MMEはfxファイルの書き換えを感知してくれるが、fxsubの書き換えは感知してくれない。
MMD開きっぱでfxsubの書き換え作業をしていた場合、書き換えを反映させたいときはfxsubをincludeしているfxの上書き保存で反映させられる。別にすべて更新でもいいが。

これでfxファイルに#define ALPHA_MAP_APPLY_SCALE と書き加えることで他と同じように Apply Scale が使える。#ifdef で分岐させたので数値はいらない。
#if の数値ではなく #ifdef にしたのは未定義がどうとかのエラーが出そうな気がしたからである。試してないので実際に出るかは知らない。

モデルのモーフで透過度を制御する

#define ALPHA_MAP_FROM 1
#define ALPHA_MAP_UV_FLIP 0
#define ALPHA_MAP_SWIZZLE 3
#define ALPHA_MAP_APPLY_SCALE
#define ALPHA_MAP_FILE "../_MaterialMap/hair_alpha.png"

const float alpha = 1.0;
const float alphaMapLoopNum = 1.0;
fxファイルのAlpha部分は現時点でこうなっているはずだ。
この時点ではまだ動画中にalpha値を変化させられない。
モデルに「髪透過」モーフを追加して、これにalpha値を連動させよう。

モデルのモーフの値を持ってくるにはどうしたらいいだろうか。
ray-mmd-1.5.2/Materials/Editor/ に数値パラメータをコントローラpmxを使って動的に制御できるfxファイルが入っている。
コントローラpmxからモーフの値を持ってくる部分は material_editor.fxsub 内に記述されている。
float 「変数」 : CONTROLOBJECT<string name="「コントローラPMXのファイル名」"; string item = "「値を取得したいモーフの名前」";>;

という形式になっているようだ。なお「」で囲ってある日本語部分は意味を表すので、実際に使うときは適切な表記で置き換えられねばならない。

最終的に「変数」にモーフの値が代入される。

「コントローラPMXのファイル名」は文字通りファイル名のみで、パスを指定してやる必要はないようだ。おそらくMMDに読み込んだpmx名から取ってきているからだろう。


これを#define と const float alphaの間に追加する。

そしてalphaに変数値を代入する。

#define ALPHA_MAP_FROM 1
#define ALPHA_MAP_UV_FLIP 0
#define ALPHA_MAP_SWIZZLE 3
#define ALPHA_MAP_APPLY_SCALE
#define ALPHA_MAP_FILE "../_MaterialMap/hair_alpha.png"

float mAlpha : CONTROLOBJECT<string name="モデル.pmx"; string item = "髪透過";>;
static const float alpha = mAlpha;
const float alphaMapLoopNum = 1.0;

最終的にこうなる。これでモデル.pmxの髪透過モーフの値をmAlpha変数に読み込める。

気をつけるべきは const float alpha の先頭に static が必要なことだ。無いとエラーが出る。

alpha = 2*mAlpha にしてもいいかもしれない。まあ好きに弄るといいだろう。


これでアルファマップを使って透過度を指定し、動画中にモーフを弄ることで変化させられる。

目的の挙動が実現できた。


Mainの問題

実は、テクスチャを直接透過する時には発生しないが、今回のようにアルファマップを使って透過度を指定する時に発生する問題がある。
透けた先がskyboxだった場合、背景が描画されず真っ黒になってしまうのだ。
原因はどうやらmainで指定された透過部とmaterialで指定された透過部とが不一致である事のようだ。

これを直すにはmain.fxを書き換えてやらなければならない。
例のごとく複製して別名のものを作ろう。今回はmain_hair_dynamic_alpha.fx としたとする。
中を見ると2行目から
#define ALPHA_MAP_FROM 3
#define ALPHA_MAP_UV_FLIP 0
#define ALPHA_MAP_SWIZZLE 3
#define ALPHA_MAP_FILE "alpha.png"

const float alpha = 1.0;
const float alphaThreshold = 0.999;
const float alphaMapLoopNum = 1.0;

となっている。

これをmaterialと同じように

#define ALPHA_MAP_FROM 1
#define ALPHA_MAP_UV_FLIP 0
#define ALPHA_MAP_SWIZZLE 3
#define ALPHA_MAP_APPLY_SCALE
#define ALPHA_MAP_FILE "../Materials/_MaterialMap/hair_alpha.png"

float mAlpha : CONTROLOBJECT<string name="モデル.pmx"; string item = "髪透過";>;
static const float alpha = mAlpha;
const float alphaThreshold = 1.0;
const float alphaMapLoopNum = 1.0;

とし、main.fxsub でも231行目に

#ifdef ALPHA_MAP_APPLY_SCALE
alphaValue=alpha*(alphaValue-1)+1;
#endif

を追加する。


これで問題ない。

つまり今回のようなmaterialを使うときにはmainも合わせて個別に用意しなければならないわけで、やや面倒だが仕方ない。


まとめ

material_hair_dynamic_alpha.fx のAlpha部分のビフォーアフター:
#define ALPHA_MAP_FROM 3
#define ALPHA_MAP_UV_FLIP 0
#define ALPHA_MAP_SWIZZLE 3
#define ALPHA_MAP_FILE "alpha.png"

const float alpha = 1.0;
const float alphaMapLoopNum = 1.0;

これが、

#define ALPHA_MAP_FROM 1
#define ALPHA_MAP_UV_FLIP 0
#define ALPHA_MAP_SWIZZLE 3
#define ALPHA_MAP_APPLY_SCALE
#define ALPHA_MAP_FILE "../_MaterialMap/hair_alpha.png"

float mAlpha : CONTROLOBJECT<string name="モデル.pmx"; string item = "髪透過";>;
static const float alpha = mAlpha;
const float alphaMapLoopNum = 1.0;

こうなった。

使うときは"../_MaterialMap/hair_alpha.png" や "モデル.pmx"、"髪透過"の部分は適宜書き換えよう。


material_common_2.0.fxsubの追加部分を付近も含めて(太字が追加部分):

#if ALPHA_MAP_SWIZZLE == 0
float alphaValue = alphaValues.r;
#elif ALPHA_MAP_SWIZZLE == 1
float alphaValue = alphaValues.g;
#elif ALPHA_MAP_SWIZZLE == 2
float alphaValue = alphaValues.b;
#else
float alphaValue = alphaValues.a;
#endif

#ifdef ALPHA_MAP_APPLY_SCALE
alphaValue=alpha*(alphaValue-1)+1;
#endif


return saturate(alphaValue);
#else
return saturate(alpha * MaterialDiffuse.a);
#endif


次はMain。

main_hair_dynamic_alpha.fx のAlpha部分のビフォーアフター:

#define ALPHA_MAP_FROM 3
#define ALPHA_MAP_UV_FLIP 0
#define ALPHA_MAP_SWIZZLE 3
#define ALPHA_MAP_FILE "alpha.png"

const float alpha = 1.0;
const float alphaThreshold = 0.999;
const float alphaMapLoopNum = 1.0;

これが

#define ALPHA_MAP_FROM 1
#define ALPHA_MAP_UV_FLIP 0
#define ALPHA_MAP_SWIZZLE 3
#define ALPHA_MAP_APPLY_SCALE
#define ALPHA_MAP_FILE "../Materials/_MaterialMap/hair_alpha.png"

float mAlpha : CONTROLOBJECT<string name="モデル.pmx"; string item = "髪透過";>;
static const float alpha = mAlpha;
const float alphaThreshold = 1.0;
const float alphaMapLoopNum = 1.0;

こうなって、main.fxsubが

#if ALPHA_MAP_SWIZZLE == 0
float alphaValue = alphaValues.r;
#elif ALPHA_MAP_SWIZZLE == 1
float alphaValue = alphaValues.g;
#elif ALPHA_MAP_SWIZZLE == 2
float alphaValue = alphaValues.b;
#else
float alphaValue = alphaValues.a;
#endif

#ifdef ALPHA_MAP_APPLY_SCALE
alphaValue=alpha*(alphaValue-1)+1;
#endif

return alphaValue;
#else
return alpha * MaterialDiffuse.a;
#endif


これで終わり。長くなってしまったがこれでわかりやすくなったはず……多分。

まだわかりにくかったらTwitterとかで言ってください。


コメントの投稿

非公開コメント

No title

非常に助かりました。

承認待ちコメント

このコメントは管理者の承認待ちです