Multiple Render Target - MRT
About
※サンプルがXNAベースですが概念的にはDirectXなどでも共通の話題です。
「Multiple Render Target(MRT)」は、一度の実行で複数の出力先に描画する技術のことを指します。もし同一の反復処理の上で得られる結果が複数必要であるなら、2度も3度も同じ処理を繰り返してレンダリングするのは非効率でパフォーマンスを著しく落とします。そういった場合に、一度の実行で完結するように、出力先を複数用意しておくのがMRTです。分かり難い場合は次のソースコードの様な状況を想像してください。普通は下のパターンで実装しますよね? これがCGのレンダリング実行時にも言えるわけです。
int[] dataA = new int[10]; int[] dataB = new int[10]; //【×パターン】こう書くよりは for(int i=0; i<10; i++) dataA[i] = 0; for(int i=0; i<10; i++) dataB[i] = 1; //【○パターン】こうするのが普通 for(int i=0; i<10; i++) { dataA[i] = 0; dataB[i] = 1; }
今回のサンプルプログラムの実行結果が下の図です。普通の色情報を出力するパターンと、灰色の色情報を出力するパターンとを1度のレンダリングで完了しています。MRTを使わなければそれぞれ1度ずつ描画することになるわけです。
- WindowsGame1_MRT.zip
- VisualStudio2010
- XNA4.0
How to
出力先の用意
まずは複数の出力先を用意します。ここではXNAベースでサンプルを提供していますが、環境に応じて出力先を用意することができるので調査されたい。XNAでは「RendarTarget2D」クラスが用意されています。単純に、何も描かれていない透明なキャンバスを用意した、と想像すれば良いと思います。ここではMRTのために2つ用意しておきます。またその出力先をまとめて管理する「RendarTargetBinding」クラスのインスタンスもここでは用意しておきます。
//複数の出力先を用意する RenderTarget2D rTarget1; RenderTarget2D rTarget2; RenderTargetBinding[] rTargets;
RenderTarget2Dに大きさなどの情報を与えて初期化しておきます。
//出力先を生成します rTarget1 = new RenderTarget2D(GraphicsDevice, tex2d_Lena.Width, tex2d_Lena.Height, false, SurfaceFormat.Color, DepthFormat.Depth24); //2つ目の出力先 rTarget2 = new RenderTarget2D(GraphicsDevice, tex2d_Lena.Width, tex2d_Lena.Height, false, SurfaceFormat.Color, DepthFormat.Depth24); //出力先情報をまとめて管理することができます rTargets = new RenderTargetBinding[2] { rTarget1,rTarget2 };
複数の出力先を設定する
先にXNAで描画先(RendarTarget)に複数の出力先を設定する方法について解説します。とはいえそれ程難しいことはなく「GrphicsDevice.SetRendarTargetsメソッド」に先にRendarTargetをまとめた「RenderTargetBinding」を指定するだけです。その状態で、複数の描画先に出力する様に設計されたEffectを使って描画を実行すると、それぞれのRendarTargetに結果が与えられています。他の環境下であってもRendartargetを指定することはそれほど難しくはないハズです。
後は得られた結果(RendarTarget)を今まで通り描画するのみです。次の項目ではEffect(シェーダファイル)でどのようにすれば複数の出力先に結果を出力することができるのか、を解説します。
//描画結果の出力先を、複数のRenderTargetをまとめた物にします GraphicsDevice.SetRenderTargets(rTargets); GraphicsDevice.Clear(Color.CornflowerBlue); //Effectを使って、設定した複数のRenderTargetにテクスチャをそれぞれ描画します spriteBatch.Begin(SpriteSortMode.Deferred,null,null,null,null,effect); spriteBatch.Draw(tex2d_Lena,Vector2.Zero,Color.White); spriteBatch.End(); //Rendartargetをスクリーンに戻します //≒描画結果の出力先をスクリーンにします。 GraphicsDevice.SetRenderTargets(null); GraphicsDevice.Clear(Color.CornflowerBlue); //先にEffectを使って描画した結果をスクリーン上に描画します。 spriteBatch.Begin(); spriteBatch.Draw(rTarget1, Vector2.Zero, Color.White); spriteBatch.Draw(rTarget2,new Vector2(tex2d_Lena.Width,0),Color.White); spriteBatch.End();
シェーダ(Effect)内で複数の出力先を定義する
最終出力を複数にするわけですから、基本的にはPixelShaderがあれば完結できるわけです。今回のサンプルも例にもれずPixelShaderのみ用意してあります。コアとなる部分は「PixelShaderOutput構造体」です。構造体に与えられる複数の値の中に、出力情報を定義します。
次のサンプルソースコードで言えば「COLOR0」と(色情報のセマンティクスが)設定された値は、0番目の色情報出力になります。一方で「COLOR1」と設定された値は、1番目の色情報出力として管理されます。セマンティクスに設定する番号によって、出力先が管理される、ということです。
//最終的にPixelShaderから出力するデータを示す構造体 struct PixelShaderOutput { float4 rgbColor:COLOR0;//RendarTargetの0番目に出力 float4 grayColor:COLOR1;//RendarTargetの1番目に出力 }; PixelShaderOutput PixelShaderFunction(float2 texCoord:TEXCOORD) { float4 color = tex2D(ScreenTexSampler,texCoord); //最終的に出力するデータを作成 PixelShaderOutput result; //RGBカラー(0番目の出力)にはそのまま result.rgbColor = color; //Grayカラー(1番目の出力)には色変換した値を与える float gray = (color.r + color.g + color.b)/3; result.grayColor = float4(gray,gray,gray,1); //最終出力を返す return result; }
ここで誤って重複するセマンティクス番号を指定してしまうと、データが上書きされてしまいます。