Microsoft Visual Studio .NET 2003 Microsoft DirectX 9.0 SDK (December 2004) シェーダーモデル 2.0 |
■ヘアシェーディング | Prev Top Next |
関連ページ:フォンシェーディング バンプマッピング |
フォンシェーディング
今回はヘアシェーディングです。AMDのサイトにアルゴリズムが紹介されています。Hair Rendering and Shadingです。PDFファイルなので開くときは注意してください。 このページはAMDの記事を参考にしています。
髪の毛を表現するのに重要な点はいわゆる天使の輪が表現できるかです。 天使の頭にのってるリングじゃないぞ(笑)。 髪の毛のつやで光っているところです。
さて上の画像はヘアシェーディングです。天使の輪が表現できています。 次に下の画像は既出のフォンシェーディングです。一部に天使の輪が発生するだけとなっています。
やってることを紹介。
1.ハイライトを天使の輪で表現する。
2.髪の毛のラインに沿って光のてかり具合をずらす。
3.髪の毛のラインに沿って光のてかり具合の強度を調整する。
ではソースです。
---HairShader.fx---
float4x4 m_WVP; float4 m_LightDir; float4 m_EyePos; float4 m_Ambient = 1.0f; //接ベクトルのシフト値 float m_Shift[2] = { -0.1f, 0.1f }; //スペキュラーの調整値 float m_Exp[2] = { 50.0f, 30.0f }; sampler tex0 : register(s0); //ヘアテクスチャー sampler tex1 : register(s1); //R:シフトマップ G:マスクマップ struct VS_OUTPUT { float4 Pos : POSITION; float2 Tex : TEXCOORD0; float3 N : TEXCOORD1; //オブジェクトの法線ベクトル float3 E : TEXCOORD2; //頂点 -> 視点 ベクトル float3 T : TEXCOORD3; }; VS_OUTPUT VS( float3 Pos : POSITION, float3 Tangent : TANGENT0, float3 Binormal : BINORMAL0, float3 Normal : NORMAL, float2 Tex : TEXCOORD0 ) { VS_OUTPUT Out; Out.Pos = mul( float4( Pos, 1.0f ), m_WVP ); Out.Tex = Tex; //オブジェクトの法線ベクトルを正規化する Out.N = Normal; //頂点 -> 視点 へのベクトルを計算 Out.E = m_EyePos.xyz - Pos.xyz; Out.T = Tangent; return Out; } //接ベクトルをシフトする float3 ShiftTangent( float3 T, float3 N, float shift ) { float3 shiftedT = T + shift * N; return normalize( shiftedT ); } //Kajiya-Kay シェーディング float StrandSpecular( float3 T, float3 H, float exponent ) { float dotTH = dot( T, H ); float sinTH = sqrt( 1.0f - dotTH * dotTH ); return pow( sinTH, exponent ); } float4 PS( VS_OUTPUT In ) : COLOR { float3 T = normalize( In.T ); float3 N = normalize( In.N ); float3 E = normalize( In.E ); float3 L = -m_LightDir.xyz; //ハーフベクトルを計算 float3 H = normalize( L + E ); //シフトおよびマスク情報をテクスチャーから取得する float2 map = tex2D( tex1, In.Tex ).rg; //シフト情報は[ -0.5f 〜 0.5f ]にする map.r -= 0.5f; //接ベクトルをシフトする float3 t1 = ShiftTangent( T, N, m_Shift[0] + map.r ); float3 t2 = ShiftTangent( T, N, m_Shift[1] + map.r ); //髪の色を取得 float4 colored = tex2D( tex0, In.Tex ); // ランバート拡散照明 float3 diffuse = colored * max( m_Ambient, dot( N, L ) ); float3 specular = StrandSpecular( t1, H, m_Exp[0] ); specular += StrandSpecular( t2, H, m_Exp[1] ) * colored; //スペキュラーにマスクを適応する specular *= map.g; float4 Out; //フォンシェーディング(おまけ) // float s = dot( H, N ); // Out.rgb = diffuse + pow( s, 30.0f ); Out.rgb = diffuse + specular; Out.a = colored.a; return Out; } technique TShader { pass P0 { VertexShader = compile vs_2_0 VS(); PixelShader = compile ps_2_0 PS(); } }
詳しいことはAMDのサイトを見ればわかると思うので簡単に説明します。
まず重要なのは接ベクトル(Tangent)を使用しているところでしょう。
接ベクトルはXファイルに存在しないので、D3DXComputeTangentFrameEx関数を使用して作成します。バンプマッピングを参照してください。
ただ一点だけ注意。接ベクトルは図1のように髪の毛のラインに沿って作成するようにしてください。
図1
2段がまえでハイライトを計算しているため、ShiftTangentとStrandSpecular関数を2回コールします。 それぞれ白飛びしている部分と、マテリアル色が強調されている部分の計算です。 最終的にこれらの結果を合成してハイライトとします。
ShiftTangent関数について。これは接ベクトルを法線ベクトル方向にずらすことで ハイライトが発生する位置をずらします。 この処理の意味はAMDサイトの画像見ればわかると思います。
StrandSpecular関数について。Kajiya-Kay というシェーディングモデルらしいですがよく知りません。 なおAMDサイトのサンプルソースを若干変更しています。
最後にマスク処理について。髪の毛のハイライトの強度を調整します。
---HairShader.h---
class HAIR_SHADER { private: LPD3DXEFFECT m_pEffect; D3DXHANDLE m_pTechnique, m_pWVP, m_pLightDir, m_pAmbient, m_pEyePos, m_pShift, m_pExp; D3DXMATRIX m_matView, m_matProj; LPDIRECT3DDEVICE9 m_pd3dDevice; public: HAIR_SHADER( LPDIRECT3DDEVICE9 pd3dDevice ); ~HAIR_SHADER(); void Invalidate(); void Restore(); HRESULT Load(); void Begin(); void BeginPass(); void SetAmbient( float Ambient ); void SetAmbient( D3DXVECTOR4* pAmbient ); void SetShiftAndExp( float Shift1, float Shift2, float Exp1, float Exp2 ); void SetMatrix( D3DXMATRIX* pMatWorld, D3DXVECTOR4* pCameraPos, D3DXVECTOR4* pLightDir ); void CommitChanges(); void EndPass(); void End(); BOOL IsOK(); LPD3DXEFFECT GetEffect(){ return m_pEffect; }; };
---HairShader.cpp---
HAIR_SHADER::HAIR_SHADER( LPDIRECT3DDEVICE9 pd3dDevice ) { m_pd3dDevice = pd3dDevice; m_pEffect = NULL; } HAIR_SHADER::~HAIR_SHADER() { SafeRelease( m_pEffect ); } void HAIR_SHADER::Invalidate() { if( m_pEffect ) m_pEffect->OnLostDevice(); } void HAIR_SHADER::Restore() { if( m_pEffect ) m_pEffect->OnResetDevice(); } HRESULT HAIR_SHADER::Load() { D3DCAPS9 caps; m_pd3dDevice->GetDeviceCaps( &caps ); if( caps.VertexShaderVersion >= D3DVS_VERSION( 2, 0 ) && caps.PixelShaderVersion >= D3DPS_VERSION( 2, 0 ) ) { LPD3DXBUFFER pErr = NULL; HRESULT hr = D3DXCreateEffectFromFile( m_pd3dDevice, _T("HairShader.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_pLightDir = m_pEffect->GetParameterByName( NULL, "m_LightDir" ); m_pEyePos = m_pEffect->GetParameterByName( NULL, "m_EyePos" ); m_pAmbient = m_pEffect->GetParameterByName( NULL, "m_Ambient" ); m_pShift = m_pEffect->GetParameterByName( NULL, "m_Shift" ); m_pExp = m_pEffect->GetParameterByName( NULL, "m_Exp" ); m_pEffect->SetTechnique( m_pTechnique ); } else { return -1; } } else { return -2; } return S_OK; } void HAIR_SHADER::Begin() { if( m_pEffect ) { m_pd3dDevice->GetTransform( D3DTS_VIEW, &m_matView ); m_pd3dDevice->GetTransform( D3DTS_PROJECTION, &m_matProj ); m_pEffect->Begin( NULL, 0 ); } } void HAIR_SHADER::BeginPass() { if( m_pEffect ) m_pEffect->BeginPass( 0 ); } void HAIR_SHADER::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 HAIR_SHADER::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 HAIR_SHADER::SetShiftAndExp( float Shift1, float Shift2, float Exp1, float Exp2 ) { if( m_pEffect ) { D3DXVECTOR2 s = D3DXVECTOR2( Shift1, Shift2 ); D3DXVECTOR2 e = D3DXVECTOR2( Exp1, Exp2 ); m_pEffect->SetValue( m_pShift, (LPVOID)&s, sizeof( D3DXVECTOR2 ) ); m_pEffect->SetValue( m_pExp, (LPVOID)&e, sizeof( D3DXVECTOR2 ) ); } } void HAIR_SHADER::SetMatrix( D3DXMATRIX* pMatWorld, D3DXVECTOR4* pCameraPos, D3DXVECTOR4* pLightDir ) { if( m_pEffect ) { D3DXMATRIX m; D3DXVECTOR4 v; m = (*pMatWorld) * m_matView * m_matProj; m_pEffect->SetMatrix( m_pWVP, &m ); //カメラ位置 m = (*pMatWorld) * m_matView; D3DXMatrixInverse( &m, NULL, &m ); D3DXVec4Transform( &v, pCameraPos, &m ); m_pEffect->SetVector( m_pEyePos, &v ); //Light D3DXMatrixInverse( &m, NULL, pMatWorld ); D3DXVec4Transform( &v, pLightDir, &m ); D3DXVec3Normalize( (D3DXVECTOR3*)&v, (D3DXVECTOR3*)&v ); m_pEffect->SetVector( m_pLightDir, &v ); } else m_pd3dDevice->SetTransform( D3DTS_WORLD, pMatWorld ); } void HAIR_SHADER::EndPass() { if( m_pEffect ) { m_pEffect->EndPass(); } } void HAIR_SHADER::End() { if( m_pEffect ) { m_pEffect->End(); } } void HAIR_SHADER::CommitChanges() { if( m_pEffect ) m_pEffect->CommitChanges(); } BOOL HAIR_SHADER::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_pMeshFace = NULL; CDXUTMesh* m_pMeshHair = NULL; //ランバート拡散照明クラスの宣言 LAMBERT1* m_pLambert1 = NULL; //ヘアシェーダークラスの宣言 HAIR_SHADER* m_pHairShader = NULL; //シフトおよびマスクマップの宣言 LPDIRECT3DTEXTURE9 m_pShiftMaskMap = NULL; //スクリーンの解像度 UINT nWidth = 1024; UINT nHeight = 768; D3DXVECTOR4 LightPos = D3DXVECTOR4( 50.0f, 100.0f, 750.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_pLambert1 = new LAMBERT1( m_pd3dDevice ); m_pLambert1->Load(); //ヘアシェーダークラスの初期化 m_pHairShader = new HAIR_SHADER( m_pd3dDevice ); m_pHairShader->Load(); //モデルのロード //顔 m_pMeshFace = new CDXUTMesh(); m_pMeshFace->Create( m_pd3dDevice, _T("face.x") ); m_pMeshFace->SetFVF( m_pd3dDevice, D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 ); //髪 m_pMeshHair = new CDXUTMesh(); m_pMeshHair->Create( m_pd3dDevice, _T("hair.x") ); //ヘアシェーダー内で接ベクトル(Tangent)を使用するので、 //頂点データを定義する(詳細はオンラインマニュアルを参照) D3DVERTEXELEMENT9 decl[] = { {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, {0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT, 0}, {0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 0}, {0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0}, {0, 48, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, D3DDECL_END() }; //新しい頂点情報に基づき頂点データを再生成する m_pMeshHair->SetVertexDecl( m_pd3dDevice, decl ); //接ベクトルのシフトマップとスペキュラーのマスクマップ D3DXCreateTextureFromFileEx( m_pd3dDevice, _T("ShiftMaskMap.bmp"), D3DX_DEFAULT, D3DX_DEFAULT, 1, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0x0, NULL, NULL, &m_pShiftMaskMap ); 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_pHairShader->Invalidate(); } //デバイスのリセット後に初期化すべきオブジェクト void Restore() { m_pLambert1->Restore(); m_pHairShader->Restore(); //固定機能パイプラインライティングを設定する 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 matPProj, matView, matWorld; //遠近射影座標変換 //クリップ面はアプリケーションごとに調整すること D3DXMatrixPerspectiveFovLH( &matPProj, D3DX_PI/4.0f, 4.0f / 3.0f, 30.0f, 700.0f ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matPProj ); //ビュー座標変換 D3DXMatrixIdentity( &matView ); m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); m_pd3dDevice->BeginScene(); 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 ); m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xFF0F0F0F, 1.0f, 0L ); //顔レンダリング m_pLambert1->Begin(); D3DXMatrixIdentity( &matWorld ); m_pLambert1->SetMatrix( &matWorld, &LightDir ); m_pd3dDevice->SetTexture( 0, m_pMeshFace->m_pTextures[0] ); m_pLambert1->SetAmbient( 0.1f ); m_pLambert1->BeginPass(0); m_pMeshFace->m_pLocalMesh->DrawSubset( 0 ); m_pLambert1->EndPass(); m_pLambert1->End(); //レンダーステートを変更する m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); 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 ); //髪をレンダリング m_pHairShader->Begin(); D3DXMatrixIdentity( &matWorld ); m_pHairShader->SetMatrix( &matWorld, &EyePos, &LightDir ); m_pd3dDevice->SetTexture( 0, m_pMeshHair->m_pTextures[0] ); m_pd3dDevice->SetTexture( 1, m_pShiftMaskMap ); m_pHairShader->SetShiftAndExp( -0.1f, 0.1f, 50.0f, 30.0f ); m_pHairShader->SetAmbient( 0.25f ); m_pHairShader->BeginPass(); m_pMeshHair->m_pLocalMesh->DrawSubset( 0 ); m_pHairShader->EndPass(); m_pHairShader->End(); m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW ); 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; }
以上です。
最後に
エラー?
なにこれ?
光源が逆光状態のときこうなるんだが、フォンシェーディングでも発生します。
ハーフベクトルのせい?