反射マッピング


 1.はじめに…
 ブログにて、よしむさんから
「図々しいですがキューブマップなどの環境マップ系なども教えていただけたらな〜と思っています。」
…というリクエストがあったので、今回はキューブマップを使った反射エフェクトです。
なんか、作った結果がかな〜りあやしいですが…一応、公開しておきます。




 2.準備 その1. スカイボックス
キューブ環境マッピングを利用して、物体に周囲の風景を映り込ませます。
いつも通りの市松模様の地面を映り込ませてもつまらないと思ったので、今回はスカイボックスを用意します。
スカイボックス描画用のキューブマップは以下のものを利用しました。
キューブマップは、Terragen1とDirectX Texture Toolを利用して作成しました。


上記の用意したキューブマップを、キューブ型のモデルに貼り付けます。用意するキューブ型のモデルは、下の図のようにモデル内から壁が見えるように面を反転させておきました。



あとは、スカイボックス内にモデルを描画すれば、以下のような結果が得られます。



これで、スカイボックスが完成です。


 3.準備 その2. 動的キューブマップ
上記でスカイボックスを描画するためにキューブマップを作成してあるので、そのままキューブマップをペタペタ貼りつけて、反射エフェクトを適用してもいいのですが…
シーンに合わせて動的にキューブ環境マップを作成するようなものを用意しておけば、後々ゲーム作成などにも応用が利きそうです。
そんなわけで、動的キューブマップを作成します。
基本的には、RenderTargetCubeを用意しておきキューブの各面の方向見たときの風景をテクスチャに描画すればよいです。
各面への描画は下のような感じで行います。

00104:              ///--------------------------------------------------------------
00105:              /// <summary>
00106:              /// キューブマップの各面に描画を行う
00107:              /// </summary>
00108:              ///--------------------------------------------------------------
00109:              public void DrawToTexture()
00110:              {
00111:                  Vector3 target = Vector3.Zero;
00112:  
00113:                  //各面に対して処理
00114:                  for (int index = 0; index < NumFaces; index++)
00115:                  {
00116:                      CubeMapFace cubeMapface = (CubeMapFace)index;
00117:                      switch (cubeMapface)
00118:                      {
00119:                          case CubeMapFace.PositiveX:
00120:                              {
00121:                                  target = mPosition + Vector3.UnitX;
00122:                                  mView = Matrix.CreateLookAt(mPosition, target, Vector3.Up);
00123:                              }
00124:                              break;
00125:  
00126:                          case CubeMapFace.NegativeX:
00127:                              {
00128:                                  target = mPosition - Vector3.UnitX;
00129:                                  mView = Matrix.CreateLookAt(mPosition, target, Vector3.Up);
00130:                              }
00131:                              break;
00132:  
00133:                          case CubeMapFace.PositiveY:
00134:                              {
00135:                                  target = mPosition + Vector3.UnitY;
00136:                                  mView = Matrix.CreateLookAt(mPosition, target, -Vector3.UnitZ);
00137:                              }
00138:                              break;
00139:  
00140:                          case CubeMapFace.NegativeY:
00141:                              {
00142:                                  target = mPosition - Vector3.UnitY;
00143:                                  mView = Matrix.CreateLookAt(mPosition, target, Vector3.UnitZ);
00144:                              }
00145:                              break;
00146:  
00147:                          case CubeMapFace.PositiveZ:
00148:                              {
00149:                                  target = mPosition + Vector3.UnitZ;
00150:                                  mView = Matrix.CreateLookAt(mPosition, target, Vector3.Up);
00151:                              }
00152:                              break;
00153:  
00154:                          case CubeMapFace.NegativeZ:
00155:                              {
00156:                                  target = mPosition - Vector3.UnitZ;
00157:                                  mView = Matrix.CreateLookAt(mPosition, target, Vector3.Up);
00158:                              }
00159:                              break;
00160:  
00161:                          default:
00162:                              {
00163:                                  /* defaultは通らないが念のため /*
00164:                                  mView = Matrix.CreateLookAt(mPosition, Vector3.Zero, Vector3.Up);
00165:                              }
00166:                              break;
00167:                      }
00168:  
00169:                      //描画する面をレンダーターゲットに指定
00170:                      mDevice.SetRenderTarget(0, mRenderTarget, cubeMapface);
00171:  
00172:                      //バッファをクリア
00173:                      mDevice.Clear(Color.White);
00174:  
00175:                      //描画メソッドを呼び出し
00176:                      mDrawBackGroundMethod();
00177:  
00178:                      //レンダーターゲットをもとに戻す
00179:                      mDevice.SetRenderTarget(0, null);
00180:                  }
00181:  
00182:                  //テクスチャを取得し,格納
00183:                  mCubeMap = mRenderTarget.GetTexture();
00184:              }
				
上記コードで、動的にキューブマップが作成できます。
これで、準備完了です。


 4.反射エフェクト
反射エフェクトですが、C#側はモデルを描画したり、キューブマップ・反射率等のパラメータを転送するだけなので、問題ないと思いますので省略。
大事なのは、シェーダ側の処理です。
反射エフェクトの原理ですが、モデルの法線ベクトルと視線ベクトルを元に、反射ベクトルを求めます。求めた反射ベクトルを用いて、キューブ環境マップを参照し、色を取得して反映させる。…というのが原理のようです。



反射ベクトルは上図のように R = 2(E・N)N - E で求められます。…が、HLSLにreflect()という関数があるので、この関数を使って求めることにします。
実際の反射エフェクトのコードですが以下のようになります。
00063:  //----------------------------------------------------------------------
00064:  // Name : VertexShaderFunction()
00065:  // Desc : 頂点シェーダ.
00066:  //----------------------------------------------------------------------
00067:  VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
00068:  {
00069:      VertexShaderOutput output = (VertexShaderOutput)0;
00070:  
00071:      float4 worldPosition = mul(input.Position, World);
00072:      float4 viewPosition = mul(worldPosition, View);
00073:      output.Position = mul(viewPosition, Projection);
00074:  
00075:      // TODO: add your vertex shader code here.
00076:      float3 worldNormal = mul(input.Normal, World);
00077:      float3 viewNormal = mul(worldNormal, View);
00078:  
00079:      output.Reflection = reflect( normalize(viewPosition), normalize(viewNormal) );
00080:      output.Normal = mul( input.Normal, World );
00081:      output.Light = LightPosition.xyz - worldPosition.xyz;
00082:  
00083:      return output;
00084:  }
00085:  
00086:  //----------------------------------------------------------------------
00087:  // Name : PixelShaderFunction()
00088:  // Desc : ピクセルシェーダ.
00089:  //----------------------------------------------------------------------
00090:  float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
00091:  {
00092:      // TODO: add your pixel shader code here.
00093:      float4 texcolor = texCUBE(EnvSmp, input.Reflection);
00094:  
00095:      float3 L = normalize(input.Light);
00096:      float3 N = normalize(input.Normal);
00097:      float3 diffuse = ModelDiffuseColor * (max( dot(N, L), 0.0f) * 0.5f + 0.5f);
00098:      float4 result = float4( lerp(diffuse, texcolor, Reflective), 1.0f );
00099:  
00100:      return result;
00101:  }
				
頂点シェーダでやってるのは、反射ベクトルを求めてピクセルシェーダ側に送っている処理です。あとは、Halfランバート用に法線やらライトやらのベクトルを構造体に格納しているだけです。
ピクセルシェーダ側では、送られてきた反射ベクトルを元にキューブ環境マップを参照します。その後、Halfランバートを計算した結果との線形補間を実行し、計算結果を返却しています。
そんなわけで、かな〜り結果はあやしいですが一応キューブ環境マップを使って、反射エフェクトを実装してみました。実はシェーダの実装よりも背景描画やら動的キューブマップの作成の方が時間がかかっていたりしますw
この程度のサンプルであれば問題はないと思うのですが…やっぱり、6回分背景を描画するっていうのはいかがなものかなぁ〜と思いました。
そのうち、例のやつを実装してみたいと思います。


 Download
本ソースコードおよびプログラムを使用したことによる如何なる損害も製作者は責任を負いません。
本ソースコードおよびプログラムは自己責任でご使用ください。
プログラムの作成にはMicrosoft Visual Studio 2008 SP1 Professional,およびXNA Game Studio 3.1を用いています。







Flashを利用するためにはPluginが必要です。 <!-- </body> -->