Microsoft Visual Studio .NET 2003
 Microsoft DirectX 9.0 SDK (December 2004)
 シェーダーモデル 2.0

■表面化散乱(Subsurface Scattering) Prev  Top  Next
関連ページ:メモ書き ブラーフィルターその2 ボリュームライト

2009/02/22: D3D2DSQUAREクラスにResizeメンバを追加
2009/04/10: 正射影行列をやめる

無料レンタルサーバー

今回は、表面化散乱をやります。表面化散乱は前にもやってますが、あれとはまったく異なるアプローチで実装します。


トラの背後にある太陽の光を透過し、トラが光ってます。結果だけ見ると前にやった後光シェーダーに似てますが、アルゴリズムはこれとも異なります。 基本的なアルゴリズムは、ボリュームライトと同じになります。


ちなみに光源がカメラの後ろにあるときは、こうなります。この場合は光が透過しません。

処理フローはこんな感じです。
1.トラの背面の深度値をレンダリングします。
2.トラの手前面の深度値を取得し、1の結果との差分を計算し、厚みを取得します。
3.2の結果にブラーフィルターを適応し、ぼかします。このぼかしにより表面化散乱っぽくなるようにします。
4.3の結果を参照して厚みが薄くなるほど光を透過するように処理します。

ではソースです。

---UPrimitive.h---


class D3D2DSQUARE
{
private:
   //頂点フォーマット
   DWORD D3D2DFVF;

   typedef struct _D3D2DVERTEX
   {
      float x, y, z, rhw;   //頂点座標
      DWORD color;          //頂点カラー
      float tu, tv;         //テクセル
   }D3D2DVERTEX;

   LPDIRECT3DVERTEXBUFFER9 m_pd3d2DVertex;
   LPDIRECT3DDEVICE9 m_pd3dDevice;

   //2Dポリゴンのサイズ
   UINT m_Width, m_Height;

public:
   D3D2DSQUARE( LPDIRECT3DDEVICE9 pd3dDevice, D3DPRESENT_PARAMETERS* pd3dParameters );
   D3D2DSQUARE( LPDIRECT3DDEVICE9 pd3dDevice, UINT Width, UINT Height );
   ~D3D2DSQUARE();
   void Invalidate();
   //2Dポリゴンのサイズを取得
   UINT GetWidth();
   UINT GetHeight();
   //2Dポリゴンのサイズを変更
   HRESULT Resize( UINT Width, UINT Height );
   HRESULT Resize( D3DPRESENT_PARAMETERS* pd3dParameters );
   //初期化
   HRESULT Load();
   //2Dポリゴンをレンダリング
   void Render();
};

---UPrimitive.cpp---


D3D2DSQUARE::D3D2DSQUARE( LPDIRECT3DDEVICE9 pd3dDevice, D3DPRESENT_PARAMETERS* pd3dParameters )
{
   m_pd3dDevice = pd3dDevice;

   //バックバッファのサイズで格納する
   m_Width = pd3dParameters->BackBufferWidth;
   m_Height = pd3dParameters->BackBufferHeight;

   D3D2DFVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1;

   m_pd3d2DVertex = NULL;
}

D3D2DSQUARE::D3D2DSQUARE( LPDIRECT3DDEVICE9 pd3dDevice, UINT Width, UINT Height )
{
   m_pd3dDevice = pd3dDevice;

   m_Width = Width;
   m_Height = Height;

   D3D2DFVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1;

   m_pd3d2DVertex = NULL;
}

D3D2DSQUARE::~D3D2DSQUARE()
{
   Invalidate();
}

void D3D2DSQUARE::Invalidate()
{
   SafeRelease( m_pd3d2DVertex );
}

HRESULT D3D2DSQUARE::Load()
{
   HRESULT hr = -1;

   Invalidate();

   //2Dポリゴンの作成
   hr = m_pd3dDevice->CreateVertexBuffer( sizeof( D3D2DVERTEX ) * 4, D3DUSAGE_WRITEONLY, D3D2DFVF, D3DPOOL_MANAGED, &m_pd3d2DVertex, NULL );
   if( FAILED( hr ) )
      return -1;

   //ポリゴンの頂点の座標を更新する
   hr = Resize( m_Width, m_Height );

   return hr;
}

UINT D3D2DSQUARE::GetWidth()
{
   return m_Width;
}

UINT D3D2DSQUARE::GetHeight()
{
   return m_Height;
}

HRESULT D3D2DSQUARE::Resize( D3DPRESENT_PARAMETERS* pd3dParameters )
{
   HRESULT hr = -1;
   
   m_Width = pd3dParameters->BackBufferWidth;
   m_Height = pd3dParameters->BackBufferHeight;

   hr = Resize( m_Width, m_Height );
   
   return hr;
}

HRESULT D3D2DSQUARE::Resize( UINT Width, UINT Height )
{
   HRESULT hr = -1;

   m_Width = Width;
   m_Height = Height;

   D3D2DVERTEX* vtx;
   hr = m_pd3d2DVertex->Lock( 0, 0, (void**)&vtx, 0 );
   if( FAILED( hr ) )
      return -3;

   //頂点の座標
   vtx[0].x = 0.0f;           vtx[0].y = 0.0f;            vtx[0].z = 0.0f; vtx[0].rhw = 1.0f;
   vtx[1].x = (float)m_Width; vtx[1].y = 0.0f;            vtx[1].z = 0.0f; vtx[1].rhw = 1.0f;
   vtx[2].x = 0.0f;           vtx[2].y = (float)m_Height; vtx[2].z = 0.0f; vtx[2].rhw = 1.0f;
   vtx[3].x = (float)m_Width; vtx[3].y = (float)m_Height; vtx[3].z = 0.0f; vtx[3].rhw = 1.0f;

   //テクセル
   vtx[0].tu = 0.0f + 0.5f / (float)m_Width; vtx[0].tv = 0.0f + 0.5f / (float)m_Height;
   vtx[1].tu = 1.0f + 0.5f / (float)m_Width; vtx[1].tv = 0.0f + 0.5f / (float)m_Height;
   vtx[2].tu = 0.0f + 0.5f / (float)m_Width; vtx[2].tv = 1.0f + 0.5f / (float)m_Height;
   vtx[3].tu = 1.0f + 0.5f / (float)m_Width; vtx[3].tv = 1.0f + 0.5f / (float)m_Height;

   //頂点カラー
   vtx[0].color = 0xFFFFFFFF;
   vtx[1].color = 0xFFFFFFFF;
   vtx[2].color = 0xFFFFFFFF;
   vtx[3].color = 0xFFFFFFFF;

   m_pd3d2DVertex->Unlock();

   hr = S_OK;

   return hr;
}

//2Dポリゴンをレンダリングする
void D3D2DSQUARE::Render()
{
   m_pd3dDevice->SetFVF( D3D2DFVF );
   m_pd3dDevice->SetStreamSource( 0, m_pd3d2DVertex, 0, sizeof(D3D2DVERTEX) );
   //▲ポリゴンを2枚使用
   m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );
}

テクセルの座標を設定するとき、1ピクセルの半分の大きさで加算する理由に関してはメモ書きを参照してください。

---SSS.fx---


float4x4 m_WVP;                  //ワールド座標系 × ビュー座標系 × 遠近射影座標系
float4x4 m_WVPT;                 //ワールド座標系 × ビュー座標系 × 遠近射影座標系 × テクスチャー座標系
float4 m_LightDir;               //平行光源の方向ベクトル
float4 m_PointLightPos;          //点光源の位置ベクトル
float4 m_EyePos;                 //視点ベクトル
float4 m_Ambient = 0.0f;         //環境光
float  m_Transmissivity = 30.0f; //透過率
float4 m_PointLightColor;        //点光源の色
float  m_TU;                     //ブラーフィルターで使用する1テクセルの横幅の大きさ
float  m_TV;                     //ブラーフィルターで使用する1テクセルの縦幅の大きさ
float  m_ZF;                     //遠近射影行列の最遠近距離

sampler tex0 : register(s0);
sampler tex1 : register(s1);

//背面の深度値を取得する
struct VS_Z1_OUTPUT
{
   float4 Pos       : POSITION;
   float4 PosWVP    : TEXCOORD0;
};
VS_Z1_OUTPUT VS_Z1( float4 Pos    : POSITION,
                    float4 Normal : NORMAL,
                    float2 Tex    : TEXCOORD0 )
{
   VS_Z1_OUTPUT Out;
   
   Out.Pos      = mul( Pos, m_WVP );
   Out.PosWVP   = Out.Pos;
   
   return Out;
}
float4 PS_Z1( VS_Z1_OUTPUT In ) : COLOR0
{
   return In.PosWVP.z / m_ZF;
}

//厚みを取得する
struct VS_Z2_OUTPUT
{
   float4 Pos       : POSITION;
   float4 PosWVPT   : TEXCOORD0;
   float4 PosWVP    : TEXCOORD1;
};
VS_Z2_OUTPUT VS_Z2( float4 Pos    : POSITION,
                    float4 Normal : NORMAL,
                    float2 Tex    : TEXCOORD0 )
{
   VS_Z2_OUTPUT Out;
   
   Out.Pos      = mul( Pos, m_WVP );
   Out.PosWVPT  = mul( Pos, m_WVPT );
   Out.PosWVP   = Out.Pos;
   
   return Out;
}
float4 PS_Z2( VS_Z2_OUTPUT In ) : COLOR0
{
   //m_Transmissivityの値を小さくすると透明度が高くなるので、設定に注意すること
   return ( tex2Dproj( tex0, In.PosWVPT ).r - In.PosWVP.z / m_ZF ) * m_Transmissivity;
}

//ブラー処理を実行する
struct VS_Blur_OUTPUT
{
   float4 Pos    : POSITION;
   float2 Tex    : TEXCOORD0;
};

VS_Blur_OUTPUT VS_Blur( float4 Pos    : POSITION,
                        float2 Tex    : TEXCOORD0 )
{
   VS_Blur_OUTPUT Out;
   
   Out.Pos = Pos;
   Out.Tex = Tex;
   
   return Out;
}

float4 PS_Blur( VS_Blur_OUTPUT In ) : COLOR0
{
   float4 Col;
   
   //ひたすらサンプリングしぼかす
   Col = tex2D( tex0, In.Tex ) + 
         tex2D( tex0, In.Tex + float2( m_TU,         m_TV ) ) + 
         tex2D( tex0, In.Tex + float2( m_TU * 2.0f,  m_TV * 2.0f ) ) + 
         tex2D( tex0, In.Tex + float2( m_TU * 3.0f,  m_TV * 3.0f ) ) + 
         tex2D( tex0, In.Tex + float2( m_TU * 4.0f,  m_TV * 4.0f ) ) + 
         tex2D( tex0, In.Tex + float2( m_TU * 5.0f,  m_TV * 5.0f ) ) + 
         tex2D( tex0, In.Tex + float2( m_TU * 6.0f,  m_TV * 6.0f ) ) + 
         tex2D( tex0, In.Tex + float2( -m_TU,        -m_TV ) ) + 
         tex2D( tex0, In.Tex + float2( -m_TU * 2.0f, -m_TV * 2.0f ) ) + 
         tex2D( tex0, In.Tex + float2( -m_TU * 3.0f, -m_TV * 3.0f ) ) + 
         tex2D( tex0, In.Tex + float2( -m_TU * 4.0f, -m_TV * 4.0f ) ) +
         tex2D( tex0, In.Tex + float2( -m_TU * 5.0f, -m_TV * 5.0f ) ) +
         tex2D( tex0, In.Tex + float2( -m_TU * 6.0f, -m_TV * 6.0f ) );

   return Col / 13.0f;
}


//合成
struct VS_SSS_OUTPUT
{
   float4 Pos       : POSITION;
   float4 Col       : COLOR0;
   float2 Tex       : TEXCOORD0;
   float4 WVPT      : TEXCOORD1;
   float4 LocalPos  : TEXCOORD2;
};
VS_SSS_OUTPUT VS_SSS( float4 Pos     : POSITION,
                      float4 Normal  : NORMAL,
                      float2 Tex     : TEXCOORD0 )
{
   VS_SSS_OUTPUT Out;
   
   Out.Pos    = mul( Pos, m_WVP );
   Out.Tex    = Tex;

   //行列変換しない頂点座標
   Out.LocalPos = Pos;
   
   //テクスチャー座標系に変換
   Out.WVPT   = mul( Pos, m_WVPT );

   float3 L = -m_LightDir.xyz;   
   float3 N = normalize( Normal.xyz );
   
   //ランバート拡散照明によるライティング
   Out.Col = max( m_Ambient, dot(N, L) );
   
   return Out;
}

float4 PS_SSS( VS_SSS_OUTPUT In ) : COLOR0
{  
   float4 Out;
   
   //厚みマップから厚み情報を取得
   float Thickness  = tex2Dproj( tex1, In.WVPT ).r;
   
   //厚みが薄くなるほど明るくなるようにするため 1.0f から減算する
   Thickness = max( 0.0f, 1.0f - Thickness );
   
   //視点→頂点 と 頂点→点光源 のベクトルの内積
   float L = dot( normalize( In.LocalPos.xyz - m_EyePos.xyz ), normalize( m_PointLightPos.xyz - In.LocalPos.xyz ) );
   
   //負の数のとき(点光源がカメラの後ろにあるとき)は 0.0f にする。このときは光の透過が発生しなくなる
   L = max( 0.0f, L );
   
   float4 M = tex2D( tex0, In.Tex );
   
   //合成する
   Out = M * ( In.Col + Thickness * L * m_PointLightColor );
   
   return Out;
}

technique TShader
{
   //深度値を取得
   pass P0
   {
      VertexShader = compile vs_1_1 VS_Z1();
      PixelShader  = compile ps_2_0 PS_Z1();
   }

   //厚みを取得する
   pass P1
   {
      VertexShader = compile vs_1_1 VS_Z2();
      PixelShader  = compile ps_2_0 PS_Z2();
   }

   //ブラー処理を実行する
   pass P2
   {
      VertexShader = compile vs_1_1 VS_Blur();
      PixelShader  = compile ps_2_0 PS_Blur();
   }
   
   //合成
   pass P3
   {
      VertexShader = compile vs_1_1 VS_SSS();
      PixelShader  = compile ps_2_0 PS_SSS();
   }
}

表面化散乱シェーダーです。Z値の出力方法についてはメモ書きを参照してください。

---SSS.h---


class SSS : public D3D2DSQUARE
{
private:
   LPD3DXEFFECT m_pEffect;
   D3DXHANDLE m_pTechnique, m_pWVP, m_pWVPT, m_pLightDir, m_pEyePos, m_pAmbient, m_pPointLightColor, m_pPointLightPos, m_pTransmissivity, m_pTU, m_pTV, m_pZF;
   D3DXMATRIX m_matView, m_matProj;
   D3DXVECTOR4 m_CameraPos, m_LightDir, m_PointLightPos;

   LPDIRECT3DDEVICE9 m_pd3dDevice;
   D3DPRESENT_PARAMETERS* m_pd3dParameters;

public:
   SSS( LPDIRECT3DDEVICE9 pd3dDevice, D3DPRESENT_PARAMETERS* pd3dParameters );
   ~SSS();
   void Invalidate();
   void Restore();
   HRESULT Load();

   //1フレーム内の処理で変更しないと思われる各種パラメータのセット
   void SetCameraAndLight( D3DXVECTOR4* pCameraPos, D3DXVECTOR4* pLightDir, D3DXVECTOR4* pPointLightPos );

   //背面の深度値を取得
   void Step1_Begin();
   void Step1_BeginPass();
   void Step1_SetMatrix( D3DXMATRIX* pMatWorld, float pZF );
   void Step1_EndPass();
   void Step1_End();

   //手前面の深度値を取得し厚み情報を取得
   //透明度の設定
   void Step2_SetTransmissivity( float Transmissivity );
   void Step2_Begin();
   void Step2_BeginPass();
   void Step2_SetMatrix( D3DXMATRIX* pMatWorld, float pZF );
   void Step2_EndPass();
   void Step2_End();

   //厚みマップをぼかす
   void Step3_BlurFilter( float TU, float TV );

   //最終合成
   void Step4_Begin();
   void Step4_BeginPass();
   //点光源の色
   void Step4_SetPointLightColor( float PointLightColor );
   void Step4_SetPointLightColor( D3DXVECTOR4* pPointLightColor );
   void Step4_SetAmbient( float Ambient );
   void Step4_SetAmbient( D3DXVECTOR4* pAmbient );
   void Step4_SetMatrix( D3DXMATRIX* pMatWorld );
   void Step4_EndPass();
   void Step4_End();

   void CommitChanges();
   BOOL IsOK();
   LPD3DXEFFECT GetEffect(){ return m_pEffect; };
};

---SSS.cpp---


SSS::SSS( LPDIRECT3DDEVICE9 pd3dDevice, D3DPRESENT_PARAMETERS* pd3dParameters ) : D3D2DSQUARE( pd3dDevice, pd3dParameters )
{
   m_pd3dDevice = pd3dDevice;
   m_pd3dParameters = pd3dParameters;
   m_pEffect = NULL;
}

SSS::~SSS()
{
   SafeRelease( m_pEffect );
}

void SSS::Invalidate()
{
   if( m_pEffect )
      m_pEffect->OnLostDevice();
}

void SSS::Restore()
{
   if( m_pEffect )
      m_pEffect->OnResetDevice();
}

//初期化
HRESULT SSS::Load()
{
   D3DCAPS9 caps;
   HRESULT hr;

   m_pd3dDevice->GetDeviceCaps( &caps );
   if( caps.VertexShaderVersion >= D3DVS_VERSION( 1, 1 ) && caps.PixelShaderVersion >= D3DPS_VERSION( 2, 0 ) )
   {
      //2Dスプライトの初期化
      hr = D3D2DSQUARE::Load();
      if( FAILED( hr ) )
         return -1;

      //シェーダーの初期化
      LPD3DXBUFFER pErr = NULL;
      hr = D3DXCreateEffectFromFile( m_pd3dDevice, _T("sss.fx"), NULL, NULL, 0, NULL, &m_pEffect, &pErr );
      if( FAILED( hr ) )
         return -2;

      m_pTechnique       = m_pEffect->GetTechniqueByName( "TShader" );
      m_pWVP             = m_pEffect->GetParameterByName( NULL, "m_WVP" );
      m_pWVPT            = m_pEffect->GetParameterByName( NULL, "m_WVPT" );
      m_pLightDir        = m_pEffect->GetParameterByName( NULL, "m_LightDir" );
      m_pEyePos          = m_pEffect->GetParameterByName( NULL, "m_EyePos" );
      m_pAmbient         = m_pEffect->GetParameterByName( NULL, "m_Ambient" );
      m_pPointLightColor = m_pEffect->GetParameterByName( NULL, "m_PointLightColor" );
      m_pPointLightPos   = m_pEffect->GetParameterByName( NULL, "m_PointLightPos" );
      m_pTU              = m_pEffect->GetParameterByName( NULL, "m_TU" );
      m_pTV              = m_pEffect->GetParameterByName( NULL, "m_TV" );
      //透過率
      m_pTransmissivity  = m_pEffect->GetParameterByName( NULL, "m_Transmissivity" );
      //遠近射影行列の最遠近距離
      m_pZF              = m_pEffect->GetParameterByName( NULL, "m_ZF" );

      m_pEffect->SetTechnique( m_pTechnique );   
   }

   else
      return -3;

   return S_OK;
}

//各種ベクトルをセットする
void SSS::SetCameraAndLight( D3DXVECTOR4* pCameraPos, D3DXVECTOR4* pLightDir, D3DXVECTOR4* pPointLightPos )
{
   if( m_pEffect )
   {
      CopyMemory( &m_CameraPos, pCameraPos, sizeof( D3DXVECTOR4 ) );
      CopyMemory( &m_LightDir, pLightDir, sizeof( D3DXVECTOR4 ) );
      CopyMemory( &m_PointLightPos, pPointLightPos, sizeof( D3DXVECTOR4 ) );
   }
}

//STEP1:背面の深度値情報を取得する

void SSS::Step1_Begin()
{
   if(  m_pEffect )
   {
      m_pd3dDevice->GetTransform( D3DTS_VIEW, &m_matView );
      m_pd3dDevice->GetTransform( D3DTS_PROJECTION, &m_matProj );

      m_pEffect->Begin( NULL, 0 );
   }
}

void SSS::Step1_BeginPass()
{
   if( m_pEffect )
   {
      m_pEffect->BeginPass( 0 );
   }
}

//ローカル座標系
void SSS::Step1_SetMatrix( D3DXMATRIX* pMatWorld, float pZF )
{
   if( m_pEffect )
   {
      D3DXMATRIX m;

      m = (*pMatWorld) * m_matView * m_matProj;
      m_pEffect->SetMatrix( m_pWVP, &m );

      //遠近射影行列の最遠近距離
      m_pEffect->SetFloat( m_pZF, pZF );
   }
}

void SSS::Step1_EndPass()
{
   if( m_pEffect )
   {
      m_pEffect->EndPass();
   }
}

void SSS::Step1_End()
{
   if( m_pEffect )
   {
      m_pEffect->End();
   }
}

//STEP2:手前面の深度値を取得し、厚みを取得する

//透過率を設定
void SSS::Step2_SetTransmissivity( float Transmissivity )
{
   if( m_pEffect )
   {
      m_pEffect->SetFloat( m_pTransmissivity, Transmissivity );
   }
}

void SSS::Step2_Begin()
{
   if(  m_pEffect )
   {
      m_pEffect->Begin( NULL, 0 );
   }
}

void SSS::Step2_BeginPass()
{
   if( m_pEffect )
   {
      m_pEffect->BeginPass( 1 );
   }
}

//ローカル座標系
void SSS::Step2_SetMatrix( D3DXMATRIX* pMatWorld, float pZF )
{
   if( m_pEffect )
   {
      D3DXMATRIX m, m1, m2;

      m = (*pMatWorld) * m_matView * m_matProj;
      m_pEffect->SetMatrix( m_pWVP, &m );

      //テクスチャー座標系へ変換
      D3DXMatrixScaling( &m1, 0.5f, -0.5f, 1.0f );
      D3DXMatrixTranslation( &m2, 0.5f, 0.5f, 0.0f );
      m = (*pMatWorld) * m_matView * m_matProj * m1 * m2;
      m_pEffect->SetMatrix( m_pWVPT, &m );

      //遠近射影行列の最遠近距離
      m_pEffect->SetFloat( m_pZF, pZF );
   }
}

void SSS::Step2_EndPass()
{
   if( m_pEffect )
   {
      m_pEffect->EndPass();
   }
}

void SSS::Step2_End()
{
   if( m_pEffect )
   {
      m_pEffect->End();
   }
}

//STEP3:厚みマップにブラー処理を実行する

void SSS::Step3_BlurFilter( float TU, float TV )
{
   if(  m_pEffect )
   {
      m_pEffect->SetFloat( m_pTU, TU );
      m_pEffect->SetFloat( m_pTV, TV );

      m_pEffect->Begin( NULL, 0 );
      m_pEffect->BeginPass( 2 );

      D3D2DSQUARE::Render();

      m_pEffect->EndPass();
      m_pEffect->End();
   }
}

//STEP4:合成する

void SSS::Step4_Begin()
{
   if(  m_pEffect )
   {
      m_pEffect->Begin( NULL, 0 );
   }
}

void SSS::Step4_BeginPass()
{
   if( m_pEffect )
   {
      m_pEffect->BeginPass( 3 );
   }
}

//点光源の色をセットする
void SSS::Step4_SetPointLightColor( float PointLightColor )
{
   if( m_pEffect )
   {
      D3DXVECTOR4 P;
      P = D3DXVECTOR4( PointLightColor, PointLightColor, PointLightColor, 1.0f );
      m_pEffect->SetVector( m_pPointLightColor, &P );
   }
}

void SSS::Step4_SetPointLightColor( D3DXVECTOR4* pPointLightColor )
{
   if( m_pEffect )
      m_pEffect->SetVector( m_pPointLightColor, pPointLightColor );
}

//環境光をセットする
void SSS::Step4_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 SSS::Step4_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 SSS::Step4_SetMatrix( D3DXMATRIX* pMatWorld )
{
   if( m_pEffect )
   {
      D3DXMATRIX m, m1, m2;
      D3DXVECTOR4 v;

      m = (*pMatWorld) * m_matView * m_matProj;
      m_pEffect->SetMatrix( m_pWVP, &m );

      //カメラ位置
      m1 = (*pMatWorld) * m_matView;
      D3DXMatrixInverse( &m1, NULL, &m1 );
      D3DXVec4Transform( &v, &m_CameraPos, &m1 );
      m_pEffect->SetVector( m_pEyePos, &v );

      //平行光源の方向ベクトル
      D3DXMatrixInverse( &m1, NULL, pMatWorld );
      D3DXVec4Transform( &v, &m_LightDir, &m1 );
      D3DXVec3Normalize( (D3DXVECTOR3*)&v, (D3DXVECTOR3*)&v );
      m_pEffect->SetVector( m_pLightDir, &v );

      //点光源の位置ベクトル
      D3DXMatrixInverse( &m1, NULL, pMatWorld );
      D3DXVec4Transform( &v, &m_PointLightPos, &m1 );
      m_pEffect->SetVector( m_pPointLightPos, &v );

      //テクスチャー座標系へ変換
      D3DXMatrixScaling( &m1, 0.5f, -0.5f, 1.0f );
      D3DXMatrixTranslation( &m2, 0.5f, 0.5f, 0.0f );
      m = (*pMatWorld) * m_matView * m_matProj * m1 * m2;
      m_pEffect->SetMatrix( m_pWVPT, &m );

   }

   else
      m_pd3dDevice->SetTransform( D3DTS_WORLD, pMatWorld );
}

void SSS::Step4_EndPass()
{
   if( m_pEffect )
   {
      m_pEffect->EndPass();
   }
}

void SSS::Step4_End()
{
   if( m_pEffect )
   {
      m_pEffect->End();
   }
}

void SSS::CommitChanges()
{
   if( m_pEffect )
      m_pEffect->CommitChanges();
}


BOOL SSS::IsOK()
{
   if( m_pEffect )
      return TRUE;

   return FALSE;
}

表面化散乱クラスです。

---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_pMeshSun = NULL;
CDXUTMesh* m_pMeshTiger = NULL;

//ランバート拡散照明クラスの宣言
LAMBERT1* m_pLambert = NULL;

//表面化散乱クラスの宣言
SSS* m_pSSS = NULL;

//奥面と手前面の深度値情報
LPDIRECT3DTEXTURE9    m_pZMapTexture[2];
LPDIRECT3DSURFACE9    m_pZMapSurface[2];

//厚みマップにブラー適応
LPDIRECT3DTEXTURE9    m_pBlurFilterTexture[2];
LPDIRECT3DSURFACE9    m_pBlurFilterSurface[2];

//スクリーンの解像度
UINT nWidth = 1024;
UINT nHeight = 768;

//太陽の位置ベクトル
D3DXVECTOR4 LightPos = D3DXVECTOR4( 100.0f, 100.0f, 300.0f, 1.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_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_pMeshSun = new CDXUTMesh();
   m_pMeshSun->Create( m_pd3dDevice, _T("res\\sun.x") );
   m_pMeshSun->SetFVF( m_pd3dDevice, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 );

   //トラ
   m_pMeshTiger = new CDXUTMesh();
   m_pMeshTiger->Create( m_pd3dDevice, _T("res\\tiger.x") );
   m_pMeshTiger->SetFVF( m_pd3dDevice, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 );

   //ランバート拡散照明クラスの初期化
   m_pLambert = new LAMBERT1( m_pd3dDevice );
   m_pLambert->Load();
   
   //表面化散乱クラスの初期化
   m_pSSS = new SSS( m_pd3dDevice, &m_d3dParameters );
   m_pSSS->Load();


   //平行光源の位置ベクトルから方向ベクトルを計算する
   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_pLambert->Invalidate();
   m_pSSS->Invalidate();

   for( int i=0; i<2; i++ )
   {
      SafeRelease( m_pZMapSurface[i] );
      SafeRelease( m_pZMapTexture[i] );
      SafeRelease( m_pBlurFilterSurface[i] );
      SafeRelease( m_pBlurFilterTexture[i] );
   }
}

//デバイスのリセット後に初期化すべきオブジェクト
void Restore()
{
   m_pLambert->Restore();
   m_pSSS->Restore();

   for( int i=0; i<2; i++ )
   {
      //深度値情報を格納するサーフェイス
      m_pd3dDevice->CreateTexture( nWidth,
                                   nHeight,
                                   1,
                                   D3DUSAGE_RENDERTARGET,
                                   D3DFMT_A8R8G8B8,          //浮動小数点フォーマットでなくても十分だと思う
                                   D3DPOOL_DEFAULT,
                                   &m_pZMapTexture[i],
                                   NULL );
      m_pZMapTexture[i]->GetSurfaceLevel( 0, &m_pZMapSurface[i] );

      //ブラーフィルター適応後サーフェイス
      m_pd3dDevice->CreateTexture( nWidth,
                                   nHeight,
                                   1,
                                   D3DUSAGE_RENDERTARGET,
                                   D3DFMT_A8R8G8B8,          //浮動小数点フォーマットでなくても十分だと思う
                                   D3DPOOL_DEFAULT,
                                   &m_pBlurFilterTexture[i],
                                   NULL );
      m_pBlurFilterTexture[i]->GetSurfaceLevel( 0, &m_pBlurFilterSurface[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( 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 matWVP, matPProj, matView, matWorld, matTiger, matScaling, matTranslation;

      m_pd3dDevice->BeginScene();

      float zf = 700.0f;
      //遠近射影座標変換
      //クリップ面はアプリケーションごとに調整すること
      D3DXMatrixPerspectiveFovLH( &matPProj,
                                  D3DX_PI/4.0f,
                                  4.0f / 3.0f,
                                  30.0f,
                                  zf );
      m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matPProj );

      //ビュー座標変換
      D3DXMatrixIdentity( &matView );
      m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

      //トラのワールド行列作成
      D3DXMatrixScaling( &matScaling, 20.0f, 20.0f, 20.0f );
      D3DXMatrixTranslation( &matTranslation, 0.0f, 30.0f, 100.0 );
      matTiger = matScaling * matTranslation;

      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 );

      //表面化散乱が使用できるとき
      if( m_pSSS->IsOK() )
      {
         m_pSSS->SetCameraAndLight( &EyePos, &LightDir, &LightPos );

         //****************************************************************
         //ステップ1 : 背面の深度値をレンダリング
         //****************************************************************

         //背面レンダリングはこれで
         m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW );

         //新しいピクセル値が、現在のピクセル値以上のときに深度バッファに書き込むようにする
         m_pd3dDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_GREATEREQUAL );

         LPDIRECT3DSURFACE9 OldSurface = NULL;
         m_pd3dDevice->GetRenderTarget( 0, &OldSurface );
         m_pd3dDevice->SetRenderTarget( 0, m_pZMapSurface[0] );

         m_pd3dDevice->Clear( 0L,
                              NULL,
                              D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
                              0xFFFFFFFF, 
                              0.0f,    //深度値のチェックを通常の逆向きにするので0.0fでクリアする
                              0L
                            );

         m_pd3dDevice->SetTexture( 0, NULL );

         //トラ
         m_pSSS->Step1_Begin();
         m_pSSS->Step1_SetMatrix( &matTiger, zf );
         m_pSSS->Step1_BeginPass();
         m_pMeshTiger->m_pLocalMesh->DrawSubset( 0 );
         m_pSSS->Step1_EndPass();
         m_pSSS->Step1_End();

         //****************************************************************
         //ステップ2 : 手前面の深度値を取得し厚みを計算する
         //****************************************************************

         //手前面レンダリングはこれで
         m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );
         m_pd3dDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL );

         m_pd3dDevice->SetRenderTarget( 0, m_pZMapSurface[1] );
         m_pd3dDevice->SetTexture( 0, m_pZMapTexture[0] );

         m_pd3dDevice->Clear( 0L,
                              NULL,
                              D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
                              0xFFFFFFFF,
                              1.0f,
                              0L
                            );

         //トラ
         //透過率の設定
         m_pSSS->Step2_SetTransmissivity( 30.0f );
         m_pSSS->Step2_Begin();
         m_pSSS->Step2_SetMatrix( &matTiger, zf );
         m_pSSS->Step2_BeginPass();
         m_pMeshTiger->m_pLocalMesh->DrawSubset( 0 );
         m_pSSS->Step2_EndPass();
         m_pSSS->Step2_End();

         //****************************************************************
         //ステップ3 :ブラーフィルター適応
         //****************************************************************

         //D3DFVF_XYZRHW を使用するためZバッファは無効にする
         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->SetRenderTarget( 0, m_pBlurFilterSurface[0] );
         m_pd3dDevice->SetTexture( 0, m_pZMapTexture[1] );
         m_pSSS->Step3_BlurFilter( 1.0f / (float)nWidth, 0.0f );

         m_pd3dDevice->SetRenderTarget( 0, m_pBlurFilterSurface[1] );
         m_pd3dDevice->SetTexture( 0, m_pBlurFilterTexture[0] );
         m_pSSS->Step3_BlurFilter( 0.0f, 1.0f / (float)nHeight );

         //レンダーステートを戻す
         m_pd3dDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_TRUE );
         m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP );
         m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP );

         m_pd3dDevice->SetRenderTarget( 0, OldSurface );
         SafeRelease( OldSurface );
      }

      //背景などをレンダリングする

      m_pd3dDevice->Clear( 0L,
                           NULL,
                           D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
                           0x0, 
                           1.0f,
                           0L
                         );

      //背景レンダリング
      m_pLambert->Begin();
      D3DXMatrixIdentity( &matWorld );
      m_pLambert->SetMatrix( &matWorld, &LightDir );
      m_pd3dDevice->SetTexture( 0, m_pMeshBack->m_pTextures[0] );
      m_pLambert->SetAmbient( 0.1f );
      m_pLambert->BeginPass(0);
      m_pMeshBack->m_pLocalMesh->DrawSubset( 0 );
      m_pLambert->EndPass();

      //空レンダリング
      //深度バッファ書込み禁止
      m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE );
      m_pd3dDevice->SetTexture( 0, m_pMeshBack->m_pTextures[1] );
      m_pLambert->SetAmbient( 1.0f );
      m_pLambert->BeginPass();
      m_pMeshBack->m_pLocalMesh->DrawSubset( 1 );
      m_pLambert->EndPass();
      m_pLambert->End();

      //太陽レンダリング
      //ライト無効
      m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );
      //αブレンドを有効にする
      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 );

      D3DXMatrixTranslation( &matTranslation, LightPos.x, LightPos.y, LightPos.z );

      //ビルボード行列を取得(注意1)
      matWorld = GetBillBoardMatrix( m_pd3dDevice, &matTranslation );
      
      m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
      m_pd3dDevice->SetTexture( 0, m_pMeshSun->m_pTextures[0] );
      m_pMeshSun->m_pLocalMesh->DrawSubset( 0 );

      m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
      m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
      m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );

      //****************************************************************
      //ステップ4 : 表面化散乱を適応する
      //****************************************************************
      //トラ
      m_pSSS->Step4_Begin();
      m_pSSS->Step4_SetMatrix( &matTiger );
      //点光源の色
      m_pSSS->Step4_SetPointLightColor( 5.0f );
      //環境光
      m_pSSS->Step4_SetAmbient( 0.1f );
      //テクスチャーをセット
      m_pd3dDevice->SetTexture( 0, m_pMeshTiger->m_pTextures[0] );
      m_pd3dDevice->SetTexture( 1, m_pBlurFilterTexture[1] );
      m_pSSS->Step4_BeginPass();
      m_pMeshTiger->m_pLocalMesh->DrawSubset( 0 );
      m_pSSS->Step4_EndPass();
      m_pSSS->Step4_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;
}

以上です。

(注意1) ビルボードを参照してください。

さて、前にやった擬似表面化散乱ではモデルの形状に制限があったり問題があったんですが、 今回の表面化散乱でそのへんの問題はクリアされます。 でも完璧じゃないんです。残念ながら。単純にメッシュの背面の深度値、手前面の深度値により透過率を計算しているため 途中きれている場合(トラでいえば前足と後ろ足が重なって見える場合など)厚みが正しく計算されず正しく表示されません。 もっともブラー処理を行うため案外目立たないかもしれませんが。気になる方は自分で修正してください。

Prev  Top  Next
inserted by FC2 system