Microsoft Visual Studio .NET 2003 Microsoft DirectX 9.0 SDK (December 2004) シェーダーモデル 2.0 |
■動的な高さフォグ | Prev Top Next |
関連ページ:擬似表面化散乱 高さフォグ |
今年最初のネタは動的な高さフォグです。 なんすかそれって感じでしょうが、これは「Game Watch」の記事の「バーチャファイター5」で「VTF フォグ」 という名称で紹介されていた手法です。ただしこのページで紹介する手法は、「VTF フォグ」とは多分異なっていて、その違いのために「VTF フォグ」と呼ぶことができないために「動的な高さフォグ」 と自分で適当に名称をつけただけです。なので名称については気にしないでください(笑)。
さて「動的な高さフォグ」について簡単に説明すると、風圧で吹き飛ばせるフォグです。以前このサイトで紹介した高さフォグではフォグの周りをトラが歩き回っても フォグはまったく動じませんでした。しかし、今回はフォグの中を歩き回るとフォグが吹き飛ぶようになります。これ動かしてみるとかなり面白いです。
ティーポットは静止しています。この場合はだいたい均等にフォグパーティクルがばら撒かれています。
ティーポットを画面中心あたりをぐるぐるまわしてみました。その影響で画面中心あたりのフォグが周辺に向かって吹き飛ばされています。
このあとティーポットを静止させてしばらく待っているとフォグが元の位置に戻っていきます。
さてこれを実装するためにセガさんは面白いことを考えました。普通に考えればフォグパーティクルを地面の上に大量に散らしてそれを吹き飛ばすようにし、
レンダリング時には背景等とフォグパーティクルの境界部分が鋭くなるのでソフトパーティクルでもかませばいいんじゃね、となります。
まあこれでもいいとは思いますが、きれいにレンダリングするには小さいフォグパーティクルを大量にばら撒く必要があり、結構負荷が高くなるのではないかと思います(検証はしていない、あくまで予想)。
これに対し「VTF フォグ」ではまずフォグマップにフォグパーティクルをレンダリングしておき、そのテクスチャーを使用して高さフォグでレンダリングします。
最終的に高さフォグを適応するのでフォグパーティクルの大きさが大きくても、結構きれいに見えます。
おおすごい!!と思ったんですが、「VTF フォグ」ではなぜか頂点シェーダーでフォグマップを参照しているらしいです。
まあそのため「VTF」という名称がついているらしいのですが、SM3.0を使用してまで頂点シェーダーでフォグマップを参照する必要があるとは思えないので、当サイトでは素直にピクセルシェーダーでフォグマップを
参照するようにしました。これが「VTF フォグ」という名称を使用しない理由です。
処理フローはこんな風になります。
1.フォグパーティクルの座標の更新。
2.フォグマップの作成。
3.作成したフォグマップを参照し高さフォグを適応。
・1はフォグパーティクルを吹き飛ばしたり、元に位置に戻したりします。吹き飛ばしはティーポットとフォグパーティクルの距離が近くなったらティーポット位置からフォグパーティクル位置への
ベクトル方向にフォグパーティクルを移動させます。なおサンプルでは加速度パラメータを持たないためリアルな動きではないです。その辺は自分で修正してください。
また吹き飛ばすのはティーポットが移動しているときのみとしています。
・2はレンダーターゲットサーフェイスに切り替えて、1で更新した座標情報をもとにフォグパーティクルをレンダリングします。
・3は2で作成したフォグマップを参照し、高さフォグを適応します。
さてソースを見ていきます。
---UPrimitive.h---
class FOG_PARTICLE { private: DWORD D3D2DFVF; typedef struct _D3D2DVERTEX { D3DXVECTOR3 pos; DWORD color; float tu, tv; }D3D2DVERTEX; //フォグパーティクルの初期の行列配列 //フォグパーティクル数は50に固定 D3DXMATRIX m_pInitParticleMatrix[50]; LPDIRECT3DVERTEXBUFFER9 m_pd3dVertexBuffer; LPDIRECT3DDEVICE9 m_pd3dDevice; //フォグテクスチャー LPDIRECT3DTEXTURE9 m_pFogTexture; public: //現在のフォグパーティクルの行列 //フォグパーティクル数は50に固定 D3DXMATRIX m_pParticleMatrix[50]; public: FOG_PARTICLE( LPDIRECT3DDEVICE9 pd3dDevice ); ~FOG_PARTICLE(); void Invalidate(); //初期化 HRESULT Load( D3DXMATRIX pMatrix[50] ); //移動させたフォグパーティクルを初期位置に戻すために行列を更新する void NextFrame( float Speed ); //頂点の座標を更新し、レンダリングする HRESULT Render(); };
---UPrimitive.cpp---
FOG_PARTICLE::FOG_PARTICLE( LPDIRECT3DDEVICE9 pd3dDevice ) { m_pd3dDevice = pd3dDevice; D3D2DFVF = D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1; m_pd3dVertexBuffer = NULL; m_pFogTexture = NULL; } FOG_PARTICLE::~FOG_PARTICLE() { Invalidate(); } void FOG_PARTICLE::Invalidate() { SafeRelease( m_pd3dVertexBuffer ); SafeRelease( m_pFogTexture ); } HRESULT FOG_PARTICLE::Load( LPCTSTR pFogTextureFileName, D3DXMATRIX pMatrix[50] ) { HRESULT hr; Invalidate(); //行列のコピー CopyMemory( m_pParticleMatrix, pMatrix, sizeof( D3DXMATRIX ) * 50 ); CopyMemory( m_pInitParticleMatrix, pMatrix, sizeof( D3DXMATRIX ) * 50 ); hr = m_pd3dDevice->CreateVertexBuffer( sizeof( D3D2DVERTEX ) * 4, D3DUSAGE_WRITEONLY, D3D2DFVF, D3DPOOL_MANAGED, &m_pd3dVertexBuffer, NULL ); if( FAILED( hr ) ) return -1; D3D2DVERTEX* vtx; hr = m_pd3dVertexBuffer->Lock( 0, 0, (void**)&vtx, 0 ); if( FAILED( hr ) ) return -2; vtx->pos = D3DXVECTOR3( -0.5f, 0.5f, 0.0f ); vtx->color = 0xFFFFFFFF; vtx->tu = 0.0f; vtx->tv = 0.0f; vtx++; vtx->pos = D3DXVECTOR3( 0.5f, 0.5f, 0.0f ); vtx->color = 0xFFFFFFFF; vtx->tu = 1.0f; vtx->tv = 0.0f; vtx++; vtx->pos = D3DXVECTOR3( -0.5f, -0.5f, 0.0f ); vtx->color = 0xFFFFFFFF; vtx->tu = 0.0f; vtx->tv = 1.0f; vtx++; vtx->pos = D3DXVECTOR3( 0.5f, -0.5f, 0.0f ); vtx->color = 0xFFFFFFFF; vtx->tu = 1.0f; vtx->tv = 1.0f; vtx++; m_pd3dVertexBuffer->Unlock(); //フォグテクスチャーをロードする hr = D3DXCreateTextureFromFileEx( m_pd3dDevice, pFogTextureFileName, //フォグテクスチャーのファイル名 D3DX_DEFAULT, D3DX_DEFAULT, 1, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0x0, NULL, NULL, &m_pFogTexture ); if( FAILED( hr ) ) return -3; return S_OK; } void FOG_PARTICLE::NextFrame( float Speed ) { D3DXVECTOR3 p1; D3DXMATRIX m; float l; for( int i=0; i<50; i++ ) { p1.x = m_pInitParticleMatrix[i]._41 - m_pParticleMatrix[i]._41; p1.y = m_pInitParticleMatrix[i]._42 - m_pParticleMatrix[i]._42; p1.z = m_pInitParticleMatrix[i]._43 - m_pParticleMatrix[i]._43; l = D3DXVec3Length( &p1 ) * Speed; m_pParticleMatrix[i]._41 += p1.x * l; m_pParticleMatrix[i]._42 += p1.y * l; m_pParticleMatrix[i]._43 += p1.z * l; } } HRESULT FOG_PARTICLE::Render() { HRESULT hr; m_pd3dDevice->SetFVF( D3D2DFVF ); //フォグのテクスチャーをステージ0に設定 m_pd3dDevice->SetTexture( 0, m_pFogTexture ); hr = m_pd3dDevice->SetStreamSource( 0, m_pd3dVertexBuffer, 0, sizeof(D3D2DVERTEX) ); if( FAILED( hr ) ) return -1; for( int i=0; i<50; i++ ) { m_pd3dDevice->SetTransform( D3DTS_WORLD, &m_pParticleMatrix[i] ); hr = m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 ); if( FAILED( hr ) ) return -2; } return S_OK; }
まずはフォグパーティクルの行列情報の保持、行列の更新、レンダリングを行うクラスです。 フォグパーティクル数が50限定だったりと設計が適当ですので、修正してください。
---HeightFog2.fx---
float4x4 m_WVP; //ワールド × ビュー × 遠近射影行列 float4x4 m_W; //ワールド行列 float4 m_LightDir; //平行光源の方向ベクトル float4 m_Ambient = 0.0f; //環境光 float4 m_FogColor; //フォグカラー float m_MaxHeight = 10.0f; //フォグの高さの最大値 float m_MinHeight = 2.0f; //フォグの高さの最小値 float2 m_FogMapSize; //フォグマップのサイズ sampler tex0 : register(s0); //オブジェクトのデカールマップ sampler tex1 : register(s1); //フォグマップ struct VS_OUTPUT { float4 Pos : POSITION; float4 Col : COLOR0; float2 Tex : TEXCOORD0; float4 PosW : TEXCOORD1; //ワールド行列のみ適応した頂点座標 }; 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) ); Out.PosW = mul( Pos, m_W ); return Out; } float4 PS( VS_OUTPUT In ) : COLOR0 { //頂点の座標からテクセル座標を計算する float2 Tex = ( In.PosW.xz + m_FogMapSize * 0.5f ) / m_FogMapSize; Tex.y *= -1.0f; //フォグマップから色を取得する float FogColor = tex2D( tex1, Tex ).r; float Alpha = clamp( ( In.PosW.y - m_MinHeight ) / ( m_MaxHeight - m_MinHeight ), 0.0f, 1.0f ); Alpha = 1.0f - ( 1.0f - Alpha ) * FogColor; //線形合成する float4 Out = In.Col * tex2D( tex0, In.Tex ) * Alpha + m_FogColor * ( 1.0f - Alpha ); return Out; } technique TShader { pass P0 { VertexShader = compile vs_1_1 VS(); PixelShader = compile ps_2_0 PS(); } }
「動的な高さフォグ」シェーダーです。以前紹介した「高さフォグ」シェーダーを若干修正しています。
---HeightFog.h---
class HEIGHT_FOG2 { private: LPD3DXEFFECT m_pEffect; D3DXHANDLE m_pTechnique, m_pWVP, m_pW, m_pLightDir, m_pAmbient, m_pFogColor, m_pMaxHeight, m_pMinHeight, m_pFogMapSize; D3DXMATRIX m_matView, m_matProj; LPDIRECT3DDEVICE9 m_pd3dDevice; public: HEIGHT_FOG2( LPDIRECT3DDEVICE9 pd3dDevice ); ~HEIGHT_FOG2(); HRESULT Load(); void Invalidate(); void Restore(); void Begin(); void BeginPass(); //オブジェクトの環境光を設定 void SetAmbient( float Ambient ); void SetAmbient( D3DXVECTOR4* pAmbient ); //フォグの色を設定 void SetFogColor( float FogColor ); void SetFogColor( D3DXVECTOR4* pFogColor ); //フォグの高さに対する最小値と最大値 void SetHeight( float MaxHeight, float MinHeight ); //フォグマップの大きさ void SetFogMapSize( D3DXVECTOR2* pFogMapSize ); void SetMatrix( D3DXMATRIX* pMatWorld, D3DXVECTOR4* pLightDir ); void CommitChanges(); void EndPass(); void End(); BOOL IsOK(); LPD3DXEFFECT GetEffect(){ return m_pEffect; }; };
---HeightFog.cpp---
HEIGHT_FOG2::HEIGHT_FOG2(LPDIRECT3DDEVICE9 pd3dDevice) { m_pd3dDevice = pd3dDevice; m_pEffect = NULL; } HEIGHT_FOG2::~HEIGHT_FOG2() { SafeRelease( m_pEffect ); } void HEIGHT_FOG2::Invalidate() { if( m_pEffect ) m_pEffect->OnLostDevice(); } void HEIGHT_FOG2::Restore() { if( m_pEffect ) { m_pEffect->OnResetDevice(); } } HRESULT HEIGHT_FOG2::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("HeightFog2.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_pW = m_pEffect->GetParameterByName( NULL, "m_W" ); m_pLightDir = m_pEffect->GetParameterByName( NULL, "m_LightDir" ); m_pAmbient = m_pEffect->GetParameterByName( NULL, "m_Ambient" ); m_pFogColor = m_pEffect->GetParameterByName( NULL, "m_FogColor" ); m_pMaxHeight = m_pEffect->GetParameterByName( NULL, "m_MaxHeight" ); m_pMinHeight = m_pEffect->GetParameterByName( NULL, "m_MinHeight" ); m_pFogMapSize = m_pEffect->GetParameterByName( NULL, "m_FogMapSize" ); m_pEffect->SetTechnique( m_pTechnique ); } else { return -1; } } else { return -2; } return S_OK; } void HEIGHT_FOG2::Begin() { if( m_pEffect ) { m_pd3dDevice->GetTransform( D3DTS_VIEW, &m_matView ); m_pd3dDevice->GetTransform( D3DTS_PROJECTION, &m_matProj ); m_pEffect->Begin( NULL, 0 ); } } void HEIGHT_FOG2::BeginPass() { if( m_pEffect ) { m_pEffect->BeginPass( 0 ); } } void HEIGHT_FOG2::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 HEIGHT_FOG2::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 HEIGHT_FOG2::SetFogColor( float FogColor ) { if( m_pEffect ) { D3DXVECTOR4 A; A = D3DXVECTOR4( FogColor, FogColor, FogColor, 1.0f ); m_pEffect->SetVector( m_pFogColor, &A ); } } void HEIGHT_FOG2::SetFogColor( D3DXVECTOR4* pFogColor ) { if( m_pEffect ) m_pEffect->SetVector( m_pFogColor, pFogColor ); } void HEIGHT_FOG2::SetHeight( float MaxHeight, float MinHeight ) { if( m_pEffect ) { m_pEffect->SetFloat( m_pMaxHeight, MaxHeight ); m_pEffect->SetFloat( m_pMinHeight, MinHeight ); } } void HEIGHT_FOG2::SetFogMapSize( D3DXVECTOR2* pFogMapSize ) { if( m_pEffect ) { m_pEffect->SetValue( m_pFogMapSize, pFogMapSize, sizeof( D3DXVECTOR2 ) ); } } //ローカル座標系 void HEIGHT_FOG2::SetMatrix( D3DXMATRIX* pMatWorld, D3DXVECTOR4* pLightDir ) { if( m_pEffect ) { D3DXMATRIX m, m1; D3DXVECTOR4 LightDir; D3DXVECTOR4 v; m = (*pMatWorld) * m_matView * m_matProj; m_pEffect->SetMatrix( m_pWVP, &m ); m_pEffect->SetMatrix( m_pW, pMatWorld ); //Light LightDir = *pLightDir; D3DXMatrixInverse( &m1, NULL, pMatWorld ); D3DXVec4Transform( &v, &LightDir, &m1 ); //XYZ成分について正規化する D3DXVec3Normalize( (D3DXVECTOR3*)&v, (D3DXVECTOR3*)&v ); m_pEffect->SetVector( m_pLightDir, &v ); } else m_pd3dDevice->SetTransform( D3DTS_WORLD, pMatWorld ); } void HEIGHT_FOG2::CommitChanges() { if( m_pEffect ) m_pEffect->CommitChanges(); } void HEIGHT_FOG2::EndPass() { if( m_pEffect ) { m_pEffect->EndPass(); } } void HEIGHT_FOG2::End() { if( m_pEffect ) { m_pEffect->End(); } } BOOL HEIGHT_FOG2::IsOK() { if( m_pEffect ) return TRUE; return FALSE; }
動的な高さフォグクラスです。 SetFogMapSize()関数にはフォグマップの縦横の大きさを設定します。サンプルでは[ 512.0f, 512.0f ]としているのでこの値をセットします。
---Main.cpp---
LPDIRECT3D9 m_pdirect3d9 = NULL; LPDIRECT3DDEVICE9 m_pd3dDevice = NULL; D3DPRESENT_PARAMETERS m_d3dParameters; D3DCAPS9 Caps; //シーンのメッシュ //DirectX SDK(December 2004) に添付されているDXUTMesh.cppファイルにあるヘルパークラス群 CDXUTMesh* m_pMeshBack = NULL; CDXUTMesh* m_pMeshTeapot = NULL; //2Dオブジェクト(表面化散乱(Subsurface Scattering) ページ参照) D3D2DSQUARE* m_pSquObj = NULL; //フォグマップテクスチャー LPDIRECT3DTEXTURE9 m_pFogMapTexture = NULL; LPDIRECT3DSURFACE9 m_pFogMapSurface = NULL; LPD3DXRENDERTOSURFACE m_pRenderToSurface; //動的な高さフォグクラスの宣言 HEIGHT_FOG2* m_pHeightFog = NULL; //フォグパーティクルクラスの宣言 FOG_PARTICLE* m_pFogParticle = NULL; //スクリーンの解像度 UINT nWidth = 1024; UINT nHeight = 768; //フォグマップの縦横のサイズ D3DXVECTOR2 SurfaceSize = D3DXVECTOR2( 512.0f, 512.0f ); //太陽の位置ベクトル //光源の位置はカメラの視線方向にある D3DXVECTOR4 LightPos = D3DXVECTOR4( 0.0f, 40.0f, -70.0f, 1.0f ); //平行光源の光の方向ベクトル D3DXVECTOR4 LightDir; //視点の位置ベクトル D3DXVECTOR4 EyePos = D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 1.0f ); //ティーポットの座標 D3DXVECTOR3 TeapotPos, TeapotPosBk; 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_pd3dDevice->GetDeviceCaps(&Caps); //動的な高さフォグクラスの初期化 m_pHeightFog = new HEIGHT_FOG2( m_pd3dDevice ); m_pHeightFog->Load(); //背景 m_pMeshBack = new CDXUTMesh(); m_pMeshBack->Create( m_pd3dDevice, _T("res\\01.x") ); m_pMeshBack->SetFVF( m_pd3dDevice, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | 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_DIFFUSE | D3DFVF_TEX1 ); D3DXMATRIX matRotationX, matScaling, matTranslation, matParticleWorld[50]; D3DXMatrixScaling( &matScaling, 160.0f, 160.0f, 1.0f ); D3DXMatrixRotationX( &matRotationX, D3DXToRadian( 90.0f ) ); //乱数の初期化 srand( (unsigned)time( NULL ) ); D3DXVECTOR3 FogParticlePos; for( int i=0; i<50; i++ ) { //適当にフォグパーティクルの初期位置を決めていく //フォグマップの大きさが[ 512.0f, 512.0f ]なので、この範囲内に収まるようにする。 FogParticlePos = D3DXVECTOR3( (float)(rand()%512-256), -180.0f, (float)(rand()%512-256) ); D3DXMatrixTranslation( &matTranslation, FogParticlePos.x, FogParticlePos.y, FogParticlePos.z ); //フォグの矩形の面は Z軸の負の方向を向いていて、フォグパーティクルのレンダリングで使用するビュー行列は上から下を見るように設定するので回転行列が必要 matParticleWorld[i] = matScaling * matRotationX * matTranslation; } //フォグパーティクルクラスの初期化 m_pFogParticle = new FOG_PARTICLE( m_pd3dDevice ); //第一引数にフォグのテクスチャーのパスを指定する。フォグは線形合成してレンダリングするのでddsファイルとする m_pFogParticle->Load( _T("res\\Fog.dds"), matParticleWorld ); //ティーポットの初期位置 TeapotPos = D3DXVECTOR3( 0.0f, -180.0f, 0.0f ); TeapotPosBk = TeapotPos; //平行光源の位置ベクトルから方向ベクトルを計算する 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_pHeightFog->Invalidate(); SafeRelease( m_pFogMapSurface ); SafeRelease( m_pFogMapTexture ); SafeRelease( m_pRenderToSurface ); } //デバイスのリセット後に初期化すべきオブジェクト void Restore() { m_pHeightFog->Restore(); D3DXCreateTexture( m_pd3dDevice, (DWORD)SurfaceSize.x, (DWORD)SurfaceSize.y, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &m_pFogMapTexture ); m_pFogMapTexture->GetSurfaceLevel( 0, &m_pFogMapSurface ); D3DXCreateRenderToSurface( m_pd3dDevice, (DWORD)SurfaceSize.x, (DWORD)SurfaceSize.y, D3DFMT_A8R8G8B8, TRUE, D3DFMT_D16, &m_pRenderToSurface ); //固定機能パイプラインライティングを設定する 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( HWND HWnd ) { 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, matView, matWorld, matViewLookDown, matScaling, matTranslation; //**************************************************************** //STEP1 : フォグパーティクルの座標の更新 //**************************************************************** //取得したフォグパーティクルの座標とティーポットの座標が近いところがあれば吹き飛ばす //ティーポットの移動した場合のみ吹き飛ばす if( TeapotPos != TeapotPosBk ) { D3DXVECTOR3 p1; float l; for( int i=0; i<50; i++ ) { p1.x = m_pFogParticle->m_pParticleMatrix[i]._41 - TeapotPos.x; p1.y = m_pFogParticle->m_pParticleMatrix[i]._42 - TeapotPos.y; p1.z = m_pFogParticle->m_pParticleMatrix[i]._43 - TeapotPos.z; l = D3DXVec3Length( &p1 ); //ティーポットとフォグパーティクルの距離が100.0fより近いとき吹き飛ばす if( l < 100.0f ) { m_pFogParticle->m_pParticleMatrix[i]._41 += 15.0f * p1.x / l; m_pFogParticle->m_pParticleMatrix[i]._42 += 15.0f * p1.y / l; m_pFogParticle->m_pParticleMatrix[i]._43 += 15.0f * p1.z / l; } } } TeapotPosBk = TeapotPos; //吹き飛ばされたフォグパーティクルを初期位置に戻すために座標を更新する m_pFogParticle->NextFrame( 0.00001f ); //**************************************************************** //STEP2 : フォグマップの作成 //**************************************************************** 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_LINEAR ); m_pd3dDevice->SetSamplerState( 1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); m_pd3dDevice->SetSamplerState( 1, D3DSAMP_MIPFILTER, D3DTEXF_NONE ); //ビューポートの切り替え D3DVIEWPORT9 NewViewport; NewViewport.Height = (DWORD)SurfaceSize.x; NewViewport.Width = (DWORD)SurfaceSize.y; NewViewport.MinZ = 0.0f; NewViewport.MaxZ = 1.0f; NewViewport.X = 0; NewViewport.Y = 0; m_pRenderToSurface->BeginScene( m_pFogMapSurface, &NewViewport ); m_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x0, 1.0f, 0L ); //パースなしの正射影行列 D3DXMatrixOrthoLH( &matProj, SurfaceSize.x, SurfaceSize.y, 5.0f, 300.0f ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj ); //真上から見下ろすためのビュー座標変換 //視点 D3DXVECTOR3 Eye = D3DXVECTOR3( 0.0f, 100.0f, 0.0f ); //視線 D3DXVECTOR3 At = D3DXVECTOR3( 0.0f, -130.0f, 0.0f ); //上方 D3DXVECTOR3 Up = D3DXVECTOR3( 0.0f, 0.0f, 1.0f ); D3DXMatrixLookAtLH( &matViewLookDown, &Eye, &At, &Up ); m_pd3dDevice->SetTransform( D3DTS_VIEW, &matViewLookDown ); //アルファブレンドを有効にする 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_INVSRCALPHA ); //Zバッファへの書込み禁止 m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE ); //ライティングを無効にする m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); //フォグパーティクルをレンダリングする m_pFogParticle->Render(); m_pRenderToSurface->EndScene( D3DX_FILTER_POINT ); //設定を戻す m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE ); m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE ); //**************************************************************** //STEP3 : 作成したフォグマップを参照し高さフォグを適応する //**************************************************************** m_pd3dDevice->BeginScene(); m_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x0, 1.0f, 0L ); //パースつきの遠近射影行列 D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4.0f, 4.0f / 3.0f, 5.0f, 1000.0f ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj ); //ビュー座標変換を変更 //視点 Eye = D3DXVECTOR3( 0.0f, 100.0f, -300.0f ); //視線 At = D3DXVECTOR3( 0.0f, -185.0f, 0.0f ); //上方 Up = D3DXVECTOR3( 0.0f, 1.0f, 0.0f ); D3DXMatrixLookAtLH( &matView, &Eye, &At, &Up ); m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); //背景のレンダリング m_pd3dDevice->SetTexture( 0, m_pMeshBack->m_pTextures[0] ); m_pd3dDevice->SetTexture( 1, m_pFogMapTexture ); m_pHeightFog->Begin(); D3DXMatrixIdentity( &matWorld ); m_pHeightFog->SetFogColor( 1.0f ); m_pHeightFog->SetHeight( -135.0f, -180.0f ); m_pHeightFog->SetFogMapSize( &SurfaceSize ); m_pHeightFog->SetMatrix( &matWorld, &LightDir ); m_pHeightFog->SetAmbient( 0.15f ); m_pHeightFog->BeginPass(); m_pMeshBack->GetLocalMesh()->DrawSubset(0); m_pHeightFog->EndPass(); m_pHeightFog->End(); //ティーポットのレンダリング D3DXMatrixScaling( &matScaling, 30.0f, 30.0f, 30.0f ); D3DXMatrixTranslation( &matTranslation, TeapotPos.x, TeapotPos.y, TeapotPos.z ); matWorld = matScaling * matTranslation; m_pHeightFog->Begin(); m_pHeightFog->SetMatrix( &matWorld, &LightDir ); m_pHeightFog->SetAmbient( 0.15f ); m_pd3dDevice->SetTexture( 0, m_pMeshTeapot->m_pTextures[0] ); m_pHeightFog->BeginPass(); m_pMeshTeapot->GetLocalMesh()->DrawSubset(0); m_pHeightFog->EndPass(); m_pHeightFog->End(); 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; }
以上です。
さて今回のサンプルは地形モデルの大きさを -256.0f 〜 256.0f ぴったりに収める必要があります。つまり真上から見ると正方形の形になります。 この辺の説明は擬似表面化散乱でしてるのでそちらを参照してください。どんな条件下でも気軽に使用できるものではないですが 使用できる場合は積極的に使用すべきだと思います。「バーチャファイター5」は格ゲーなため、地形を真上から見ると正方形になります。そのため「擬似表面化散乱」 もそうだけどやりやすかったんでしょうな。
最後に今回紹介したサンプルは、改良の余地がいろいろあります。上でも書きましたがフォグパーティクルを吹き飛ばすとき加速度を考慮していないため、 ワープします(笑)。また吹き飛ばす方向ベクトルの計算方法も、サンプルのような単純な計算ではないと思います。 他にもいろいろありますが、所詮サンプルなんでここでやめます。後は自分で修正してください。