Microsoft Visual Studio .NET 2003 Microsoft DirectX 9.0 SDK (December 2004) シェーダーモデル 2.0 |
■ライトブルーム その2 | Prev Top Next |
関連ページ:ブラーフィルター その2 |
|
今回は、ライトブルームです。以前紹介したライトブルームはハイライト部分に発生するライトブルームでしたが、 今回は太陽光など非常に強い光源で発生する現象を擬似的に再現します。
こんな感じになります。両方ともライトブルームを適応しています。左側の画像は、太陽を中心とするブルーム(もしくはグロー)効果が発生せず、右側の画像はブルーム効果が発生しています。
ブルーム効果はまぶしい感じを強調する効果があります。したがって太陽光の位置をスクリーンの中心にくるようにカメラ位置と向きを変更するとブルーム効果が強く発生するようになります。
これは太陽光で行っているブラー処理の簡易版です。太陽光をさえぎっている樹木にかかる光のブラーが一様になっていると思います。
しかし、最初の画像を参照すると光のブラーが一様にかからず太陽光の中心位置に近づくほど強くブラーがかかるようになっているのがわかると思います。
これは現実に起こる現象で、太陽光などの光源は非常に強い光なため、全体的に白く色飛びします。しかし実際には太陽の中心位置のほうが光の強さは強くなります。
したがって太陽の中心位置のほうが円周部分より強くブラーがかかるようにする処理を行います。
では簡単に処理フローを説明します。
1.シーンをレンダリングする。
2.Zバッファはそのまま使用し、色情報を格納するサーフェイスを切り替え、太陽をレンダリングする。
3.2をブラー処理。
4.1 + 3を加算合成。
5.ブルーム効果をレンダリング。
以上です。 さてソースです。
---LightBloom1.fx---
float4x4 m_WVP; //ワールド * ビュー * 射影マトリックス float m_SunPower; //太陽テクスチャーにより取得した色情報に積算するパラメータ sampler tex0 : register(s0); //太陽テクスチャー struct VS_OUTPUT { float4 Pos : POSITION; float2 Tex : TEXCOORD0; }; VS_OUTPUT VS( float4 Pos : POSITION, float2 Tex : TEXCOORD0 ) { VS_OUTPUT Out; Out.Pos = mul( Pos, m_WVP ); Out.Tex = Tex; return Out; } float4 PS( VS_OUTPUT In ) : COLOR0 { return tex2D( tex0, In.Tex ) * m_SunPower; } technique TShader { pass P0 { VertexShader = compile vs_1_1 VS(); //1.1でコンパイルするとm_Powerを積算しても積算しない結果と同じなので、2.0でコンパイルする PixelShader = compile ps_2_0 PS(); } }
まず太陽テクスチャーです。これを使用してレンダリングします。このテクスチャー画像のフォーマットは8R8G8Bです。
このテクスチャーを白く色飛びさせるため、m_SunPowerを積算し、レンダリングします。この処理を行うシェーダーです。
---LightBloom1.h---
class LIGHTBLOOM1 { private: LPD3DXEFFECT m_pEffect; D3DXHANDLE m_pTechnique, m_pWVP, m_pSunPower; D3DXMATRIX m_matView, m_matProj; LPDIRECT3DDEVICE9 m_pd3dDevice; public: LIGHTBLOOM1( LPDIRECT3DDEVICE9 pd3dDevice ); ~LIGHTBLOOM1(); void Invalidate(); void Restore(); HRESULT Load(); void Begin(); void BeginPass(); void SetSunPower( float SunPower ); void SetMatrix( D3DXMATRIX* pMatWorld ); void CommitChanges(); void EndPass(); void End(); BOOL IsOK(); LPD3DXEFFECT GetEffect(){ return m_pEffect; }; };
---LightBloom.cpp---
LIGHTBLOOM1::LIGHTBLOOM1( LPDIRECT3DDEVICE9 pd3dDevice ) { m_pd3dDevice = pd3dDevice; m_pEffect = NULL; } LIGHTBLOOM1::~LIGHTBLOOM1() { SafeRelease( m_pEffect ); } void LIGHTBLOOM1::Invalidate() { if( m_pEffect ) m_pEffect->OnLostDevice(); } void LIGHTBLOOM1::Restore() { if( m_pEffect ) { m_pEffect->OnResetDevice(); } } HRESULT LIGHTBLOOM1::Load() { HRESULT hr; D3DCAPS9 caps; //ハードウェアがサポートするバーテックスシェーダーとピクセルシェーダーのバージョンをチェックする m_pd3dDevice->GetDeviceCaps( &caps ); if( caps.VertexShaderVersion >= D3DVS_VERSION( 1, 1 ) && caps.PixelShaderVersion >= D3DPS_VERSION( 2, 0 ) ) { LPD3DXBUFFER pErr = NULL; hr = D3DXCreateEffectFromFile( m_pd3dDevice, _T("LightBloom1.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_pSunPower = m_pEffect->GetParameterByName( NULL, "m_SunPower" ); m_pEffect->SetTechnique( m_pTechnique ); } else { return -1; } } else { return -2; } return S_OK; } void LIGHTBLOOM1::Begin() { if( m_pEffect ) { m_pd3dDevice->GetTransform( D3DTS_VIEW, &m_matView ); m_pd3dDevice->GetTransform( D3DTS_PROJECTION, &m_matProj ); m_pEffect->Begin( NULL, 0 ); } } void LIGHTBLOOM1::BeginPass() { if( m_pEffect ) { m_pEffect->BeginPass( 0 ); } } void LIGHTBLOOM1::SetSunPower( float SunPower ) { if( m_pEffect ) { m_pEffect->SetFloat( m_pSunPower, SunPower ); } } //ローカル座標系 void LIGHTBLOOM1::SetMatrix( D3DXMATRIX* pMatWorld ) { if( m_pEffect ) { D3DXMATRIX m; m = (*pMatWorld) * m_matView * m_matProj; m_pEffect->SetMatrix( m_pWVP, &m ); } else m_pd3dDevice->SetTransform( D3DTS_WORLD, pMatWorld ); } void LIGHTBLOOM1::CommitChanges() { if( m_pEffect ) m_pEffect->CommitChanges(); } void LIGHTBLOOM1::EndPass() { if( m_pEffect ) { m_pEffect->EndPass(); } } void LIGHTBLOOM1::End() { if( m_pEffect ) { m_pEffect->End(); } } BOOL LIGHTBLOOM1::IsOK() { if( m_pEffect ) return TRUE; return FALSE; }
太陽光を白く色飛びさせる、シェーダーの制御クラスです。なお色情報に1.0fを超える値が必要となるのでレンダーターゲットサーフェイスのフォーマットは 必ずHDRフォーマット(浮動小数点フォーマット)を使用してください。でないとこのシェーダー使用する意味がないので。
---BlurFilter3.fx---
float m_TU[5]; //X方向の隣のテクセル位置 float m_TV[5]; //Y方向の隣のテクセル位置 sampler s0 : register(s0); struct VS_OUTPUT { float4 Pos : POSITION; float2 Tex : TEXCOORD0; }; VS_OUTPUT VS( float4 Pos : POSITION, float2 Tex : TEXCOORD0 ) { VS_OUTPUT Out; Out.Pos = Pos; Out.Tex = Tex; return Out; } float4 PS1( VS_OUTPUT In ) : COLOR0 { //テクセルを取得 float2 Texel0 = In.Tex + float2( -m_TU[0], 0.0f ); float2 Texel1 = In.Tex + float2( -m_TU[1], 0.0f ); float2 Texel2 = In.Tex + float2( -m_TU[2], 0.0f ); float2 Texel3 = In.Tex + float2( -m_TU[3], 0.0f ); float2 Texel4 = In.Tex + float2( -m_TU[4], 0.0f ); float2 Texel5 = In.Tex + float2( m_TU[0], 0.0f ); float2 Texel6 = In.Tex + float2( m_TU[1], 0.0f ); float2 Texel7 = In.Tex + float2( m_TU[2], 0.0f ); float2 Texel8 = In.Tex + float2( m_TU[3], 0.0f ); float2 Texel9 = In.Tex + float2( m_TU[4], 0.0f ); //取得したテクセル位置のカラー情報を取得する。 //それぞれのカラー値に重みをかけている。この重み値はすべての合計が 1.0f になるように調整する。 float p0 = tex2D( s0, In.Tex ).r * 0.20f; float p1 = tex2D( s0, Texel0 ).r * 0.12f; float p2 = tex2D( s0, Texel1 ).r * 0.10f; float p3 = tex2D( s0, Texel2 ).r * 0.08f; float p4 = tex2D( s0, Texel3 ).r * 0.06f; float p5 = tex2D( s0, Texel4 ).r * 0.04f; float p6 = tex2D( s0, Texel5 ).r * 0.12f; float p7 = tex2D( s0, Texel6 ).r * 0.10f; float p8 = tex2D( s0, Texel7 ).r * 0.08f; float p9 = tex2D( s0, Texel8 ).r * 0.06f; float p10 = tex2D( s0, Texel9 ).r * 0.04f; //カラーを合成する p0 = p0 + p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9 + p10; return float4( p0, p0, p0, 1.0f ); } float4 PS2( VS_OUTPUT In ) : COLOR0 { //テクセルを取得 float2 Texel0 = In.Tex + float2( 0.0f, -m_TV[0] ); float2 Texel1 = In.Tex + float2( 0.0f, -m_TV[1] ); float2 Texel2 = In.Tex + float2( 0.0f, -m_TV[2] ); float2 Texel3 = In.Tex + float2( 0.0f, -m_TV[3] ); float2 Texel4 = In.Tex + float2( 0.0f, -m_TV[4] ); float2 Texel5 = In.Tex + float2( 0.0f, m_TV[0] ); float2 Texel6 = In.Tex + float2( 0.0f, m_TV[1] ); float2 Texel7 = In.Tex + float2( 0.0f, m_TV[2] ); float2 Texel8 = In.Tex + float2( 0.0f, m_TV[3] ); float2 Texel9 = In.Tex + float2( 0.0f, m_TV[4] ); //取得したテクセル位置のカラー情報を取得する。 //それぞれのカラー値に重みをかけている。この重み値はすべての合計が 1.0f になるように調整する。 float p0 = tex2D( s0, In.Tex ).r * 0.20f; float p1 = tex2D( s0, Texel0 ).r * 0.12f; float p2 = tex2D( s0, Texel1 ).r * 0.10f; float p3 = tex2D( s0, Texel2 ).r * 0.08f; float p4 = tex2D( s0, Texel3 ).r * 0.06f; float p5 = tex2D( s0, Texel4 ).r * 0.04f; float p6 = tex2D( s0, Texel5 ).r * 0.12f; float p7 = tex2D( s0, Texel6 ).r * 0.10f; float p8 = tex2D( s0, Texel7 ).r * 0.08f; float p9 = tex2D( s0, Texel8 ).r * 0.06f; float p10 = tex2D( s0, Texel9 ).r * 0.04f; //カラーを合成する p0 = p0 + p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9 + p10; return float4( p0, p0, p0, 1.0f ); } technique TShader { pass P0 { VertexShader = compile vs_1_1 VS(); PixelShader = compile ps_2_0 PS1(); } pass P1 { VertexShader = compile vs_1_1 VS(); PixelShader = compile ps_2_0 PS2(); } }
---BlurFilter.h---
//D3D2DSQUAREは2Dオブジェクト。詳細は表面化散乱(Subsurface Scattering) を参照 class BLURFILTER3 : public D3D2DSQUARE { private: LPD3DXEFFECT m_pEffect; D3DXHANDLE m_pTechnique, m_pTU, m_pTV; LPDIRECT3DDEVICE9 m_pd3dDevice; public: BLURFILTER3( LPDIRECT3DDEVICE9 pd3dDevice, D3DPRESENT_PARAMETERS* pd3dParameters ); BLURFILTER3( LPDIRECT3DDEVICE9 pd3dDevice, UINT Width, UINT Height ); ~BLURFILTER3(); void Invalidate(); void Restore(); HRESULT Load(); void Render( UINT Pass ); BOOL IsOK(); LPD3DXEFFECT GetEffect(){ return m_pEffect; }; };
---BlurFilter.cpp---
BLURFILTER3::BLURFILTER3( LPDIRECT3DDEVICE9 pd3dDevice, D3DPRESENT_PARAMETERS* pd3dParameters ) : D3D2DSQUARE( pd3dDevice, pd3dParameters ) { m_pd3dDevice = pd3dDevice; m_pEffect = NULL; } BLURFILTER3::BLURFILTER3( LPDIRECT3DDEVICE9 pd3dDevice, UINT Width, UINT Height ) : D3D2DSQUARE( pd3dDevice, Width, Height ) { m_pd3dDevice = pd3dDevice; m_pEffect = NULL; } BLURFILTER3::~BLURFILTER3() { SafeRelease( m_pEffect ); } void BLURFILTER3::Invalidate() { if( m_pEffect ) m_pEffect->OnLostDevice(); } void BLURFILTER3::Restore() { if( m_pEffect ) m_pEffect->OnResetDevice(); } HRESULT BLURFILTER3::Load() { D3DCAPS9 caps; HRESULT hr; m_pd3dDevice->GetDeviceCaps( &caps ); if( caps.VertexShaderVersion >= D3DVS_VERSION( 1, 1 ) && caps.PixelShaderVersion >= D3DPS_VERSION( 2, 0 ) ) { hr = D3D2DSQUARE::Load(); if( FAILED( hr ) ) return -1; //シェーダーの初期化 LPD3DXBUFFER pErr = NULL; hr = D3DXCreateEffectFromFile( m_pd3dDevice, _T("BlurFilter3.fx"), NULL, NULL, 0, NULL, &m_pEffect, &pErr ); if( FAILED( hr ) ) return -2; m_pTechnique = m_pEffect->GetTechniqueByName( "TShader" ); m_pTU = m_pEffect->GetParameterByName( NULL, "m_TU" ); m_pTV = m_pEffect->GetParameterByName( NULL, "m_TV" ); //1テクセルの大きさをセット float TU = 1.0f / D3D2DSQUARE::GetWidth(); float TV = 1.0f / D3D2DSQUARE::GetHeight(); const int Array = 5; float u[Array]; float v[Array]; for( int i=0; i<Array; i++ ) { u[i] = TU * ( i + 1 ); v[i] = TV * ( i + 1 ); } m_pEffect->SetFloatArray( m_pTU, u, Array ); m_pEffect->SetFloatArray( m_pTV, v, Array ); m_pEffect->SetTechnique( m_pTechnique ); } else { return -3; } return S_OK; } void BLURFILTER3::Render( UINT Pass ) { if( m_pEffect ) { m_pEffect->Begin( NULL, 0 ); m_pEffect->BeginPass( Pass ); D3D2DSQUARE::Render(); //2Dスプライトのレンダリング m_pEffect->EndPass(); m_pEffect->End(); } } BOOL BLURFILTER3::IsOK() { if( m_pEffect == NULL ) return FALSE; return TRUE; }
太陽光のあふれ処理を行うためにぼかしフィルターを適応します。既出のブラーフィルター その2とほとんど同じです。 変更点は、今回は太陽光をHDRフォーマット(浮動小数点フォーマット)にレンダリングする必要があるため D3DFMT_R32Fフォーマットにレンダリングします。 このフォーマットは赤成分しか持たないためブラーをかけるときは赤成分の色情報のみ取得し、 ブラー処理を行うように修正します。
このシェーダーを適応すると太陽光の中心に近づくほど強くブラーがかかるようになります。
---Main.cpp---
LPDIRECT3DDEVICE9 m_pd3dDevice = NULL; D3DPRESENT_PARAMETERS m_d3dParameters; D3DCAPS9 Caps; //シーンのメッシュ //DirectX SDK(December 2004) に添付されているDXUTMesh.cppファイルにあるヘルパークラス群 CDXUTMesh* m_pMeshBack1 = NULL; CDXUTMesh* m_pMeshBack2 = NULL; CDXUTMesh* m_pMeshHDR = NULL; //D3D2DSQUAREは2Dオブジェクト。詳細は表面化散乱(Subsurface Scattering) を参照 D3D2DSQUARE* m_pSquObj = NULL; //ランバート拡散照明クラスの宣言 LAMBERT1* m_pLambert1 = NULL; //ライトブルームクラスの宣言 LIGHTBLOOM1* m_pLightBloom1 = NULL; //ブラーフィルタークラスの宣言 BLURFILTER3* m_pBlurFilter3 = NULL; //HDRフォーマットサーフェイス LPDIRECT3DTEXTURE9 m_pHDRTexture; LPDIRECT3DSURFACE9 m_pHDRSurface; //HDRフォーマットサーフェイスの色情報をアプリ側で参照するために一時的にコピーするためのサーフェイス LPDIRECT3DTEXTURE9 m_pCopyTexture = NULL; LPDIRECT3DSURFACE9 m_pCopySurface = NULL; //ブラーフィルター用サーフェイス LPDIRECT3DTEXTURE9 m_pBlurTexture[2]; LPDIRECT3DSURFACE9 m_pBlurSurface[2]; //スクリーンの解像度 UINT nWidth = 1024; UINT nHeight = 768; UINT HalfWidth = nWidth / 4; UINT HalfHeight = nHeight / 4; //太陽の位置ベクトル //太陽の位置はカメラの正面に置く D3DXVECTOR4 LightPos = D3DXVECTOR4( 72.0f, 100.0f, 620.0f, 0.0f ); //平行光源の光の方向ベクトル D3DXVECTOR4 LightDir; //視点の位置ベクトル D3DXVECTOR4 EyePos = D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 1.0f ); bool RenderOK = false; 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_pMeshBack1 = new CDXUTMesh(); m_pMeshBack1->Create( m_pd3dDevice, _T("res\\01.x") ); m_pMeshBack1->SetFVF( m_pd3dDevice, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 ); //樹木 m_pMeshBack2 = new CDXUTMesh(); m_pMeshBack2->Create( m_pd3dDevice, _T("res\\02.x") ); m_pMeshBack2->SetFVF( m_pd3dDevice, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 ); //太陽 m_pMeshHDR = new CDXUTMesh(); m_pMeshHDR->Create( m_pd3dDevice, _T("res\\HDR.x") ); m_pMeshHDR->SetFVF( m_pd3dDevice, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 ); //2Dオブジェクトの初期化 m_pSquObj = new D3D2DSQUARE( m_pd3dDevice, &m_d3dParameters ); m_pSquObj->Load(); //ランバート拡散照明クラスの初期化 m_pLambert1 = new LAMBERT1( m_pd3dDevice ); m_pLambert1->Load(); //ライトブルームクラスの初期化 m_pLightBloom1 = new LIGHTBLOOM1( m_pd3dDevice ); m_pLightBloom1->Load(); //ブラーフィルタークラスの初期化 m_pBlurFilter3 = new BLURFILTER3( m_pd3dDevice, HalfWidth, HalfHeight ); m_pBlurFilter3->Load(); //CPU側でサーフェイスの色情報を参照する目的で使用するサーフェイス //システムメモリに確保するのでデバイスロストを考慮する必要なし m_pd3dDevice->CreateTexture( nWidth, nHeight, 1, 0, D3DFMT_R32F, D3DPOOL_SYSTEMMEM, &m_pCopyTexture, NULL ); m_pCopyTexture->GetSurfaceLevel( 0, &m_pCopySurface ); //平行光源の光の方向ベクトルを計算 LightDir = D3DXVECTOR4( -LightPos.x, -LightPos.y, -LightPos.z, 0.0f ); D3DXVec3Normalize( (D3DXVECTOR3*)&LightDir, (D3DXVECTOR3*)&LightDir ); 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_pLightBloom1->Invalidate(); m_pBlurFilter3->Invalidate(); for( int i=0; i<2; i++ ) { SafeRelease( m_pBlurSurface[i] ); SafeRelease( m_pBlurTexture[i] ); } SafeRelease( m_pHDRSurface ); SafeRelease( m_pHDRTexture ); } void Restore() { m_pLambert1->Restore(); m_pLightBloom1->Restore(); m_pBlurFilter3->Restore(); //HDRサーフェイス m_pd3dDevice->CreateTexture( nWidth, nHeight, 1, D3DUSAGE_RENDERTARGET, D3DFMT_R32F, D3DPOOL_DEFAULT, &m_pHDRTexture, NULL ); m_pHDRTexture->GetSurfaceLevel( 0, &m_pHDRSurface ); //ブラーフィルターサーフェイス for( int i=0; i<2; i++ ) { m_pd3dDevice->CreateTexture( m_pBlurFilter3->GetWidth(), m_pBlurFilter3->GetHeight(), 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &m_pBlurTexture[i], NULL ); m_pBlurTexture[i]->GetSurfaceLevel( 0, &m_pBlurSurface[i] ); } //固定機能パイプラインライティングを設定する D3DLIGHT9 Light; ZeroMemory(&Light, sizeof(D3DLIGHT9)); Light.Type = D3DLIGHT_DIRECTIONAL; Light.Direction = D3DXVECTOR3( LightDir.x, LightDir.y, LightDir.z ); Light.Ambient = D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f ); Light.Diffuse = D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f ); Light.Specular = D3DXCOLOR( 0.0f, 0.0f, 0.0f, 0.0f ); m_pd3dDevice->SetLight(0, &Light); m_pd3dDevice->LightEnable(0, TRUE); D3DMATERIAL9 Material; ZeroMemory( &Material, sizeof( Material ) ); Material.Diffuse.r = 1.0f; Material.Diffuse.g = 1.0f; Material.Diffuse.b = 1.0f; Material.Diffuse.a = 1.0f; m_pd3dDevice->SetMaterial( &Material ); } //メッセージループからコールされる関数 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 { //シーンのクリア m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x0, 1.0f, 0L ); m_pd3dDevice->BeginScene(); D3DXMATRIX matProj, matView, matWorld, matTranslation, matScaling; //パースあり射影行列 D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4.0f, 4.0f / 3.0f, 11.0f, 220.0f ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj ); //ビュー座標変換 D3DXMatrixIdentity( &matView ); m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); //ワールド座標変換 D3DXMatrixIdentity( &matWorld ); m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MIPFILTER, D3DTEXF_NONE ); //**************************************************************** //STEP1:背景のレンダリング //**************************************************************** //背景のレンダリング m_pLambert1->Begin(); D3DXMatrixIdentity( &matWorld ); m_pLambert1->SetMatrix( &matWorld, &LightDir ); m_pLambert1->SetAmbient( 0.0f ); m_pLambert1->BeginPass(); //地面 m_pd3dDevice->SetTexture( 0, m_pMeshBack1->m_pTextures[0] ); m_pMeshBack1->m_pLocalMesh->DrawSubset(0); //空 m_pLambert1->SetAmbient( 1.0f ); m_pLambert1->CommitChanges(); m_pd3dDevice->SetTexture( 0, m_pMeshBack1->m_pTextures[1] ); m_pMeshBack1->m_pLocalMesh->DrawSubset(1); //樹木 m_pLambert1->SetAmbient( 0.0f ); m_pLambert1->CommitChanges(); m_pd3dDevice->SetTexture( 0, m_pMeshBack2->m_pTextures[0] ); m_pMeshBack2->m_pLocalMesh->DrawSubset(0); m_pLambert1->EndPass(); m_pLambert1->End(); //**************************************************************** //STEP2:太陽をHDRフォーマットサーフェイスにレンダリング //**************************************************************** //レンダーターゲットサーフェイスを切り替える LPDIRECT3DSURFACE9 OldSurface = NULL; m_pd3dDevice->GetRenderTarget( 0, &OldSurface ); m_pd3dDevice->SetRenderTarget( 0, m_pHDRSurface ); //レンダーターゲットをクリアする //ただしZバッファは背景のZ値をそのまま使用するためクリアしない m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET, 0x0, 1.0f, 0L ); //太陽をレンダリング D3DXMatrixTranslation( &matTranslation, LightPos.x, LightPos.y, LightPos.z ); //ビルボードマトリックスを取得(ビルボード ページ参照) matWorld = GetBillBoardMatrix( m_pd3dDevice, &matTranslation ); m_pLightBloom1->Begin(); m_pLightBloom1->SetSunPower( 5.0f ); m_pLightBloom1->SetMatrix( &matWorld ); m_pLightBloom1->BeginPass(); m_pd3dDevice->SetTexture( 0, m_pMeshHDR->m_pTextures[0] ); m_pMeshHDR->m_pLocalMesh->DrawSubset(0); m_pLightBloom1->EndPass(); m_pLightBloom1->End(); //**************************************************************** //STEP3:STEP2のレンダリング結果をぼかす //**************************************************************** //縮小バッファにレンダリングするのでビューポートを切り替える D3DVIEWPORT9 OldViewport, NewViewport; m_pd3dDevice->GetViewport( &OldViewport ); NewViewport.Width = m_pBlurFilter3->GetWidth(); NewViewport.Height = m_pBlurFilter3->GetHeight(); NewViewport.X = 0; NewViewport.Y = 0; NewViewport.MinZ = 0.0f; NewViewport.MaxZ = 1.0f; m_pd3dDevice->SetViewport( &NewViewport ); //太陽をぼかす m_pd3dDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_FALSE ); m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP ); m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP ); m_pd3dDevice->SetTexture( 0, m_pHDRTexture ); for( BYTE i=0; i<2; i++ ) { m_pd3dDevice->SetRenderTarget( 0, m_pBlurSurface[i % 2] ); m_pBlurFilter3->Render( i % 2 ); m_pd3dDevice->SetTexture( 0, m_pBlurTexture[i % 2] ); } m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP ); m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP ); //**************************************************************** //STEP4:HDRフォーマットサーフェイスに書き込まれた色情報を参照する(注意1) //**************************************************************** //レンダーターゲットサーフェイスはCPU側から直接参照できないので、コピーしてから参照する //コピー元と先のサーフェイスのサイズとフォーマットは一致させること!! m_pd3dDevice->GetRenderTargetData( m_pHDRSurface, m_pCopySurface ); D3DLOCKED_RECT rc; hr = m_pCopyTexture->LockRect( 0, &rc, NULL, 0 ); LPDWORD pTextureBits = (LPDWORD)rc.pBits; DWORD Color = 0; //サーフェイスのすべてのカラー情報を加算する for( UINT y=0; y<nHeight; y++ ) { for( UINT x=0; x<nWidth; x++ ) { //太陽の表示面積を計算する //単純に0かそうでないかでチェックする、おおざっぱな方法だがこれでもとりあえず問題はないと思われる。 if( (*pTextureBits) ) Color++; pTextureBits++; } } hr = m_pCopyTexture->UnlockRect( 0 ); //レンダーターゲットをバックバッファに戻す m_pd3dDevice->SetRenderTarget( 0, OldSurface ); SafeRelease( OldSurface ); //ビューポートを戻す m_pd3dDevice->SetViewport( &OldViewport ); //**************************************************************** //STEP5:ぼかした太陽イメージをレンダリングし、バックバッファに加算合成する //**************************************************************** m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); //加算合成 m_pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); m_pd3dDevice->SetTexture( 0, m_pBlurTexture[1] ); m_pSquObj->Render(); //**************************************************************** //STEP6:太陽周辺のブルーム効果をレンダリングする //**************************************************************** if( Color > 0 ) { //視線方向ベクトルとカメラ位置から太陽の位置へ向かうベクトルの内積を計算する //ビュー行列の逆行列を取得 D3DXMATRIX matInverse; D3DXMatrixInverse( &matInverse, NULL, &matView ); //視線方向ベクトル D3DXVECTOR4 Look = D3DXVECTOR4( 0.0f, 0.0f, 1.0f, 0.0f ); D3DXVec4Transform( &Look, &Look, &matInverse ); D3DXVec3Normalize( (D3DXVECTOR3*)&Look, (D3DXVECTOR3*)&Look ); //カメラ位置ベクトルを取得 D3DXVECTOR4 Camera = D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 1.0f ); D3DXVec4Transform( &Camera, &Camera, &matInverse ); //カメラ位置から太陽位置へ向かうベクトルを取得 D3DXVECTOR4 Sun = D3DXVECTOR4( LightPos.x - Camera.x, LightPos.y - Camera.y, LightPos.z - Camera.z, 0.0f ); D3DXVec3Normalize( (D3DXVECTOR3*)&Sun, (D3DXVECTOR3*)&Sun ); float Dot = 0.0f; Dot = D3DXVec4Dot( &Look, &Sun ) - 0.95f; if( Dot < 0.0f ) Dot = 0.0f; Dot*=0.001f; D3DXMATRIX matScaling, matTranslation; D3DXMatrixScaling( &matScaling, 5.0f, 5.0f, 1.0f ); D3DXMatrixTranslation( &matTranslation, LightPos.x, LightPos.y, LightPos.z ); //ビルボードマトリックスを取得(ビルボード ページ参照) matWorld = matScaling * GetBillBoardMatrix( m_pd3dDevice, &matTranslation ); m_pLightBloom1->Begin(); m_pLightBloom1->SetSunPower( Dot * (float)( Color ) ); m_pLightBloom1->SetMatrix( &matWorld ); m_pLightBloom1->BeginPass(); m_pd3dDevice->SetTexture( 0, m_pMeshHDR->m_pTextures[0] ); m_pMeshHDR->m_pLocalMesh->DrawSubset(0); m_pLightBloom1->EndPass(); m_pLightBloom1->End(); } m_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE ); m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); 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; }
以上です。ちょっと長かったです。
(注意1) 太陽光をレンダリングしたHDRフォーマットサーフェイスを参照し、太陽がどのくらいの面積でレンダリングされたかをチェックします。
なぜこんなことをするかというと太陽光が他オブジェクトに遮断された場合、ブルーム効果が発生しなくなるためどのくらい遮断されたかをチェックする必要があるためです。
最初は、ブルーム効果をZバッファを参照し切り取った後ブラーをかけることもやってみましたが、この場合ブルーム効果が丸くなりません。
ブルーム効果は丸くならないとおかしいのでこの方法は却下しました。
IDirect3DDevice9::GetRenderTargetData()で太陽をレンダリングしたHDRフォーマットを
別のフォーマットにコピーしています。これはレンダーターゲットサーフェイスは直接ロックできないからです。
詳しくはオンラインマニュアルを参照してください。
あと今回の処理は重いです。通常60FPS程度なのですが、30FPSまで落ちました。最適化しないとちょっとだめですな。
最後に今回は太陽のテクスチャーに8R8G8BのLDRフォーマットテクスチャーを使用しましたが、 HDRフォーマットのテクスチャーもあるらしいです。今回の処理は本来HDRフォーマットを使用したほうがいいのでしょうが、 使い方がよくわからないのでLDRフォーマットを使用しました。そのうちやってみようかとは思うのですが。