見出し画像

【MMEの作り方】ポストエフェクト編03/深度バッファ

▼今回の記事ページで使うサンプルファイル

 今回の記事ぺージの解説で使うサンプルとなるfxファイルは以下のリンクからダウンロードできます。
 ただし、ファイルをDL・使用した際にトラブル・不利益が生じたとしても筆者は責任を負わないものとします。自己責任で御利用ください。

▼深度バッファもポストエフェクトでよく使う

 前回の記事では、ポストエフェクト処理でよく使うぼかし表現を実装するためにガウスぼかしについて解説しました。
 しかし、ポストエフェクト系の処理に関してはもう一つよく使われる手法があります。
 それが深度バッファを使った手法です。

 深度バッファとは、3D空間にあるオブジェクトが特定のオブジェクト(カメラや照明)からどれだけ離れた位置にあるかの情報です。
 以下の画像は「カメラから離れているオブジェクトの大きな値を取る(色が白くなる)」という情報を取得してみた例です。

深度バッファ

 例えば、カメラからの距離の情報を取得できると「カメラの近くにあるモノはくっきり見えるけど、奥にあるものには霧がかかっている」みたいなエフェクトを作れます。
 実際に作ってみた様子が以下の画像です。

霧(フォグ)エフェクト

 霧だけでなく、「深度バッファの情報に対して特定の値から離れた場所にあるものに対してはぼかし表現を加える」という処理をすると被写界深度(カメラのピンぼけ)表現が出来るなどもあります。

被写界深度エフェクト

 このように深度バッファがとれると制作できるポストエフェクトの幅が広がります。

▼コードの中身

 では、上記の画像のような霧を画面に入れられるファイルの中身を見ていきましょう。
 サンプルファイル『3-3-1_PE_Fog_Main.x』を背景を設置してあるMMDに入れてみてください。
 今回も作例に使った背景モデルは当方作の『神社ステージ』です(ダイレクトマーケティング)

 今回はXファイルを動かすためのfxファイルはメインとサブの2種類あります。
 メインで主要な画像処理を行い、サブでは深度バッファを取得する処理を行っています。

■メインファイルの処理:外部ファイルの取得

 まずメインファイル(3-3-1_PE_Fog_Main.fx)の処理を見ていきましょう。

 メインファイルでまず見てほしいのは、外部にあるサブファイル(3-3-1_PE_Fog_Main.fx)を呼び出す処理です。

// サブの外部ファイルで生成した深度バッファの格納先
// こちらの値をこのファイル内で使う
texture DOFMapRT : OFFSCREENRENDERTARGET <
	float2 ViewPortRatio = { 1,1 };
string Format = "D3DFMT_A16B16G16R16F";
float4 ClearColor = { 0, 0, 0, 1 };
float ClearDepth = 1.0f;
bool AntiAlias = false;
string DefaultEffect = "self = hide;" "* = 3-3-1_PE_Fog_Sub.fx;";
> ;
sampler ScnSampRT = sampler_state {
	texture = <DOFMapRT>;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
	MipFilter = LINEAR;
	AddressU = CLAMP;
	AddressV = CLAMP;
};

 コード内に『string DefaultEffect = "self = hide;" "* = 3-3-1_PE_Fog_Sub.fx;";』とあるように、ここで使用する外部ファイルの名前を指定しています。

 で、MME初学者に対してのトラップになるのが、

texture DepthBuffer : RENDERDEPTHSTENCILTARGET <
	float2 ViewPortRatio = { 1,1 };
> ;

 という記述です。

 これはMME内でテクスチャと紐づけてあるfxファイルを動かすためには使われる記述ではあります。
 しかも名前に『DepthBuffer』つまり深度バッファとあるので「これで深度バッファの情報をプログラム内で使えるのでは?」という勘違いを招きます。

 しかし。
 MMEの仕様書には、『RENDERDEPTHSTENCILTARGET』という指定に関して、
『このセマンティクスで生成したテクスチャは、レンダリング後も、内容を参照することはできない。』
 と明記してあります。

 そうです。これを使っても深度バッファを取得できません。
(それに気づかずに当方は2日ほど沼りました)
 
 なので外部ファイルを使って深度バッファを取得して、それを使用するという形になっています。

■メインファイルの処理:深度バッファの使用

 今回もガウスぼかしのときと同じように、ピクセルシェーダーが複数個あります。
①まず元画像を取得
②元画像に横軸方向のぼかしを入れる
③横軸をぼかした画像に深度バッファを使って霧を入れる
 という処理を行っています(今回は話の本筋ではないのでガウスぼかしは横軸を入れるだけの簡略化したものになっています)。

 では、コード内の深度バッファを使って霧を入れる処理をみてみましょう。

float4 Basic_PS(float2 Tex: TEXCOORD0) : COLOR0
{
	float4 Color = float4 (0, 0, 0, 1);

	float4 OriginColor = tex2D(ScnSamp2, Tex);
	float  FogFactor   = (1 - tex2D(ScnSampRT, Tex).r) * Visibility;

	float4 ResultColor = lerp(FogColor, OriginColor, FogFactor) ;


	return ResultColor;

 コード内の『ScnSampRT』が深度バッファが格納されているテクスチャの場所になります。
 つまり、この中の画像を使って深度バッファを取っているわけです。
 あとは、lerp演算子を使って元の画像に対して霧の色(この場合は白)を入れているわけです。

■サブファイルの処理

 続いて、深度バッファを取るための記述がされているサブファイル(3-3-1_PE_Fog_Sub.fx)についてです。

 まず頂点シェーダーに関して

    // カメラとの相対位置
    Out.Eye = CameraPos - (mul(Pos, World)).xyz;

 これについてはコメントアウトのメモ書き通り、この式でカメラとの相対的な位置を定義しています。

 次に、ピクセルシェーダーの以下の部分が深度バッファに関する処理です。

	float3 Focus = pow(FogPos_Re - CameraPos, 2);
	Color.r = sqrt(Eye.x * Eye.x + Eye.y * Eye.y + Eye.z * Eye.z) / sqrt(Focus.x + Focus.y + Focus.z);
	Color.r = Color.r < 0 ? -Color.r * 2 : Color.r;

 それぞれの行に関して解説すると、

float3 Focus = pow(FogPos_Re - CameraPos, 2);

 カメラとフォグ基準点の距離の2乗を各軸ごとに求めています。

Color.r = sqrt(Eye.x * Eye.x + Eye.y * Eye.y + Eye.z * Eye.z) / sqrt(Focus.x + Focus.y + Focus.z);

 これは、
分子部分 ⇒ カメラからピクセルまでの実際の距離

分母部分 ⇒ カメラからフォグ基準点までの距離

つまりこの式は、相対的な深度(0~1の範囲に正規化された距離)を表しています。

 最後に、

Color.r = Color.r < 0 ? -Color.r * 2 : Color.r;

 の部分。
 これは負の距離が発生した場合の補正です。
 理論上は距離は常に正の値ですが、行列の向きやカメラ座標の処理順序によって
 Eyeベクトルが負の向きを持つことがあります。
 その場合でも深度が正しい方向に出るよう、負値を正値に反転して補正しています。
 また、2を乗算しているのは、近すぎる部分を少し強調して深度の勾配を見やすくするための補正値です。

目次ぺージに戻る


いいなと思ったら応援しよう!