2011年6月23日木曜日

SSAO僕にも出来たよ!

シェーダプログラムをやっておかないとなと思って、さしあたってSSAOにロマンを感じていたので挑戦してみました。
シェーダの前提が全然わからず、かなり辛かったのですがいろいろ協力いただいてひとまず表示までこぎつけました。

ランバートライティングのみで光源がちょうど反対側でしょうか。
のっぺりしているところを撮りました。
同じ角度でSSAOを利かせますと、陰影がディティール感出ますね。
実装方法です。初心者向けです。
まずフレームワークというかウインドウの初期化やDirectXの初期化は適当に済ませてください。
まるぺけさんのhttp://marupeke296.com/TIPS_UltraShortDirectXProg.htmlとかいいと思います。

プログラムの方は以下の雰囲気です。
ID3DXEffect* pEffect;
D3DXMATRIX View, Proj;
LPDIRECT3DSURFACE9       backbuffer;
LPDIRECT3DTEXTURE9       texColor;
LPDIRECT3DSURFACE9       surColor;
LPDIRECT3DTEXTURE9       texNormalDepth;
LPDIRECT3DSURFACE9       surNormalDepth;
LPDIRECT3DTEXTURE9       rayMap;

// エフェクトの読み込み
LPD3DXBUFFER pErr = NULL;
HRESULT hr = D3DXCreateEffectFromFile(
 d3ddev,
 _T("SSAO.fx"),
 NULL,
 NULL,
 D3DXSHADER_DEBUG,
 NULL,
 &pEffect,
 &pErr);
if( FAILED( hr ) )
{
 if( pErr != NULL )
 { // コンパイルエラー出力
  const char *err = (char*)pErr->GetBufferPointer();
  pErr->Release();
 }
}
// バックバッファを覚えとく
d3ddev->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer);
// SSAOで使う。レイマップ/法線深度マップ/通常のカラーマップを作成します
// GetScreenWidth/GetScreenHeightはスクリーンサイズを取ってくるのを作ってください
d3ddev->CreateTexture(GetScreenWidth(), GetScreenHeight(), 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8,D3DPOOL_DEFAULT, &texColor, 0);
texColor->GetSurfaceLevel(0, &surColor);
d3ddev->CreateTexture(GetScreenWidth(), GetScreenHeight(), 1, D3DUSAGE_RENDERTARGET, D3DFMT_A32B32G32R32F,D3DPOOL_DEFAULT, &texNormalDepth, 0);
texNormalDepth->GetSurfaceLevel(0, &surNormalDepth);

// 16x16のレイマップを作成しますmakeRayMapという
// コールバック関数を自分で用意してテクスチャに書き込んじゃいます
d3ddev->CreateTexture(16, 16, 1, 0, D3DFMT_A32B32G32R32F, D3DPOOL_MANAGED, &rayMap, 0);
D3DXFillTexture(rayMap, makeRayMap, 0);

// ビュー変換・射影変換
D3DXMatrixPerspectiveFovLH( &Proj, D3DXToRadian(45), static_cast(GetScreenWidth())/static_cast(GetScreenHeight()), 1.0f, 10000.0f);
で初期化をしておきます。
makeRayMap関数は以下のとおりです。
コメントアウトしている方が本来のレイの作成方法なのですが、拾ってきたベクトル群の方が見た目いい感じだったので、そっちにしました。
static void WINAPI makeRayMap(D3DXVECTOR4* pOut, const D3DXVECTOR2* pTexCoord, const D3DXVECTOR2* pTexelSize, void* data)
{
/*
 float r = 1.0f * (float)rand() / (float)RAND_MAX;
 float t = 6.2831853f * (float)rand() / ((float)RAND_MAX + 1.0f);
 float cp = 2.0f * (float)rand() / (float)RAND_MAX - 1.0f;
 float sp = sqrt(1.0f - cp * cp);
 float ct = cos(t), st = sin(t);

 pOut->x = abs(r * sp * ct);
 pOut->y = abs(r * sp * st);
 pOut->z = abs(r * cp);
 pOut->w = 0;
*/
 static int i=0;

 D3DXVECTOR3 vec;
 switch(i%16)
 {
 case 0: vec = D3DXVECTOR3(0.53812504f, 0.18565957f, -0.43192f); break;
 case 1: vec = D3DXVECTOR3(0.13790712f, 0.24864247f, 0.44301823f); break;
 case 2: vec = D3DXVECTOR3(0.33715037f, 0.56794053f, -0.005789503f); break;
 case 3: vec = D3DXVECTOR3(-0.6999805f, -0.04511441f, -0.0019965635f); break;
 case 4: vec = D3DXVECTOR3(0.06896307f, -0.15983082f, -0.85477847f); break;
 case 5: vec = D3DXVECTOR3(0.056099437f, 0.006954967f, -0.1843352f); break;
 case 6: vec = D3DXVECTOR3(-0.014653638f, 0.14027752f, 0.0762037f); break;
 case 7: vec = D3DXVECTOR3(0.010019933f, -0.1924225f, -0.034443386f); break;
 case 8: vec = D3DXVECTOR3(-0.35775623f, -0.5301969f, -0.43581226f); break;
 case 9: vec = D3DXVECTOR3(-0.3169221f, 0.106360726f, 0.015860917f); break;
 case 10:vec = D3DXVECTOR3(0.010350345f, -0.58698344f, 0.0046293875f); break;
 case 11:vec = D3DXVECTOR3(-0.08972908f, -0.49408212f, 0.3287904f); break;
 case 12:vec = D3DXVECTOR3(0.7119986f, -0.0154690035f, -0.09183723f); break;
 case 13:vec = D3DXVECTOR3(-0.053382345f, 0.059675813f, -0.5411899f); break;
 case 14:vec = D3DXVECTOR3(0.035267662f, -0.063188605f, 0.54602677f); break;
 case 15:vec = D3DXVECTOR3(-0.47761092f, 0.2847911f, -0.0271716f); break;
 }
  // テクスチャなので0-1の値しか持てないので、-1,1を0,1へ変換
 pOut->x = (vec.x+1.f)*0.5f;
 pOut->y = (vec.y+1.f)*0.5f;
 pOut->z = (vec.z+1.f)*0.5f;
 pOut->w = 0.f;
 ++i;
}
毎フレのループが以下の感じです。
float f,l; // 毎フレ動かしたかったので適当な変数
struct d3dverts {
 float x, y, z, w;
 float u, v;
 enum { fvf = D3DFVF_XYZRHW | D3DFVF_TEX1 };
};

d3ddev->BeginScene();
f+=dt; // もちろん適当でdtはデルタタイムをどっかから持ってくる
l=sin(f)*2.f+300.f;

D3DXMATRIX mat;
D3DXMatrixLookAtLH( &View, &D3DXVECTOR3(l*sin(f),5.f,-l*cos(f)), &D3DXVECTOR3(0,0,0), &D3DXVECTOR3(0,1,0) );
D3DXMatrixIdentity( &mat );
mat = mat * View * Proj;
// シェーダ内で使う変数更新
pEffect->SetMatrix( "m_WVP", &mat );
pEffect->SetVector( "m_LightDir", &D3DXVECTOR4(1,1,1,0) );
pEffect->SetVector( "m_Ambient" , &D3DXVECTOR4(0.5,0.5,0.5,0));

// MRTをつかって普通のカラーレンダリングと法線と深度値のテクスチャを
// それぞれ surColor(texColor)/surNormalDepth(texNormalDepth)に出力する
d3ddev->SetRenderTarget(0, surColor);
d3ddev->SetRenderTarget(1, surNormalDepth);
d3ddev->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0, 1.0f, 0);

// 描画開始
pEffect->SetTechnique( "RenderScene0" );
UINT numPass;
pEffect->Begin( &numPass, 0 );

pEffect->BeginPass(0);
// ここはどうにかメッシュでもモデルデータを表示する処理を作っておいてください
for(auto ite = m_kMeshObjectList.begin(); m_kMeshObjectList.end()!=ite; ite++)
{
 (*ite)->Draw();
}
pEffect->EndPass();
// ここまででカラーと法線深度テクスチャの完成

// ここから上で作ったテクスチャをもとに画面に描画する
d3ddev->SetRenderTarget(0, backbuffer);
d3ddev->SetRenderTarget(1, NULL);  // これやらないと表示されない!!
d3ddev->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0, 1.0f, 0);
// それぞれ0,1,2の順番でそれぞれのテクスチャを設定する
d3ddev->SetTexture(0, rayMap );
d3ddev->SetTexture(1, texNormalDepth );
d3ddev->SetTexture(2, texColor );
        
// SSAOはピクセルシェーダだけなのでバーテックスシェーダは走らせない
pEffect->BeginPass(1);
// 画面いっぱいに4角ポリゴンを表示してピクセルシェーダを走らせる
// この時 d3dverts で適宜した D3DFVF_XYZRHW がヴァーテックスシェーダ走らせなくても計算済みの頂点(4角ポリ)
// だよと定義するものらしいです。
d3ddev->SetFVF( d3dverts::fvf  );
const d3dverts quadverts[4] = {
{ -0.5f+0.f,   -0.5f,                   0.0f, 1.0f, 0.0f, 0.0f},
{ -0.5f+GetScreenWidth(), -0.5f,                   0.0f, 1.0f, 1.0f, 0.0f},
{ -0.5f+0.f,   -0.5f+GetScreenHeight(), 0.0f, 1.0f, 0.0f, 1.0f},
{ -0.5f+GetScreenWidth(), -0.5f+GetScreenHeight(), 0.0f, 1.0f, 1.0f, 1.0f},
};
d3ddev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, quadverts, sizeof(d3dverts));
pEffect->EndPass();
pEffect->End();
d3ddev->EndScene();
d3ddev->Present( NULL, NULL, NULL, NULL );
以下がシェーダです。
これは
http://d.hatena.ne.jp/shuichi_h/20100318
から拝借したもので、コードの説明自体は同じことです。
SSAO.fx
はじめのバーテックスシェーダとピクセルシェーダは通常のモデル描画&カラー、法線深度値出力用

float4x4 m_WVP;
float4 m_LightDir;
float4 m_Ambient = 0.0f;
struct VS_OUTPUT
{
  float4 Pos   : POSITION;     //頂点の座標
  float4 Col   : COLOR0;       //頂点カラー
  float4 depth : COLOR1 ;
  float3 normal : COLOR2 ;
};
VS_OUTPUT VS( float4 Pos     : POSITION,   //頂点の座標
              float4 Normal  : NORMAL )    //法線ベクトル
{
   VS_OUTPUT Out = (VS_OUTPUT)0;
   Out.Pos    = mul( Pos, m_WVP );
   float3 L = -normalize( m_LightDir.xyz );
   float3 N = normalize( Normal.xyz );
   Out.Col = max( m_Ambient, dot(N, L) );
   // 深度値は座標そのもの
   Out.depth = Out.Pos;
   // 法線
   Out.normal = Normal.xyz;
   Out.normal = normalize(Out.normal);
   return Out;
}
struct SP_OUTPUT
{
  float4 Color  : COLOR0; // カラー
  float4 NormalDepth   : COLOR1; // 法線と深度
};
SP_OUTPUT PS( VS_OUTPUT In ): COLOR0
{  
  SP_OUTPUT Out = (SP_OUTPUT)0;
  Out.Color = In.Col;
  Out.NormalDepth.xyz = In.normal;    // xyzが法線で
  Out.NormalDepth.w = In.depth.z/In.depth.w;  // wに深度
  return Out;
}
float totStrength = 1.38;
float strength = 0.0007;
float offset = 18.0;
float falloff = 0.000002;
float rad = 0.03;

#define SAMPLES 16
const float invSamples = 1.0/SAMPLES;

sampler rayMap : register(s0) = sampler_state 
{ 
  MipFilter = NONE;
  MinFilter = POINT;
  MagFilter = POINT;
}; 
sampler normalMap : register(s1) = sampler_state
{
  MipFilter = NONE;
  MinFilter = LINEAR;
  MagFilter = LINEAR;
};
sampler colorMap : register(s2) = sampler_state
{
  MipFilter = NONE;
  MinFilter = LINEAR;
  MagFilter = LINEAR;
};
float4 SSAO( float2 uv : TEXCOORD0) : COLOR0
{
  float4 Output = (Float4)0;

  float4 currentPixelSample = tex2D(normalMap, uv);
  float currentPixelDepth = currentPixelSample.a;
  float3 norm = currentPixelSample.xyz * 2.0f - 1.0f;

  float bl = 0.0;
  float radD = rad / currentPixelDepth;

  float3 ray, occNorm;
  float2 se;
  float occluderDepth, depthDifference, normDiff;

  for(int i=0; i'<'SAMPLES; ++i)
  {
    float3 fres = tex2D(rayMap, float2(float(i)/16.f,0)).xyz*2.f-1.f;
    ray = radD * fres;
    se = uv + sign(dot(ray,norm)) * ray * float2(1.0f, -1.0f);
    float4 occluderFragment = tex2D(normalMap, se.xy);
    occNorm = occluderFragment.xyz * 2.0f - 1.0f;
    depthDifference = currentPixelDepth - occluderFragment.a;
    normDiff = (1.0 - dot(normalize(occNorm), normalize(norm)));
    bl += step(falloff, depthDifference) * normDiff * (1.0 - smoothstep(falloff, strength, depthDifference));
  }
  float ao = 1.0 - totStrength * bl * invSamples;
  Output = tex2D(colorMap, uv)*ao;
  return Output;

}
technique RenderScene0
{
  pass P0
  {
    VertexShader = compile vs_3_0 VS();
    PixelShader  = compile ps_3_0 PS();   
  }
  pass P1
  {
    PixelShader  = compile ps_3_0 RenderScenePS0();
  }
}

1 件のコメント:

  1. シェーダが実行できません。。
    そのコードだけじゃ実行不可なんでしょうか?

    返信削除