ホーム < ゲームつくろー! < DirectX技術編 < 深度バッファシャドウの根っこ:影を描画してみる:サンプルプログラム

その46 深度バッファシャドウの根っこ:影を描画してみる:サンプルプログラム



 DirectX技術編その46「深度バッファシャドウの根っこ:影を描画してみる」で説明した内容を踏まえたサンプルプログラムです。実行すると、整列したキューブが上下し、その影がプレートに投影されます。


サンプルスクリーンショット。プレートに影が映っているのがわかります。実はキューブにもセルフシャドウが発生しています。

左上には同時にZ値テクスチャも描画しています。


○ コンパイル方法

 最下に挙げたサンプルプログラムをコンパイルするには、次のファイルをパスの通ったフォルダに置きます。

Z値テクスチャ作成クラス関連
P ・ ZTexCreator.h  Z値テクスチャ作成クラス宣言
P ・ ZTexCreator.cpp  Z値テクスチャ作成クラス実装
P ・ ZTexCreator.rc  Z値テクスチャ作成クラス用のリソースファイル
・ ZTexCreator.fx  Z値プロットシェーダプログラム
深度バッファシャドウエフェクトクラス関連
P ・DepthBufShadowEffect.h  深度バッファシャドウエフェクトクラス宣言
P ・DepthBufShadowEffect.cpp  深度バッファシャドウエフェクトクラス実装
P ・DepthBufShadowEffect.rc  深度バッファシャドウエフェクトクラス用のリソースファイル
・DepthBufShadowEffect.fx  深度バッファシャドウエフェクトシェーダプログラム
その他
P ・ EffectResource.h  エフェクトリソースヘッダー
P ・ comptr.h  COMポインタクラス
P ・ main.cpp  メイン部分
・ Cube2.x  立方体Xファイル
・ Plate.x  板Xファイル

上記必要ファイルはすべてこちらからダウンロードできます。

 Xファイルとエフェクトファイル以外をプロジェクトに追加すればコンパイルできます(追加するファイルを「P」で示しています)。実行形式も入っておりますので、まずは実行してみて描画を確認してみて下さい。

○ プログラムで遊ぶポイント

 Z値テクスチャを1パス目で作成しています。そのテクスチャは深度バッファシャドウエフェクトクラスのインスタンスに予め渡していますので、2パス目で同じようなレンダリングをすれば、影が描画されます。これらの作業の殆どはクラスが行ってくれているので、メインは描画部分に集中しています。

 キューブやプレートのワールド位置はGetCubeWorldMatrix関数及びGetPlateWorldMatrix関数で毎回取得しています。ここは物凄く適当に作っています。双方ともワールド変換行列を出力すれば良いので、色々と数値を触ったりして遊べるところです。

 ライトのビューの結果は左上に描画しています。ここに描画されているオブジェクトが見切れると、シェーダプログラム内でカメラが見ている頂点に対応するZ値が無くなります。つまり比較する正しいZ値が無い(本当はあります)ため、影が生成されなくなります。わざとそういう状態を作ってみると、深度バッファシャドウの性質が良くわかると思います。

 ライトの射影変換行列はカメラと同じにする必要は一切ありません。ライト目線は広い部分を含めた方が良いため、視野角度を広くするのが有効ですが、あまり広くし過ぎるとライトからちょっと離れるだけでず〜っと向こうに行ってしまう現象が起こります。これはZ値の解像度を下げてしまうために、結果としてレンダリングされる影の見栄えが落ちます。そういうことも体験してみると良いかと思います。


 以下はメイン部のプログラムです。メインは上からデバッグしていくと、何をしているか良くわかるように1つのソースで完結させています。今回のポイントになりそうなところは太文字で示されています。

(もしうまく動かない、プログラムへの組み込み方がわからない等ございましたら掲示板にてご連絡下さい)

/// 深度バッファシャドウサンプルプログラム

#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")

#include <windows.h>
#include <tchar.h>
#include <d3d9.h>
#include <d3dx9.h>
#include "Comptr.h"
#include <crtdbg.h>
#include "ZTexCreator.h"
#include "DepthBufShadowEffect.h"

using namespace IKD;


_TCHAR gName[100] = _T("深度バッファシャドウサンプルプログラム");


// 板のワールド変換行列生成
void GetPlateWorldMatrix( D3DXMATRIX *PlateWorld )
{
   float PlateScale = 1.0f;
   D3DXMATRIX Scale;
   D3DXMatrixIdentity( PlateWorld );
   D3DXMatrixScaling( &Scale, PlateScale, 1.0f, PlateScale);
   *PlateWorld *= Scale;
   PlateWorld->_42 = -60.0f;
}


// 立方体のワールド変換行列生成
void GetCubeWorldMatrix( float f, int x, int z, D3DXMATRIX *mat)
{
   D3DXMATRIX RotY, RotZ;
   D3DXMatrixIdentity( mat );
   D3DXMatrixRotationY( &RotY, D3DXToRadian( f ) );
   D3DXMatrixRotationZ( &RotZ, D3DXToRadian( f*2.353f ) );
   *mat *= RotY * RotZ;
   mat->_41 = x*20.0f;  mat->_43 = z*20.0f; mat->_42=sin(f/10)*40;
}


// ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam){
   switch( mes )
   {
   case WM_DESTROY:
      PostQuitMessage(0);
      break;
   }
   return DefWindowProc(hWnd, mes, wParam, lParam);
}


int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
   _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

   /////////////////////////////////////
   // アプリケーションの初期化

   MSG msg; HWND hWnd;
   WNDCLASSEX wcex ={sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInstance, NULL, NULL,
                           (HBRUSH)(COLOR_WINDOW+1), NULL, (_TCHAR*)gName, NULL};
   if(!RegisterClassEx(&wcex))
      return 0;

   int ClientSize = 600;
   DWORD WndStyle = WS_OVERLAPPEDWINDOW & ~(WS_MAXIMIZEBOX | WS_SIZEBOX);
   RECT WndRect={0, 0, ClientSize, ClientSize};
   AdjustWindowRect( &WndRect, WndStyle, false );

   if(!(hWnd = CreateWindow( gName, gName, WndStyle, CW_USEDEFAULT, 0,
       WndRect.right-WndRect.left, WndRect.bottom-WndRect.top, NULL, NULL, hInstance, NULL)))
      return 0;

   // Direct3Dの初期化
   LPDIRECT3D9 g_pD3D;
   LPDIRECT3DDEVICE9 g_pD3DDev;
   if( !(g_pD3D = Direct3DCreate9( D3D_SDK_VERSION )) ) return 0;
   Com_ptr<IDirect3D9> spD3D(g_pD3D);

   D3DPRESENT_PARAMETERS d3dpp = {0,0,D3DFMT_UNKNOWN,0,D3DMULTISAMPLE_NONE,0,
                           D3DSWAPEFFECT_DISCARD,NULL,TRUE,TRUE,D3DFMT_D16,0,D3DPRESENT_RATE_DEFAULT,D3DPRESENT_INTERVAL_DEFAULT}; 

   if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDev ) ) )
      if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDev ) ) )
         return 0;
   Com_ptr<IDirect3DDevice9> cpDev( g_pD3DDev );


   /////////////////////////////////////////////////////
   // Z値テクスチャ生成オブジェクトの生成と初期化
   /////////////
   UINT TexSize = 1024;   // テクスチャサイズ
   CZTexCreator ZTexCreator;   // Z値テクスチャ作成オブジェクト
   Com_ptr<IDirect3DTexture9> cpShadowMapTex;   // Z値テクスチャ
   if(!ZTexCreator.Init( cpDev, TexSize, TexSize, D3DFMT_A8R8G8B8 ))   // 通常はこのテクスチャが作成できます
   if(!ZTexCreator.Init( cpDev, TexSize, TexSize, D3DFMT_R5G6B5 ))      // 万が一は16bitに落とします
      return 0;
   ZTexCreator.GetZTex( cpShadowMapTex );


   /////////////////////////////////////////////////////
   // 深度バッファシャドウオブジェクトの生成と初期化
   /////////////
   CDepthBufShadowEffect DepthBS;      // 深度バッファシャドウオブジェクト
   DepthBS.Init( cpDev );
   DepthBS.SetShadowMap( cpShadowMapTex );   // シャドウマップテクスチャを登録

   // 立方体と板オブジェクトの読み込み
   Com_ptr<ID3DXBuffer> cpMatBuf;   // 今回はマテリアルは無視します・・・
   Com_ptr<ID3DXMesh> cpMeshCube, cpMeshPlate;
   DWORD dwMatNum, dwMatNum_Plate;
   if(FAILED(D3DXLoadMeshFromX( _T("Cube2.x"), D3DXMESH_MANAGED, cpDev.GetPtr(), NULL, cpMatBuf.ToCreator(), NULL, &dwMatNum, cpMeshCube.ToCreator() )))   return 0;
   if(FAILED(D3DXLoadMeshFromX( _T("Plate.x"), D3DXMESH_MANAGED, cpDev.GetPtr(), NULL, cpMatBuf.ToCreator(), NULL, &dwMatNum_Plate, cpMeshPlate.ToCreator() )))   return 0;

   // Z値テクスチャ描画用のスプライト生成
   Com_ptr<ID3DXSprite> cpSprite;
   D3DXCreateSprite( cpDev.GetPtr(), cpSprite.ToCreator() );


   // ビュー・射影変換行列を初期化して固定情報として登録する
   D3DXMATRIX CameraView, CameraProj;   // カメラビュー変換・射影変換
   D3DXMATRIX LightView, LightProj;   // ライトビュー変換・射影変換
   float LightScale = 1.5f;
   D3DXMatrixPerspectiveFovLH( &CameraProj, D3DXToRadian(45), 640.0f/480.0f, 10.0f, 1000.0f);
   D3DXMatrixPerspectiveFovLH( &LightProj, D3DXToRadian(45), 1.0f, 40.0f, 300.0f);
   D3DXMatrixLookAtLH( &LightView, &D3DXVECTOR3(LightScale*100,LightScale*55,LightScale*100), &D3DXVECTOR3(0,-20,0), &D3DXVECTOR3(0,1,0) );
   // Z値テクスチャOBJへ登録
   ZTexCreator.SetViewMatrix( &LightView );
   ZTexCreator.SetProjMatrix( &LightProj );
   // 深度バッファシャドウOBJへ登録
   // カメラビューは毎回変わるので描画時に登録します
   DepthBS.SetLightViewMatrix( &LightView );
   DepthBS.SetLightProjMatrix( &LightProj );
   DepthBS.SetCameraProjMatrix( &CameraProj );

   // ウィンドウ表示
   ShowWindow( hWnd, SW_SHOW );

   float Ratio = (float)ClientSize / TexSize;   // 画面に対する比率を計算
   float f=0.0f;
   DWORD i;
   int x, z, CubeNum = 4;
   D3DXMATRIX CubeWorldMat;
   D3DXMATRIX PlateWorldMatrix;

   do
   {
      f+=0.20f;
      // メッセージ処理
      if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ){   DispatchMessage(&msg);}

      // 描画開始
      g_pD3DDev->BeginScene();

      ///////////////////////////////////////
      //■パス1 : Z値テクスチャに
      //        : ライト方向から描画

      ZTexCreator.Begin();   // シェーダ開始

      // 立方体描画
      for(z=0; z<CubeNum; z++)
      {
         for(x=0; x<CubeNum; x++)
         {
            GetCubeWorldMatrix( f, x, z, &CubeWorldMat );
            ZTexCreator.SetWorldMatrix( &CubeWorldMat );   // 立方体のワールド変換行列を登録
            for(i=0; i<dwMatNum; i++)
            {
               ZTexCreator.SetParamToEffect();      // 描画の直前に呼び出す必要があります
               ZTexCreator.BeginPass();
               cpMeshCube->DrawSubset(i);            // メッシュ描画
               ZTexCreator.EndPass();
            }
         }
      }

      // 板描画
      GetPlateWorldMatrix( &PlateWorldMatrix );
      ZTexCreator.SetWorldMatrix( &PlateWorldMatrix );
      ZTexCreator.SetParamToEffect();      // 描画の直前に呼び出す必要があります
      for(i=0; i<dwMatNum_Plate; i++){
         ZTexCreator.BeginPass();
         cpMeshPlate->DrawSubset(i);
         ZTexCreator.EndPass();
      }

      ZTexCreator.End();   // シェーダ終了


      ///////////////////////////////////////
      //■パス2 : 深度バッファシャドウ描画

      // カメラの視点を変化
      D3DXMatrixLookAtLH( &CameraView, &D3DXVECTOR3(40*sin(f/20),70,40*cos(f/20)), &D3DXVECTOR3(20,-20,20), &D3DXVECTOR3(0,1,0) );
      DepthBS.SetCameraViewMatrix( &CameraView );

      DepthBS.Begin();

      // 立方体描画
      for(z=0; z<CubeNum; z++)
      {
         for(x=0; x<CubeNum; x++)
         {
            GetCubeWorldMatrix( f, x, z, &CubeWorldMat);
            DepthBS.SetWorldMatrix( &CubeWorldMat );
            for(i=0; i<dwMatNum; i++)
            {
               DepthBS.SetParamToEffect();      // 描画の直前に呼び出す必要があります
               DepthBS.BeginPass();
               cpMeshCube->DrawSubset(i);         // メッシュ描画
               DepthBS.EndPass();
            }
         }
      }

      // 板描画
      GetPlateWorldMatrix( &PlateWorldMatrix );
      DepthBS.SetWorldMatrix( &PlateWorldMatrix );
      DepthBS.SetParamToEffect();      // 描画の直前に呼び出す必要があります
      for(i=0; i<dwMatNum_Plate; i++){
         DepthBS.BeginPass();
         cpMeshPlate->DrawSubset(i);
         DepthBS.EndPass();
      }


      //////////////////////////////////
      //■パス3 : Z値テクスチャを描画
      D3DXMATRIX SpriteScaleMat;
      D3DXMatrixScaling( &SpriteScaleMat, Ratio/3, Ratio/3, 1.0f );
      cpSprite->SetTransform( &SpriteScaleMat );
      cpSprite->Begin(0);
      cpSprite->Draw(cpShadowMapTex.GetPtr(), NULL, NULL, NULL, 0xffffffff );
      cpSprite->End();

      g_pD3DDev->EndScene();     
      g_pD3DDev->Present( NULL, NULL, NULL, NULL );

   }while(msg.message != WM_QUIT);

   return 0;
}