Ray-MMD に自作ポストエフェクトを差し込んでみる

この記事では Ray-MMD に自作のポストエフェクトを組み込む方法を紹介します。HLSL の基礎的な知識を前提とします。

## なぜ自作ポストエフェクトを Ray-MMD に組み込みたいのか

ポストエフェクトであれば別に Ray-MMD に組み込まなくても Ray-MMD と一緒に利用できます。しかし、Ray-MMD に組み込みたくなる場合もたまにはあったりします。

Ray-MMD に組み込むと次のようなメリットがあります。

- Ray-MMD が持っている情報にアクセスできる。
    - 例えばピクセルの深度やアルベドなど
- Ray-MMD の描画パスの任意の位置にポストエフェクトを差し込める
    - 例えば HDR がかかるより前に自作エフェクトを差し込める
- Ray-MMD が持っている便利な関数が使える
    - 例えば汎用の頂点シェーダを使って楽をするとか、Ray-MMD の BRDF を使って色を計算するとか

一方、Ray-MMD と切り離して単体でエフェクトを使うことができなくなってしまうので、そこらへんはメリットとデメリットを比較して組み込むかどうかを考える必要があるでしょう。

それでは Ray-MMD に実際に自作エフェクトを組み込んでいきます。

## Step 1: ray.conf に設定項目を作る

`ray.conf` を開いてみましょう。このファイルには Ray-MMD の設定項目がずらりと並んでいます。

このファイルに今から作るポストエフェクトの有効無効を切り替えるフラグを定義します。以下の切り取り線で囲まれた部分をこのファイルに書き足してください。

```c
// Screen Space Subsurface Scattering
// 0 : None
// 1 : Enable
#define SSSS_QUALITY 1

//----------- ✂ -----------
// 0 : None
// 1 : Enable
#define POST_EFFECT_EXAMPLE 1
//----------- ✂ -----------

// Bokeh Depth Of Field
// MeasureMode can supports the following options:
// When MeasureMode at (0.00 ~ 0.24) : Test circle 
// When MeasureMode at (0.25 ~ 0.49) : Test circle + Bone from ray.x
// When MeasureMode at (0.50 ~ 0.98) : FocalDistance Only
// When MeasureMode at (0.99 ~ 1.00) : FocalDistance + Bone from ray.x
// 0 : None
// 1 : Enable
#define BOKEH_QUALITY 0
```

## Step 2: ピクセルシェーダを書く

ポストエフェクトとして使うピクセルシェーダを用意します。まずは単色で塗りつぶすだけのものにしておきましょう。

以下の内容のファイルを `Shader` ディレクトリの下に `PostEffectExample.fxsub` という名前で作成してください。

```c
float4 PostEffectExamplePS(in float4 coord : TEXCOORD0) : COLOR
{
    // 萌葱色で塗りつぶす
    return float4(0.0, 0.43, 0.33, 1.0);
}
```

## Step 3: パスを差し込む

ピクセルシェーダが用意できたらそれを呼び出してみましょう。

`ray.fx` をエディタで開いてみてください。`ray.fx` というファイルには Ray-MMD の様々なパスがずらーーっと並んでいます。このパスの列の中に上で書いたシェーダを呼び出すパスを差し込んでみます。

まずは include するところからです。以下の切り取り線で囲われた部分を追加してください。

```c
#if SSR_QUALITY
#	include "shader/PostProcessSSR.fxsub"
#endif

//----------- ✂ -----------
#if POST_EFFECT_EXAMPLE
#   include "shader/PostEffectExample.fxsub"
#endif
//----------- ✂ -----------

#if BOKEH_QUALITY
#	include "shader/PostProcessHexDOF.fxsub"
#endif
```

次は pass の追加をします。`ray.fx` の中に `pass Hogehogehoge<...> { ... }` のようなブロックが並んでいる部分があるのでそこをまず見つけてください。その中に `#if BOKEH_QUALITY` と書かれている行があります。その行の **直前** に以下のブロックを追加してください。

```c
#if POST_EFFECT_EXAMPLE
    pass PostEffectExample<string Script= "Draw=Buffer;";>{
        AlphaBlendEnable = false; AlphaTestEnable = false;
        ZEnable = false; ZWriteEnable = false;
        VertexShader = compile vs_3_0 ScreenSpaceQuadVS();
        PixelShader  = compile ps_3_0 PostEffectExamplePS();
    }
#endif
```

ここで使われている `ScreenSpaceQuadVS` という頂点シェーダは Ray-MMD が定義している汎用的な頂点シェーダで、画面全体を覆う1枚の長方形を作る頂点シェーダです。ポストエフェクトの頂点シェーダはこれを使えば大体事足りるでしょう。

`PostEffectExamplePS` は上で自分たちが定義したピクセルシェーダですね。

最後にこのパスを呼び出すコードを `DefferedLighting` の Script に追加します。Script の定義を見ていって `#if BOKEH_QUALITY` と書かれている行を探してください。その直前に以下のコードを追加してください。

```c
#if POST_EFFECT_EXAMPLE
    "RenderColorTarget=ShadingMap;"
    "Pass=PostEffectExample;"
#endif
```

これで自作ポストエフェクトが読み込まれるようになりました。単色で塗りつぶすシェーダを書いたので、画面が一色で塗りつぶされているはずです！

![萌葱色で塗りつぶされた画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632034/85321d57-c033-314f-ab70-a5d3eac037cb.png)

## Step 4: Gbuffer から情報を取得して表示してみる。

単色で塗りつぶすだけだと面白くないので、深度を表示してみましょう。

`PostEffectExamplePS` を以下のように書き換えて、MMDのメニューの MMEffect → 全て更新(R) を押してください。

```c
float4 PostEffectExamplePS(in float4 coord : TEXCOORD0) : COLOR
{
    float4 MRT0 = tex2Dlod(Gbuffer5Map, float4(coord.xy, 0, 0));
    float4 MRT1 = tex2Dlod(Gbuffer6Map, float4(coord.xy, 0, 0));
    float4 MRT2 = tex2Dlod(Gbuffer7Map, float4(coord.xy, 0, 0));
    float4 MRT3 = tex2Dlod(Gbuffer8Map, float4(coord.xy, 0, 0));

    MaterialParam material;
    DecodeGbuffer(MRT0, MRT1, MRT2, MRT3, material);

    float color = exp(-0.01 * material.linearDepth);
    return float4(color, 0, 0, 1);
}
```

すると以下のように深度が浅い部分は赤く描画され、深度が深い部分は黒く描画されるようになります。なかなか良い感じですね！

![深度が表示された画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632034/3ba913b8-7733-29f6-05b9-93fdef7aa74a.png)

ここで出てくる Gbuffer というのは、Geometry Buffer の略で、各ピクセルのライティングに必要な情報が書き込まれている2Dテクスチャです。`DecodeGbuffer()` を使うと Gbuffer に詰め込まれた情報を読んで `MaterialParam` に書き込んでくれます。具体的にどのような情報が取れるのかは `gbuffer.fxsub` を読むのがよいでしょう。

```
struct MaterialParam
{
	float3 normal;
	float3 albedo;
	float3 specular;
	float smoothness;
	float alpha;
	float visibility;
	float customDataA;
	float3 customDataB;
	float linearDepth;
	int lightModel;
};
```

## おわりに

簡単にですが、Ray-MMD にポストエフェクトを組み込む方法を紹介しました。ポストエフェクトから Gbuffer の中身を参照できるのは結構面白いと思うので、皆さんもぜひ Ray-MMD にポストエフェクトを組み込んでみてください！