Deferred Rendering


 1.はじめに…
Deferred Rendering(遅延レンダリング)を実装してみました。適当に組んだので全然あっていないかも…
あと普通のDeferred Renderingのデモとかはライトの数が多いと思うのですが,1個1個全部点灯を確認するのが面倒だったので,今回のサンプルのライト数は9個です。



 2.Deferred Rendering
流行っているらしいですね、Deferred Rendering。色々な所で使うようになっているみたいですね。
通常のシェーダで,使えるライトの高々数個です。実際どのぐらい使えるんでしたっけ?8個ぐらいだったけ?まぁ,とにかく数十個ライトがあるようなシーンだと通常のForward Rendering(前方レンダリング)では,使えるライトが数個なので「どうしよっか?」と悩んでしまうわけです。多分,Forward Renderingで真面目にやるとしたら,(使いたいライトの数/シェーダで使えるライトの数)回分の描画が必要です。1回1回ジオメトリ処理をする必要があるので,とても負荷が高くなることが容易に想像できると思います。

そこで出てくるのが,今回紹介するDeferred Rendering!(…なんか通販っぽいセリフだな)
このテクニックを使えば自由にライトの数を増やすことができます。ただし,透過処理を使うものとの相性が悪いので注意。αを使いたい場合は,Forward Renderingを使ってうまくあれこれするみたいです。だけど,Forward Renderingを使うとDeferred Renderingのうまみが減るので,うまく考えて使いましょう。

さて,Deferred Renderingの方法ですが,レンダリングを2パスで行います。
1パス目=ジオメトリパス
2パス目=ライティングパス
ジオメトリパスで,シーン内のポリゴンのレンダリングを先に行い,ライティングを行う際にピクセルシェーダで必要となる計算中間値をGバッファと呼ばれる中間バッファに書き出します。このGバッファへの書き出しする際に使うのが,以前サンプルで書いたMRT(Multiple Rendering Target)です。次のライティングパスで,Gバッファに出力されたデータを用いてライティング処理を行います。
今回のサンプルではGバッファとしてレンダリングターゲットを3つ使用しました。適当なサンプルなので,Gバッファの内訳はかなり適当です。ちゃんとしたゲームとかアプリを作る場合は,制限に縛られると思うので,うまく圧縮したり,法線ベクトルのZ成分が計算から復元できたりするので,うまくGバッファに詰め込みましょう。
さて,コードを見ながら詳細を説明していきましょう。
まずは,MRTの設定をします。
00175:  bool DeferredRenderer::InitializeMRT( const int width, const int height )
00176:  {
00177:      // GLEWの初期化
00178:      if ( glewInit() != GLEW_OK )
00179:      {
00180:          ELOG( "Error : glewInit() Failed." );
00181:          return false;
00182:      }
00183:  
00184:      // 浮動小数点テクスチャが使えるかチェック
00185:      if ( !GLEW_ARB_texture_float )
00186:      {
00187:          ELOG( "Error : ARB_texture_float is not supported." );
00188:          return false;
00189:      }
00190:  
00191:      // テクスチャ3枚生成
00192:      glGenTextures( NUM_GBUFFER, mTexture );
00193:  
00194:      // 3枚分の設定をしておく
00195:      glEnable( GL_TEXTURE_2D );
00196:      for( int i=0; i<NUM_GBUFFER; i++ )
00197:      {
00198:          glBindTexture( GL_TEXTURE_2D, mTexture[i] );
00199:          glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
00200:      	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
00201:      	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
00202:      	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
00203:          glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0 );
00204:          glBindTexture( GL_TEXTURE_2D, 0 );
00205:      }
00206:      glDisable( GL_TEXTURE_2D );
00207:      assert( !GL_ERROR_CHECK );
00208:  
00209:      // レンダーバッファの設定
00210:      glGenRenderbuffersEXT( 1, &mRenderBuffer );
00211:      glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, mRenderBuffer );
00212:      glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, width, height );
00213:  
00214:      // フレームバッファの設定
00215:      glGenFramebuffersEXT( 1, &mFrameBuffer );
00216:      glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, mFrameBuffer );
00217:      glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, mTexture[0], 0 );
00218:      glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, mTexture[1], 0 );
00219:      glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT2_EXT, GL_TEXTURE_2D, mTexture[2], 0 );
00220:      glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, mRenderBuffer );
00221:      glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
00222:      assert( !GL_ERROR_CHECK );
00223:  
00224:      return true;
00225:  }
一応今回のサンプルでは,3枚分をGバッファとして用意してあります。あとテクスチャは浮動小数点テクスチャ使ってみました。
MRTの設定ができたら,まずGバッファにデータを書き込みます。
00286:  void DeferredRenderer::Render()
00287:  {
00288:      //========================================
00289:      // pass1. ジオメトリ処理
00290:      //========================================
00291:  
00292:      // ワールド行列計算
00293:      Matrix world;
00294:      world.Identity();
00295:  
00296:      // ビュー行列計算
00297:      mCamera.Compute();
00298:  
00299:      // 射影行列計算
00300:      Matrix proj = Math::CreatePerspectiveFieldOfView( Math::PiOver4, DemoApp::GetInstance()->GetAspectRatio(), 0.1f, 1000.0f );
00301:  
00302:      // パラメータ転送
00303:      cgSetMatrixParameterfr( mParam.projection, proj );
00304:      cgSetMatrixParameterfr( mParam.view, mCamera.GetView() );
00305:      cgSetParameter3fv( mParam.cameraPosition, mCamera.GetPosition() );
00306:  
00307:      glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, mFrameBuffer );
00308:      GLuint attachments[ 3 ] = 
00309:      {
00310:          GL_COLOR_ATTACHMENT0_EXT,
00311:          GL_COLOR_ATTACHMENT1_EXT,
00312:          GL_COLOR_ATTACHMENT2_EXT
00313:      };
00314:      glDrawBuffers( 3, attachments );
00315:    
00316:      // バッファクリア
00317:      glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
00318:      glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
00319:  
00320:      // ビューポート設定
00321:      glViewport( 0, 0, (GLsizei)FRAME_BUFFER_WIDTH, (GLsizei)FRAME_BUFFER_HEIGHT );
00322:  
00323:      assert( !GL_ERROR_CHECK );
00324:    
00325:      // ジオメトリパス
00326:      {
00327:         // パスステートを設定
00328:          cgSetPassState( mpEffect->GetPass( 0, "Geometry" ) );
00329:  
00330:          // モデル描画
00331:          cgSetMatrixParameterfr( mParam.world, world );
00332:          mSaturn.Draw();
00333:  
00334:          world = Math::CreateScale( 5.0f ) * Math::CreateTranslation( 0.0f, -1.0f, 0.0f );
00335:          cgSetMatrixParameterfr( mParam.world, world );
00336:          mGround.Draw();
00337:  
00338:          // パスステートをリセット
00339:          cgResetPassState( mpEffect->GetPass( 0, "Geometry" ) );
00340:      }
00341:  
00342:      glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
00343:      
00344:      // 呼ばなくてもいいんだけど,念のため
00345:      glFlush();
00346:  
00347:      // Gバッファ転送
00348:      cgGLSetTextureParameter( mParam.positionSmp, mTexture[ GBUFFER_1 ] );
00349:      cgGLSetTextureParameter( mParam.normalDepthSmp, mTexture[ GBUFFER_2 ] );
00350:      cgGLSetTextureParameter( mParam.diffuseAlbedoSmp, mTexture[ GBUFFER_3 ] );
00351:  
やっていることは大したことないですね。glDrawBuffers()で関連づけさせておいて,描画するだけです。
ジオメトリパスのシェーダ処理は下記のような感じにしてみました。
00030:  /////////////////////////////////////////////////////////////////////////
00031:  // GeometryVSInput structure
00032:  /////////////////////////////////////////////////////////////////////////
00033:  struct GeometryVSInput
00034:  {
00035:      float4 Position : POSITION;
00036:      float2 TexCoord : TEXCOORD0;
00037:      float3 Normal   : NORMAL;
00038:  };
00039:  
00040:  /////////////////////////////////////////////////////////////////////////
00041:  // GeometryVSOutput structure
00042:  /////////////////////////////////////////////////////////////////////////
00043:  struct GeometryVSOutput
00044:  {
00045:      float4 Position      : POSITION;
00046:      float2 TexCoord      : TEXCOORD0;
00047:      float4 WorldPosition : TEXCOORD1;
00048:      float3 WorldNormal   : TEXCOORD2;
00049:      float4 DiffuseColor  : TEXCOORD3;
00050:  };
00051:  
00052:  /////////////////////////////////////////////////////////////////////////
00053:  // GeometryPSOutput structure
00054:  /////////////////////////////////////////////////////////////////////////
00055:  struct GeometryPSOutput
00056:  {
00057:      float4 Position      : COLOR0;
00058:      float4 NormalDepth   : COLOR1;
00059:      float4 DiffuseAlbedo : COLOR2;
00060:  };
00061:  
00080:  //-----------------------------------------------------------------------
00081:  // Name : GeometryVSFunc()
00082:  // Desc : 遅延レンダリング - ジオメトリパス頂点処理
00083:  //-----------------------------------------------------------------------
00084:  GeometryVSOutput GeometryVSFunc( const GeometryVSInput input )
00085:  {
00086:      GeometryVSOutput output = (GeometryVSOutput)0;
00087:  
00088:      float4 worldPos = mul( input.Position, World );
00089:      float4 viewPos  = mul( worldPos, View  );
00090:      float4 projPos  = mul( viewPos, Projection );
00091:  
00092:      float3 worldNormal = mul( input.Normal, (float3x3)World );
00093:   
00094:      output.Position      = projPos;
00095:      output.TexCoord      = input.TexCoord;
00096:      output.WorldNormal   = worldNormal;
00097:      output.WorldPosition = worldPos;
00098:      output.DiffuseColor  = glstate.material.diffuse;
00099:  
00100:      return output;
00101:  }
00102:  
00103:  //-----------------------------------------------------------------------
00104:  // Name : GeometryPSFunc()
00105:  // Desc : 遅延レンダリング - ジオメトリパスピクセル処理
00106:  //-----------------------------------------------------------------------
00107:  GeometryPSOutput GeometryPSFunc( const GeometryVSOutput input )
00108:  {
00109:      GeometryPSOutput output = (GeometryPSOutput)0;
00110:  
00111:      float depth   = input.WorldPosition.z / input.WorldPosition.w;
00112:  
00113:      // -1~1までの範囲を0~1に変換
00114:      float3 normal = normalize( input.WorldNormal ) * 0.5f + 0.5f;
00115:  
00116:      output.Position      = input.WorldPosition;
00117:      output.NormalDepth   = float4( normal, depth );
00118:      output.DiffuseAlbedo = input.DiffuseColor;
00119:  
00120:      return output;
00121:  }
シェーダも大したことやっていないです。単に来たデータをそのまま出力しているだけですね。
続いて,ライティングパスです。
00353:      //========================================
00354:      // pass2. ライティング処理
00355:      //========================================
00356:  
00357:      // バッファクリア
00358:      glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
00359:  
00360:      // ビューポート設定
00361:      glViewport( 0, 0, DemoApp::GetInstance()->GetWidth(), DemoApp::GetInstance()->GetHeight() );
00362:     
00363:      // ライティングパス
00364:      {
00365:          cgSetPassState( mpEffect->GetPass( 0, "Lighting" ) );
00366:   
00367:          cgGLSetParameterArray4f( mParam.lightPostion, 0, NUM_LIGHT, &mLightPosition[0].x );
00368:          cgGLSetParameterArray3f( mParam.lightColor, 0, NUM_LIGHT, &mLightColor[0].x );
00369:  
00370:          DrawRect();
00371:   
00372:          cgResetPassState( mpEffect->GetPass( 0, "Lighting" ) );
00373:      }
00374:  
00375:      assert( !GL_ERROR_CHECK );
00376:  }
00377:  
3枚のテクスチャをシェーダに転送して,あとはライティングを行うためのライト位置・ライトカラーの配列をシェーダに転送してテクスチャを描画するために矩形を書いておくだけです。
ライティングパスのシェーダは下のような感じです。
00062:  /////////////////////////////////////////////////////////////////////////
00063:  // LightVSInput structure
00064:  /////////////////////////////////////////////////////////////////////////
00065:  struct LightVSInput
00066:  {
00067:      float4 Position : POSITION;
00068:      float2 TexCoord : TEXCOORD0;
00069:  };
00070:  
00071:  /////////////////////////////////////////////////////////////////////////
00072:  // LightVSOutput structure
00073:  /////////////////////////////////////////////////////////////////////////
00074:  struct LightVSOutput
00075:  {
00076:      float4 Position : POSITION;
00077:      float2 TexCoord : TEXCOORD0;
00078:  };
00079:  
00123:  //-----------------------------------------------------------------------
00124:  // Name : LightVSFunc()
00125:  // Desc : 遅延レンダリング - ライティングパス頂点処理
00126:  //-----------------------------------------------------------------------
00127:  LightVSOutput LightVSFunc( const LightVSInput input )
00128:  {
00129:      LightVSOutput output = (LightVSOutput)0;
00130:  
00131:      output.Position = input.Position;
00132:      output.TexCoord = input.TexCoord;
00133:  
00134:      return output;
00135:  }
00136:  
00137:  //------------------------------------------------------------------------
00138:  // Name : LightPSFunc()
00139:  // Desc : 遅延レンダリング - ライティングパスピクセル処理
00140:  //------------------------------------------------------------------------
00141:  float4 LightPSFunc( const LightVSOutput input ) : COLOR0
00142:  {
00143:      float3 WorldPosition = tex2D( PositionSmp, input.TexCoord ).rgb;
00144:      float3 WorldNormal = float3( 0, 0, 0 );
00145:      float  Depth = 0;
00146:      {
00147:          float4 normalDepth = tex2D( NormalDepthSmp, input.TexCoord );
00148:          WorldNormal   = normalDepth.rgb * 2.0f - 1.0f;
00149:          Depth         = normalDepth.a;
00150:      }
00151:  
00152:      float4 Color    = float4( 0, 0, 0, 0 );
00153:      float3 EyeDir   = normalize( CameraPosition - WorldPosition );
00154:      float4 Diffuse = tex2D( DiffuseAlbedoSmp, input.TexCoord );
00155:  
00156:      for( int i=0; i<NUM_LIGHT; ++i )
00157:      {
00158:          float3 LightDir = LightPosition[ i ].xyz - WorldPosition;
00159:          float3 L = normalize( LightDir );
00160:          float  attenution = 1.0f / length( LightDir );
00161:          Color.rgb += attenution * LightColor[ i ] * Diffuse.rgb * max( dot( L, WorldNormal ), 0 );
00162:          Color.a   = 1.0f;
00163:      }
00164:  
00165:      return Color;  
00166:  }
00167:  
スポットライトみたいにしたかったので,減衰させる係数attenutionを入れてありますが,よく見るライティング処理なので複雑な処理もなく問題ないでしょう。
…ってなわけで,Deferred Renderingやってみました。適当に作ったので,間違っていたらごめんなさい&正しいやり方を教えて下さいm(_ _)m。


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







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