Microsoft Visual Studio .NET 2003 Microsoft DirectX 9.0 SDK (December 2004) シェーダーモデル 2.0 |
■デプスバッファシャドー |
Prev Top Next 関連ページ:メモ書き ランバート拡散照明 |
|
今回は、シェーダーを使用して影を生成します。シャドーマップという2Dテクスチャーを使用して影生成します。 このシャドーマップを使用する影生成にはさまざまな手段がありますが、その中でも簡単なものから始めます。
デプスバッファシャドーは、2ステップで処理します。
まず1ステップ目でシーンのライト位置にカメラを置いてレンダリングし、そのZ値の最も手前の値を取得します。絵にするとこんな感じです。
へたくそですいません(笑)。
赤いところが取得すべきZ値なのですが、まあわかるでしょう。
Z値を出力したレンダーターゲットサーフェイスの内容です。レンダーターゲットサーフェイスのフォーマットは精度を考慮してD3DFMT_R32Fを
使用したほうがよいのですが、わかりにくいのでここではD3DFMT_A8R8G8B8で表示してます。
次に2ステップ目で上で取得したデプスバッファのZ値(仮にAとする)とレンダリング対象のメッシュのZ値(仮にBとする)を比較し、
A < B となる部分を調べます。この部分が影になります。絵にするとこんな感じです。
赤いところが影になります。
影の部分がわかったので、後はメッシュの色成分を任意に減算し影を表現します。最終的にこうなります。
影が表示されているのが確認できると思います。ティーポットの下にあるのはサンプルがよくないけど柵のつもりです。
丸棒ではなく平面をカラーキーによりマスク処理しています。
ではプログラムです。
---Lambert3.fx---
float4x4 m_WVP; // ワールド座標系 × ビュー(ライト基準) × 正射影座標系 //float4x4 m_WVPT; // ワールド座標系 × ビュー(ライト基準) × 正射影座標系 × テクスチャー座標系 sampler tex0 : register(s0); // メッシュのテクスチャー //sampler tex1 : register(s1); // デプスバッファサーフェイスのテクスチャー //**************************************************************** // カラーキーを考慮しない場合のメッシュのZ値を取得するシェーダー //**************************************************************** struct VS_OUTPUT { float4 Pos : POSITION; float2 Depth : TEXCOORD0; }; VS_OUTPUT VS( float4 Pos : POSITION, float4 Normal : NORMAL, float2 Tex : TEXCOORD0 ) { VS_OUTPUT Out; Out.Pos = mul( Pos, m_WVP ); Out.Depth.xy = Out.Pos.zw; return Out; } float4 PS( VS_OUTPUT In ) : COLOR0 { float4 Out; Out = In.Depth.x / In.Depth.y; return Out; } //**************************************************************** // カラーキーを考慮する場合のメッシュのZ値を取得するシェーダー(注意1) //**************************************************************** //アルファブレンドによりマスク処理するように変更する。 /* struct VS_OUTPUT1 { float4 Pos : POSITION; float2 Tex : TEXCOORD0; float2 Depth : TEXCOORD1; float4 DepthUV : TEXCOORD2; //デプスバッファのテクセル座標 }; VS_OUTPUT1 VS1( float4 Pos : POSITION, float4 Normal : NORMAL, float2 Tex : TEXCOORD0 ) { VS_OUTPUT1 Out; Out.Pos = mul( Pos, m_WVP ); Out.Tex = Tex; Out.Depth.xy = Out.Pos.zw; // デプスバッファの深度情報を取得するためにテクセル座標をセット Out.DepthUV = mul( Pos, m_WVPT ); return Out; } float4 PS1( VS_OUTPUT1 In ) : COLOR0 { float4 Out; //レンダリングするピクセル位置に対応するデプスバッファ上のテクセルの位置のカラー情報を取得する float4 ZMapColor = tex2Dproj( tex1, In.DepthUV ); float z = In.Depth.x / In.Depth.y; //メッシュのテクスチャーのα値が0より大きいときは、メッシュのZ値を出力する if( tex2D( tex0, In.Tex ).a > 0.0f ) { //デプスバッファのZ値 > メッシュのZ値 のときはメッシュのZ値を出力する if( ZMapColor.r > z ) Out = z; else Out = ZMapColor; } //メッシュのテクスチャーのα値が0のときは、デプスバッファのZ値を出力する else Out = ZMapColor; return Out; } */ struct VS1_OUTPUT { float4 Pos : POSITION; float2 Tex : TEXCOORD0; //テクセル float2 Depth : TEXCOORD1; }; VS1_OUTPUT VS1( float4 Pos : POSITION, float4 Normal : NORMAL, float2 Tex : TEXCOORD0 ) { VS1_OUTPUT Out; Out.Pos = mul( Pos, m_WVP ); Out.Tex = Tex; Out.Depth.xy = Out.Pos.zw; return Out; } float4 PS1( VS1_OUTPUT In ) : COLOR0 { float4 Out; //RGB成分にはZ値を出力する Out.rgb = In.Depth.x / In.Depth.y; //A成分にはアルファブレンドの線形合成で使用するアルファ値を出力する Out.a = tex2D( tex0, In.Tex ).a; return Out; } technique TShader { //カラーキーを考慮しない pass P0 { VertexShader = compile vs_1_1 VS(); PixelShader = compile ps_2_0 PS(); } //カラーキーを考慮する pass P1 { VertexShader = compile vs_1_1 VS1(); PixelShader = compile ps_2_0 PS1(); } }1ステップ目で使用するシェーダーです。メッシュをライト位置からながめて、Z値を取得する処理を行います。
(注意1) デプスバッファシャドーの場合、カラーキーを適応したメッシュでも正しく影生成できます。 上の画像でいえば柵の影の部分で、正しくレンダリングできてるのが確認できると思います。ちなみに影生成アルゴリズムに「ステンシルシャドウボリューム」と いうものがありますが、この技法の場合メッシュの形がそのまま影となるため正しくレンダリングできません。
あとVS1とPS1関数を大幅修正しました。もともとシェーダー内で陰面処理を行っていましたが、レンダーステートの設定だけでZバッファに透明ピクセルが書き込まれるのを防ぐことができるらしいです。 これに伴いあちこち修正してるのでこのページを参考にされた方は申し訳ないですが修正してください。 以前の方法の場合IDirect3DDevice9::SetTextureとIDirect3DDevice9::SetRenderTargetで同じサーフェイスを指定する必要がありましたが、これやると実行時にワーニングエラーが発生します。
Direct3D9: (WARN) :Can not render to a render target that is also used as a texture. A render target was detected as bound, but couldn't detect if texture was actually used in rendering.
意味はよくわかりませんが、参照と書き込みに同じサーフェイスを使用してるので動作は保障しないよということかなと勝手に思ってます。 ワーニングエラーなので実行は可能ですが、正しく動作するかは保障できないので修正したかったのですが、方法がわかったので修正しました。
---Lambert.h---
class LAMBERT3 { private: LPD3DXEFFECT m_pEffect; // D3DXHANDLE m_pTechnique, m_pWVP, m_pWVPT; D3DXHANDLE m_pTechnique, m_pWVP; LPDIRECT3DDEVICE9 m_pd3dDevice; public: LAMBERT3( LPDIRECT3DDEVICE9 pd3dDevice ); ~LAMBERT3(); void Invalidate(); void Restore(); HRESULT Load(); void Begin(); void BeginPass( UINT Pass ); void SetMatrix( D3DXMATRIX* pMatWVP ); void CommitChanges(); void EndPass(); void End(); BOOL IsOK(); LPD3DXEFFECT GetEffect(){ return m_pEffect; }; };
---Lambert.cpp---
LAMBERT3::LAMBERT3( LPDIRECT3DDEVICE9 pd3dDevice ) { m_pd3dDevice = pd3dDevice; m_pEffect = NULL; } LAMBERT3::~LAMBERT3() { //SafeReleaseは関数ではなくマクロ //#define SafeRelease(x) { if(x) { (x)->Release(); (x)=NULL; } } SafeRelease( m_pEffect ); } void LAMBERT3::Invalidate() { if( m_pEffect ) m_pEffect->OnLostDevice(); } void LAMBERT3::Restore() { if( m_pEffect ) m_pEffect->OnResetDevice(); } HRESULT LAMBERT3::Load() { D3DCAPS9 caps; //ハードウェアがサポートするバーテックスシェーダーとピクセルシェーダーのバージョンをチェックする m_pd3dDevice->GetDeviceCaps( &caps ); if( caps.VertexShaderVersion >= D3DVS_VERSION( 1, 1 ) && caps.PixelShaderVersion >= D3DPS_VERSION( 2, 0 ) ) { LPD3DXBUFFER pErr = NULL; HRESULT hr = D3DXCreateEffectFromFile( m_pd3dDevice, _T("Lambert3.fx"), NULL, NULL, 0, NULL, &m_pEffect, &pErr ); if( SUCCEEDED( hr ) ) { m_pTechnique = m_pEffect->GetTechniqueByName( "TShader" ); m_pWVP = m_pEffect->GetParameterByName( NULL, "m_WVP" ); // m_pWVPT = m_pEffect->GetParameterByName( NULL, "m_WVPT" ); m_pEffect->SetTechnique( m_pTechnique ); } else return -1; } else return -2; return S_OK; } void LAMBERT3::Begin() { if( m_pEffect ) { m_pEffect->Begin( NULL, 0 ); } } void LAMBERT3::BeginPass( UINT Pass ) { if( m_pEffect ) { m_pEffect->BeginPass( Pass ); } } void LAMBERT3::SetMatrix( D3DXMATRIX* pMatWVP ) { if( m_pEffect ) { // D3DXMATRIX m, m1, m2; m_pEffect->SetMatrix( m_pWVP, pMatWVP ); // //行列変換マトリックスをテクスチャー座標系へ変換 // D3DXMatrixScaling( &m1, 0.5f, -0.5f, 1.0f ); // D3DXMatrixTranslation( &m2, 0.5f, 0.5f, 0.0f ); // m = (*pMatWVP) * m1 * m2; // m_pEffect->SetMatrix( m_pWVPT, &m ); } } void LAMBERT3::CommitChanges() { if( m_pEffect ) m_pEffect->CommitChanges(); } void LAMBERT3::EndPass() { if( m_pEffect ) m_pEffect->EndPass(); } void LAMBERT3::End() { if( m_pEffect ) m_pEffect->End(); } BOOL LAMBERT3::IsOK() { if( m_pEffect ) return TRUE; return FALSE; }Lambert3.fxシェーダーを制御するクラスです。特に解説することもないでしょう。
---DepthBufferShadow.fx---
float4x4 m_WVP; // ワールド座標系 × ビュー(カメラ基準)座標系 × 正射影座標系 float4x4 m_LWVP; // ワールド座標系 × ビュー(ライト基準)座標系 × 正射影座標系 float4x4 m_LWVPT; // ワールド座標系 × ビュー(ライト基準)座標系 × 正射影座標系 × テクスチャー座標系 float m_Bias = 0.0f; // Z値の比較による誤差を補正する float m_ShadowColor = 0.1f; // 影によるメッシュのカラーの減算値 float4 m_LightDir; float4 m_Ambient = 0.0f; sampler tex0 : register(s0); // オブジェクトのテクスチャー sampler tex1 : register(s1); // デプスバッファサーフェイスのテクスチャー struct VS_OUTPUT { float4 Pos : POSITION; float4 Col : COLOR0; float2 Tex : TEXCOORD0; float2 Depth : TEXCOORD1; //正射影座標系 に変換した頂点座標 float4 LightUV : TEXCOORD2; //テクスチャー座標系に変換した頂点座標 }; VS_OUTPUT VS( float4 Pos : POSITION, float4 Normal : NORMAL, float2 Tex : TEXCOORD0 ) { VS_OUTPUT Out; Out.Pos = mul( Pos, m_WVP ); Out.Tex = Tex; // ランバート拡散照明によるメッシュの色情報を計算する float3 L = -m_LightDir.xyz; float3 N = normalize( Normal.xyz ); Out.Col = max( m_Ambient, dot(N, L) ); // 正射影座標系に変換したZ値を計算 Out.Depth.xy = mul(Pos, m_LWVP).zw; // テクスチャー座標系に変換した頂点座標を計算 Out.LightUV = mul(Pos, m_LWVPT); return Out; } float4 PS( VS_OUTPUT In ) : COLOR0 { float4 Out = tex2D( tex0, In.Tex ); float3 ShadowColor; //Zバッファサーフェイスから深度値を取得する float d = tex2Dproj( tex1, In.LightUV ).r; //陰と影をなめらかにするために陰と影の両方で減算せず、暗い方で減算するようにする if( d * In.Depth.y < In.Depth.x - m_Bias ) ShadowColor = min( ( In.Col.r + In.Col.g + In.Col.b ) * 0.3333f, m_ShadowColor ); else ShadowColor = In.Col.rgb; Out.rgb *= ShadowColor; return Out; } technique TShader { pass P0 { VertexShader = compile vs_1_1 VS(); PixelShader = compile ps_2_0 PS(); } }2ステップ目で使用するシェーダーです。ライトからみたZ値とカメラからみたZ値を比較して、影になる部分を暗くします。
---DepthBufferShadow.h---
class DEPTH_BUFFER_SHADOW { private: LPD3DXEFFECT m_pEffect; D3DXHANDLE m_pTechnique, m_pWVP, m_pLWVP, m_pLWVPT, m_pLightDir, m_pAmbient, m_pBias, m_pShadowColor; D3DXMATRIX m_matView, m_matProj; LPDIRECT3DDEVICE9 m_pd3dDevice; public: DEPTH_BUFFER_SHADOW( LPDIRECT3DDEVICE9 pd3dDevice ); ~DEPTH_BUFFER_SHADOW(); void Invalidate(); void Restore(); HRESULT Load(); void Begin(); void BeginPass(); void SetAmbient( float Ambient ); void SetAmbient( D3DXVECTOR4* pAmbient ); void SetMatrix( D3DXMATRIX* pMatWorld, D3DXMATRIX* pMatLight, D3DXVECTOR4* pLightDir ); void SetBias( float pBias ); void SetShadowColor( float pShadowColor ); void CommitChanges(); void EndPass(); void End(); BOOL IsOK(); LPD3DXEFFECT GetEffect(){ return m_pEffect; }; };
---DepthBufferShadow.cpp---
DEPTH_BUFFER_SHADOW::DEPTH_BUFFER_SHADOW( LPDIRECT3DDEVICE9 pd3dDevice ) { m_pd3dDevice = pd3dDevice; m_pEffect = NULL; } DEPTH_BUFFER_SHADOW::~DEPTH_BUFFER_SHADOW() { SafeRelease( m_pEffect ); } void DEPTH_BUFFER_SHADOW::Invalidate() { if( m_pEffect ) m_pEffect->OnLostDevice(); } void DEPTH_BUFFER_SHADOW::Restore() { if( m_pEffect ) m_pEffect->OnResetDevice(); } HRESULT DEPTH_BUFFER_SHADOW::Load() { D3DCAPS9 caps; //ハードウェアがサポートするバーテックスシェーダーとピクセルシェーダーのバージョンをチェックする m_pd3dDevice->GetDeviceCaps( &caps ); if( caps.VertexShaderVersion >= D3DVS_VERSION( 1, 1 ) && caps.PixelShaderVersion >= D3DPS_VERSION( 2, 0 ) ) { LPD3DXBUFFER pErr = NULL; HRESULT hr = D3DXCreateEffectFromFile( m_pd3dDevice, _T("DepthBufferShadow.fx"), NULL, NULL, 0, NULL, &m_pEffect, &pErr ); if( SUCCEEDED( hr ) ) { m_pTechnique = m_pEffect->GetTechniqueByName( "TShader" ); m_pWVP = m_pEffect->GetParameterByName( NULL, "m_WVP" ); m_pLWVP = m_pEffect->GetParameterByName( NULL, "m_LWVP" ); m_pLWVPT = m_pEffect->GetParameterByName( NULL, "m_LWVPT" ); m_pLightDir = m_pEffect->GetParameterByName( NULL, "m_LightDir" ); m_pAmbient = m_pEffect->GetParameterByName( NULL, "m_Ambient" ); m_pBias = m_pEffect->GetParameterByName( NULL, "m_Bias" ); m_pShadowColor = m_pEffect->GetParameterByName( NULL, "m_ShadowColor" ); m_pEffect->SetTechnique( m_pTechnique ); } else { return -1; } } else { return -2; } return S_OK; } void DEPTH_BUFFER_SHADOW::Begin() { if( m_pEffect ) { m_pd3dDevice->GetTransform( D3DTS_VIEW, &m_matView ); m_pd3dDevice->GetTransform( D3DTS_PROJECTION, &m_matProj ); m_pEffect->Begin( NULL, 0 ); } } void DEPTH_BUFFER_SHADOW::BeginPass() { if( m_pEffect ) m_pEffect->BeginPass(0); } void DEPTH_BUFFER_SHADOW::SetAmbient( float Ambient ) { if( m_pEffect ) { D3DXVECTOR4 A; A = D3DXVECTOR4( Ambient, Ambient, Ambient, 1.0f ); m_pEffect->SetVector( m_pAmbient, &A ); } else { D3DMATERIAL9 old_material; m_pd3dDevice->GetMaterial( &old_material ); old_material.Ambient.r = Ambient; old_material.Ambient.g = Ambient; old_material.Ambient.b = Ambient; old_material.Ambient.a = 1.0f; m_pd3dDevice->SetMaterial( &old_material ); } } void DEPTH_BUFFER_SHADOW::SetAmbient( D3DXVECTOR4* pAmbient ) { if( m_pEffect ) m_pEffect->SetVector( m_pAmbient, pAmbient ); else { D3DMATERIAL9 old_material; m_pd3dDevice->GetMaterial( &old_material ); old_material.Ambient.r = pAmbient->x; old_material.Ambient.g = pAmbient->y; old_material.Ambient.b = pAmbient->z; old_material.Ambient.a = pAmbient->w; m_pd3dDevice->SetMaterial( &old_material ); } } void DEPTH_BUFFER_SHADOW::SetMatrix( D3DXMATRIX* pMatWorld, D3DXMATRIX* pMatLight, D3DXVECTOR4* pLightDir ) { if( m_pEffect ) { D3DXMATRIX m, m1, m2; D3DXVECTOR4 LightDir; D3DXVECTOR4 v; //カメラ基準の行列変換マトリックスをセットする m = (*pMatWorld) * m_matView * m_matProj; m_pEffect->SetMatrix( m_pWVP, &m ); //ライトの方向ベクトルを計算 LightDir = *pLightDir; D3DXMatrixInverse( &m1, NULL, pMatWorld ); D3DXVec4Transform( &v, &LightDir, &m1 ); //XYZ成分について正規化する D3DXVec3Normalize( (D3DXVECTOR3*)&v, (D3DXVECTOR3*)&v ); m_pEffect->SetVector( m_pLightDir, &v ); //ライト基準の行列変換マトリックスをセットする m = (*pMatWorld) * (*pMatLight); m_pEffect->SetMatrix( m_pLWVP, &m ); //ライト基準の行列変換マトリックスをテクスチャー座標系へ変換 D3DXMatrixScaling( &m1, 0.5f, -0.5f, 1.0f ); D3DXMatrixTranslation( &m2, 0.5f, 0.5f, 0.0f ); m = m * m1 * m2; m_pEffect->SetMatrix( m_pLWVPT, &m ); } else m_pd3dDevice->SetTransform( D3DTS_WORLD, pMatWorld ); } //Z値の比較によって発生する計算誤差を軽減するためのバイアス値(注意2) void DEPTH_BUFFER_SHADOW::SetBias( float pBias ) { if( m_pEffect ) m_pEffect->SetFloat( m_pBias, pBias ); } //影によるメッシュのカラーの減算値 //1.0fで完全に黒になる void DEPTH_BUFFER_SHADOW::SetShadowColor( float pShadowColor ) { if( m_pEffect ) m_pEffect->SetFloat( m_pShadowColor, pShadowColor ); } void DEPTH_BUFFER_SHADOW::CommitChanges() { if( m_pEffect ) m_pEffect->CommitChanges(); } void DEPTH_BUFFER_SHADOW::EndPass() { if( m_pEffect ) m_pEffect->EndPass(); } void DEPTH_BUFFER_SHADOW::End() { if( m_pEffect ) m_pEffect->End(); } BOOL DEPTH_BUFFER_SHADOW::IsOK() { if( m_pEffect ) return TRUE; return FALSE; }DepthBufferShadow.fxシェーダーを制御するクラスです。
(注意2) デプスバッファサーフェイスのZ値とカメラ位置から見たときのZ値を比較するとき、サーフェイスの解像度やフォーマットにより程度の差はあるが、計算誤差が生じます。
バイアス値(pBias)を0.0fに設定したときの結果です。上記の画像の場合柵の部分にマッハバンドが発生しています。これを軽減するために、Z値を操作するためのバイアス値を設定します。
なおシェーダー内の計算方法の関係で0.0f 〜 1.0fの範囲内の値にならないので注意してください。
---Main.cpp---
LPDIRECT3DDEVICE9 m_pd3dDevice = NULL; D3DPRESENT_PARAMETERS m_d3dParameters; D3DCAPS9 Caps; //シーンのメッシュ //DirectX SDK(December 2004) に添付されているDXUTMesh.cppファイルにあるヘルパークラス群 CDXUTMesh* m_pMeshBack = NULL; CDXUTMesh* m_pMeshTeapot = NULL; CDXUTMesh* m_pMeshAlpha = NULL; //ランバート拡散照明クラス LAMBERT1* m_pLambert1 = NULL; //Z値出力クラス LAMBERT3* m_pLambert3 = NULL; //デプスバッファシャドークラス DEPTH_BUFFER_SHADOW* m_pDepthBufferShadow1 = NULL; //ライト位置からみたときのシーンのZ値を格納するサーフェイス LPDIRECT3DTEXTURE9 m_pZBufferTexture = NULL; LPDIRECT3DSURFACE9 m_pZBufferSurface = NULL; //上のレンダーターゲットサーフェイスと一緒に使用するZバッファサーフェイス LPDIRECT3DSURFACE9 m_pZBuffer = NULL; //ライトの方向ベクトル D3DXVECTOR4 LightDir; //ライトの視点ベクトル D3DXVECTOR4 LightEye = D3DXVECTOR4( 0.0f, 0.0f, -1.0f, 1.0f ); //ライトの上ベクトル D3DXVECTOR4 LightUp = D3DXVECTOR4( 0.0f, 1.0f, 0.0f, 1.0f ); //ライトの視線ベクトル D3DXVECTOR4 LightAt = D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 1.0f ); //ライトのビュー行列 D3DXMATRIX LightMatrix; //カメラのビュー行列 D3DXMATRIX matView; //バックバッファの解像度 UINT nWidth = 1024; UINT nHeight = 768; int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmpLine*/, INT /*nCmdShow*/) { char* AppName = "Tutrial"; MSG msg; ZeroMemory(&msg, sizeof(MSG)); HWND hWnd = NULL; WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_VREDRAW | CS_HREDRAW; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(DWORD); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = NULL; wc.hIconSm = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = AppName; wc.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH ); wc.hInstance = hInstance; RegisterClassEx(&wc); //**************************************************************** //ここでウィンドウの作成処理 //**************************************************************** //**************************************************************** //ここでDirect3Dの初期化を行う。 //**************************************************************** m_pd3dDevice->GetDeviceCaps( &caps ); //メッシュのロード //背景 m_pMeshBack = new CDXUTMesh(); m_pMeshBack->Create( m_pd3dDevice, _T("res\\back.x") ); m_pMeshBack->SetFVF( m_pd3dDevice, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 ); //ティーポット m_pMeshTeapot = new CDXUTMesh(); m_pMeshTeapot->Create( m_pd3dDevice, _T("res\\t-pot.x") ); m_pMeshTeapot->SetFVF( m_pd3dDevice, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 ); //αオブジェクト m_pMeshAlpha = new CDXUTMesh(); m_pMeshAlpha->Create( m_pd3dDevice, _T("res\\alpha.x") ); m_pMeshAlpha->SetFVF( m_pd3dDevice, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 ); //ランバート拡散照明クラスの初期化 m_pLambert1 = new LAMBERT1( m_pd3dDevice ); m_pLambert1->Load(); //Z値出力クラスの初期化 m_pLambert3 = new LAMBERT3( m_pd3dDevice ); m_pLambert3->Load(); //デプスバッファシャドークラスの初期化 m_pDepthBufferShadow = new DEPTH_BUFFER_SHADOW( m_pd3dDevice ); m_pDepthBufferShadow->Load(); //平行光源の回転マトリックス //カメラ位置と反対の位置に光源を設定 D3DXMATRIX mx, my; D3DXMatrixRotationY( &my, D3DXToRadian( 170.0f ) ); D3DXMatrixRotationX( &mx, D3DXToRadian( 30.0f ) ); LightMatrix = mx * my; D3DXVec3TransformCoord( (D3DXVECTOR3*)&LightEye, (D3DXVECTOR3*)&LightEye, &LightMatrix ); D3DXVec3TransformCoord( (D3DXVECTOR3*)&LightUp, (D3DXVECTOR3*)&LightUp, &LightMatrix ); D3DXVec3TransformCoord( (D3DXVECTOR3*)&LightAt, (D3DXVECTOR3*)&LightAt, &LightMatrix ); //ライトの方向ベクトルを計算 LightDir = LightAt - LightEye; //ワールド空間上のビュー行列の初期化 D3DXMatrixTranslation( &matView, -50.0f, -80.0f, 300.0f ); RenderOK = true; Restore(); ::ShowWindow(hWnd, SW_SHOW); ::UpdateWindow(hWnd); do { if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) { TranslateMessage(&msg); DispatchMessage(&msg); } else { if( MainLoop(hWnd) == FALSE ) DestroyWindow( hWnd ); } }while( msg.message != WM_QUIT ); UnregisterClass( AppName, hInstance ); return msg.wParam; } //デバイスのリセット前に開放すべきオブジェクト void Invalidate() { m_pLambert1->Invalidate(); m_pLambert3->Invalidate(); m_pDepthBufferShadow->Invalidate(); SafeRelease( m_pZBufferTexture ); SafeRelease( m_pZBufferSurface ); SafeRelease( m_pZBuffer ); } //デバイスのリセット後に初期化すべきオブジェクト void Restore() { DWORD SurfaceWidth = nWidth * 2; DWORD SurfaceHeight = nHeight * 2; m_pLambert1->Restore(); m_pLambert3->Restore(); m_pDepthBufferShadow->Restore(); D3DXCreateTexture( m_pd3dDevice, SurfaceWidth, SurfaceHeight, 1, D3DUSAGE_RENDERTARGET, D3DFMT_R32F, D3DPOOL_DEFAULT, &m_pZBufferTexture ); m_pZBufferTexture->GetSurfaceLevel( 0, &m_pZBufferSurface ); //デプスバッファシャドーの場合、精度を考慮しバックバッファのサイズより大きいサイズのレンダーターゲットサーフェイスを使用する場合がある。 //レンダーターゲットサーフェイスとZバッファサーフェイスのサイズは同じにする必要があるのでZバッファサーフェイスも作成しそのサイズを同じにする。 m_pd3dDevice->CreateDepthStencilSurface( SurfaceWidth, SurfaceHeight, D3DFMT_D16, m_d3dParameters.MultiSampleType, m_d3dParameters.MultiSampleQuality, TRUE, &m_pZBuffer, NULL ); } BOOL MainLoop() { HRESULT hr; //レンダリング不可能 if( RenderOK == false ) { hr = m_pd3dDevice->TestCooperativeLevel(); switch( hr ) { //デバイスは消失しているがReset可能 case D3DERR_DEVICENOTRESET: //開放 Invalidate(); //デバイスをリセットする hr = m_pd3dDevice->Reset( &m_d3dParameters ); switch( hr ) { //デバイスロスト case D3DERR_DEVICELOST: break; //内部ドライバーエラー case D3DERR_DRIVERINTERNALERROR: return FALSE; break; //メソッドの呼び出しが無効です case D3DERR_INVALIDCALL: return FALSE; break; case S_OK: //初期化 Restore(); RenderOK = true; } break; } } //レンダリング可能 else { D3DXMATRIX matProj, matViewLight, matProjLight, matWorld, m; D3DXVECTOR3 LEye; m_pd3dDevice->BeginScene(); //**************************************************************** //ステップ1 : デプスバッファサーフェイスにライト位置から見た時のシーンのZ値を出力する //**************************************************************** //ライト位置からのビュー座標系 //ライトの視点を適当に伸ばす LEye = D3DXVECTOR3( LightEye.x, LightEye.y, LightEye.z ) * 700.0f; D3DXMatrixLookAtLH( &matViewLight, &LEye, (D3DXVECTOR3*)&LightAt, (D3DXVECTOR3*)&LightUp ); //ライト位置からの射影行列はゆがませないようにするため左手座標系正射影行列を作成する(注意3) //クリップ面はアプリケーションごとに調整すること D3DXMatrixOrthoLH( &matProjLight, (float)nWidth, (float)nHeight, 300.0f, 1000.0f ); m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MIPFILTER, D3DTEXF_NONE ); m_pd3dDevice->SetSamplerState( 1, D3DSAMP_MINFILTER, D3DTEXF_POINT ); m_pd3dDevice->SetSamplerState( 1, D3DSAMP_MAGFILTER, D3DTEXF_POINT ); m_pd3dDevice->SetSamplerState( 1, D3DSAMP_MIPFILTER, D3DTEXF_NONE ); //アルファブレンドによる合成方法を線形合成にする m_pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); //この値とテクスチャーのアルファ値とを比較する m_pd3dDevice->SetRenderState( D3DRS_ALPHAREF, 0x00000080 ); //D3DRS_ALPHAREFで指定した値 > テクスチャーのアルファ値 のときレンダー ターゲット サーフェイスへ書き込みしない //つまりテクスチャーのアルファ値が黒っぽい色のときレンダー ターゲット サーフェイスに書き込まない m_pd3dDevice->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL ); //レンダーターゲットサーフェイスを設定する LPDIRECT3DSURFACE9 OldSurface = NULL, OldZMap = NULL; m_pd3dDevice->GetRenderTarget( 0, &OldSurface ); m_pd3dDevice->SetRenderTarget( 0, m_pZBufferSurface ); m_pd3dDevice->GetDepthStencilSurface( &OldZMap ); m_pd3dDevice->SetDepthStencilSurface( m_pZBuffer ); m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xFFFFFFFF, 1.0f, 0L ); //ここから不透明オブジェクトのレンダリング //地面 m_pLambert3->Begin(); D3DXMatrixIdentity( &matWorld ); m = matWorld * matViewLight * matProjLight; m_pLambert3->SetMatrix( &m ); m_pd3dDevice->SetTexture( 0, NULL ); m_pLambert3->BeginPass(0); m_pMeshBack->m_pLocalMesh->DrawSubset( 0 ); m_pLambert3->EndPass(); //空 D3DXMatrixIdentity( &matWorld ); m = matWorld * matViewLight * matProjLight; m_pLambert3->SetMatrix( &m ); m_pLambert3->BeginPass(0); m_pMeshBack->m_pLocalMesh->DrawSubset( 1 ); m_pLambert3->EndPass(); //ティーポット D3DXMATRIX matTeaPot[2], matScaling, matTranslation; D3DXMatrixScaling( &matScaling, 20.0f, 20.0f, 20.0f ); D3DXMatrixTranslation( &matTranslation, 0.0f, 110.0f, 80.0 ); matTeaPot[0] = matScaling * matTranslation; m = matTeaPot[0] * matViewLight * matProjLight; m_pLambert3->SetMatrix( &m ); m_pLambert3->BeginPass(0); m_pMeshTeapot->m_pLocalMesh->DrawSubset( 0 ); m_pLambert3->EndPass(); D3DXMatrixTranslation( &matTranslation, 0.0f, 110.0f, -80.0 ); matTeaPot[1] = matScaling * matTranslation; m = matTeaPot[1] * matViewLight * matProjLight; m_pLambert3->SetMatrix( &m ); m_pLambert3->BeginPass(0); m_pMeshTeapot->m_pLocalMesh->DrawSubset( 0 ); m_pLambert3->EndPass(); m_pLambert3->End(); //ここから半透明オブジェクトレンダリング // //コピーしたサーフェイスに対し書き込みする // m_pd3dDevice->SetRenderTarget( 0, m_pZBufferSurface ); // //コピー元サーフェイスはZ値を参照するためのテクスチャーとする // m_pd3dDevice->SetTexture( 1, m_pZBufferTexture ); // //Zバッファへの書き込みを無効にする // m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE ); //両面レンダリング m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); //アルファ ブレンドを有効にする。 m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); //アルファ テストを有効にする m_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, TRUE ); //柵 m_pLambert3->Begin(); D3DXMatrixIdentity( &matWorld ); m = matWorld * matViewLight * matProjLight; m_pLambert3->SetMatrix( &m ); m_pd3dDevice->SetTexture( 0, m_pMeshAlpha->m_pTextures[0] ); m_pLambert3->BeginPass(1); m_pMeshAlpha->m_pLocalMesh->DrawSubset( 0 ); m_pLambert3->EndPass(); m_pLambert3->End(); // m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE ); //アルファ ブレンドを無効にする m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); //アルファ テストを無効にする m_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW ); m_pd3dDevice->SetRenderTarget( 0, OldSurface ); SafeRelease( OldSurface ); m_pd3dDevice->SetDepthStencilSurface( OldZMap ); SafeRelease( OldZMap ); //バックバッファを初期化する m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x0, 1.0f, 0L ); //**************************************************************** //ステップ2 : デプスバッファサーフェイスを参照し、オブジェクトと影をレンダリングする //**************************************************************** //カメラ位置からの射影座標変換 D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4.0f, (float)nWidth / (float)nHeight, 50.0f, 1000.0f ); m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj ); //デプスバッファサーフェイスのテクスチャーをサンプラ番号1にセットする m_pd3dDevice->SetTexture( 1, m_pZBufferTexture ); //SetShadowColor() と SetAmbient の値は基本的に同じにした方がよい m_pDepthBufferShadow->SetBias( 0.02f ); m_pDepthBufferShadow->SetShadowColor( 0.2f ); m_pDepthBufferShadow->SetAmbient( 0.2f ); m_pDepthBufferShadow->Begin(); m = matViewLight * matProjLight; //地面 D3DXMatrixIdentity( &matWorld ); m_pDepthBufferShadow->SetMatrix( &matWorld, &m, &LightDir ); m_pd3dDevice->SetTexture( 0, m_pMeshBack->m_pTextures[0] ); m_pDepthBufferShadow->BeginPass(); m_pMeshBack->m_pLocalMesh->DrawSubset( 0 ); m_pDepthBufferShadow->EndPass(); //ティーポット m_pDepthBufferShadow->SetMatrix( &matTeaPot[0], &m, &LightDir ); m_pd3dDevice->SetTexture( 0, m_pMeshTeapot->m_pTextures[0] ); m_pDepthBufferShadow->BeginPass(); m_pMeshTeapot->m_pLocalMesh->DrawSubset( 0 ); m_pDepthBufferShadow->EndPass(); m_pDepthBufferShadow->SetMatrix( &matTeaPot[1], &m, &LightDir ); m_pDepthBufferShadow->BeginPass(); m_pMeshTeapot->m_pLocalMesh->DrawSubset( 0 ); m_pDepthBufferShadow->EndPass(); m_pDepthBufferShadow->End(); m_pd3dDevice->SetTexture( 1, NULL ); //空 m_pLambert1->Begin(); D3DXMatrixIdentity( &matWorld ); m_pLambert1->SetMatrix( &matWorld, &LightDir ); m_pLambert1->SetAmbient( 1.0F ); m_pd3dDevice->SetTexture( 0, m_pMeshBack->m_pTextures[1] ); m_pLambert1->BeginPass(0); m_pMeshBack->m_pLocalMesh->DrawSubset( 1 ); m_pLambert1->EndPass(); m_pLambert1->End(); //ここから半透明オブジェクトをレンダリング //アルファブレンドを有効にする m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); //アルファ テストを有効にする m_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, TRUE ); //両面レンダリング m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); // //アルファブレンドを線形合成でレンダリング // m_pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); // m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); // m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); //柵 m_pDepthBufferShadow->Begin(); D3DXMatrixIdentity( &matWorld ); m_pDepthBufferShadow->SetMatrix( &matWorld, &m, &LightDir ); m_pd3dDevice->SetTexture( 0, m_pMeshAlpha->m_pTextures[0] ); m_pDepthBufferShadow->BeginPass(); m_pMeshAlpha->m_pLocalMesh->DrawSubset( 0 ); m_pDepthBufferShadow->EndPass(); m_pDepthBufferShadow->End(); //アルファブレンドを無効にする m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); //アルファ テストを無効にする m_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW ); m_pd3dDevice->SetTexture( 1, NULL ); m_pd3dDevice->EndScene(); hr = m_pd3dDevice->Present( NULL, NULL, NULL, NULL ); //デバイスロストのチェック switch( hr ) { //デバイスロスト case D3DERR_DEVICELOST: RenderOK = false; break; //内部ドライバーエラー case D3DERR_DRIVERINTERNALERROR: return FALSE; break; //メソッドの呼び出しが無効です case D3DERR_INVALIDCALL: return FALSE; break; } } return TRUE; }ふー、やっと終わった。みずらいかもしれないけど、何とか理解してください。
(注意3) 正射影行列を使用します。これはパースをつけるとカメラによるビューとライトによるビューの設定しだいで精度が左右されることがあるからです。 またクリップ面の設定には注意してください。シーン内のメッシュのZ値が0.0f〜1.0fの範囲内におさまるようにしないとZ値の精度が上がりません。
●最後に…
サンプルイメージを変更したり、クラス設計を変更したりいろいろ修正しました。
本当はPSMやるつもりだったんですが、なんかうまくいかなくて今回は見送りました。
PSMも古い技術なんですが、それすらできないなんて自分の技術力のなさを痛感した今日この頃です。