スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

DXライブラリでソフトシャドウシェーダ

こんにちは
毎回ブログを自分用のメモ帳としか思ってないペチコです

わかりやすい解説とかこれっぽっちも考えてません
というよりペチコが教えを乞いたいです


今回は間が空いてめんどくさくなったシャドウシェーダで遊びます
ちょっと色を付けてソフトシャドウにしてみましょう

シャドウについてはDXライブラリさんの「3Dアクション基本+深度値を利用した影表現基本」が素晴らしくわかりやすいのでご参考ください

モデルはいつものティ力(てぃちから)ちゃんです
深度バッファ描画



ソフトシャドウの流れはこんなカンジ

1.ライトから見た深度バッファを描画します
深度バッファ描画
この時点でなんとなく変だけど気にしないことにします

2.カメラを戻して影のみ描画
シャドウ描画
この時点では影はまだ鋭利です

3.その画面をぼかします
シャドウにブラーをかける
ぼけぼけになりました。ペチコの頭も

4.普通にレンダリングする時にぼかしたシャドウも一緒に適用して完成
ソフトシャドウ完成
妙に暗いのは前の手抜きランバートをベースにしたせい
網目が少ないのは深度バッファの解像度と深度バッファシェーダがおかしいせい

どっちもDXライブラリさん純正を使えば治ると思います
おかしなまま得意げに掲載するペチコなのでした

シャドウのやり方は色々あると思いますので上手い方法があればペチコにも教えてください


ではシェーダを見て行きます
↑のようにペチコは恐ろしく適当なのでその辺は適当に読み替えてください

C++側での準備として
・深度バッファ記録用スクリーン
・シャドウ描画用スクリーン
・シャドウぼかし用スクリーン
の3つを予め作っておきます

流れはDXライブラリさんの「3Dアクション基本+深度値を利用した影表現基本」をベースとして書いてきます


まず深度バッファ記録用スクリーンにライトから見た深度バッファを描画します

深度バッファ記録用頂点シェーダ
ShadowMap_1VS.vso
// 頂点シェーダーの入力
struct VS_INPUT
{
float4 Pos : POSITION ; // ローカル
} ;

// 頂点シェーダーの出力
struct VS_OUTPUT
{
float4 Pos : POSITION ; // 射影
float4 Tex : TEXCOORD0 ; // テクスチャ座標
//float4 Depth : TEXCOORD1 ; // 深度バッファ
//float3 E : TEXCOORD1; // 視線ベクトル(ローカル)
//float4 WorldPos : TEXCOORD2; // ワールドポジション
} ;

// C++ 側で設定する定数の定義
float4x4 g_World : register( c94 ) ; // ローカル→ワールド
float4x4 g_View : register( c6 ) ; // ワールド→ビュー
float4x4 g_Proj : register( c2 ) ; // ビュー→射影

// main関数
VS_OUTPUT main( VS_INPUT VSInput )
{
VS_OUTPUT VSOutput;

//ローカル→ワールド→ビュー→射影変換
VSOutput.Pos = mul(mul(mul(VSInput.Pos, g_World), g_View), g_Proj);
VSOutput.Tex = VSOutput.Pos;

return VSOutput;
}

深度バッファ記録用ピクセルシェーダ
ShadowMap_1PS.pso
// ピクセルシェーダーの入力
struct PS_INPUT
{
float4 Tex : TEXCOORD0; //深度バッファ用
} ;

// ピクセルシェーダーの出力
struct PS_OUTPUT
{
float4 Color0 : COLOR0 ;
} ;

sampler DiffuseMapTexture : register( s0 ) ; // ディフューズマップテクスチャ

// main関数
PS_OUTPUT main( PS_INPUT PSInput )
{
PS_OUTPUT PSOutput;

//PSOutput.Color0 = PSInput.Tex.z / PSInput.Tex.w;
PSOutput.Color0 = PSInput.Tex.z ;
PSOutput.Color0.a = tex2D( DiffuseMapTexture, PSInput.Tex.xy ).a;

return PSOutput;
}

特筆すべき所は何もありません
ライブラリさんの解説をご参考ください
↑で書いた流れの1.の画像が記録用スクリーンに描画されます


次に影のみ描画シェーダでシャドウ記録用スクリーンに描画します
カメラのビュー行列と射影行列をレジスタで渡す必要がありますのでその辺もライブラリさんをご参考ください←手抜き

シャドウ描画頂点シェーダ
ShadowMap_2VS.vso
// 頂点シェーダーの入力
struct VS_INPUT
{
float4 Pos : POSITION ; // ローカル
float2 Tex : TEXCOORD0 ; // テクスチャ座標
} ;

// 頂点シェーダーの出力
struct VS_OUTPUT
{
float4 Pos : POSITION ; // 射影
float2 Tex : TEXCOORD0 ; // テクスチャ座標
float4 Depth : TEXCOORD2 ; // 深度バッファ
} ;

// C++ 側で設定する定数の定義
float4x4 g_World : register( c94 ) ; // ローカル→ワールド
float4x4 g_View : register( c6 ) ; // ワールド→ビュー
float4x4 g_Proj : register( c2 ) ; // ビュー→射影

float4x4 cfLightViewMatrix : register( c43 ) ; // ライトのワールド → ビュー行列
float4x4 cfLightProjectionMatrix : register( c47 ) ; // ライトのビュー  → 射影行列

// main関数
VS_OUTPUT main( VS_INPUT VSInput )
{
VS_OUTPUT VSOutput;

//カメラ
float4 worldPosition = mul(VSInput.Pos, g_World);
float4 viewPosition = mul(worldPosition, g_View);
VSOutput.Pos = mul(viewPosition, g_Proj);

//ライト
float4 tmp = mul(cfLightViewMatrix,worldPosition);
VSOutput.Depth = mul(cfLightProjectionMatrix,tmp);

//uv
VSOutput.Tex=VSInput.Tex;

return VSOutput;
}

シャドウ描画ピクセルシェーダ
ShadowMap_2PS.pso
// ピクセルシェーダーの入力
struct PS_INPUT
{
float2 Tex : TEXCOORD0 ; // テクスチャUV
float4 Depth : TEXCOORD2 ; // テクスチャ座標
} ;

// ピクセルシェーダーの出力
struct PS_OUTPUT
{
float4 Color0 : COLOR0 ;
} ;

// マテリアルパラメータ
struct MATERIAL
{
float4 Diffuse ; // ディフューズカラー
float4 Specular ; // スペキュラカラー
float4 Power ; // スペキュラの強さ
} ;

// ライトパラメータ
struct LIGHT
{
float4 Position ; // 座標( ビュー空間 )
float3 Direction ; // 方向( ビュー空間 )
float4 Diffuse ; // ディフューズカラー
float4 Specular ; // スペキュラカラー
float4 Ambient ; // アンビエントカラーとマテリアルのアンビエントカラーを乗算したもの
float4 Range_FallOff_AT0_AT1 ; // x:有効距離 y:スポットライト用FallOff z:距離による減衰処理用パラメータ0 w:距離による減衰処理用パラメータ1
float4 AT2_SpotP0_SpotP1 ; // x:距離による減衰処理用パラメータ2 y:スポットライト用パラメータ0( cos( Phi / 2.0f ) ) z:スポットライト用パラメータ1( 1.0f / ( cos( Theta / 2.0f ) - cos( Phi / 2.0f ) ) )
} ;

// C++ 側で設定するテクスチャや定数の定義
sampler DiffuseMapTexture : register( s0 ) ; // ディフューズマップテクスチャ
sampler ShadowMapTexture : register( s1 ) ; // ディフューズマップテクスチャ
float4 cfAmbient_Emissive : register( c1 ) ; // エミッシブカラー + マテリアルアンビエントカラー * グローバルアンビエントカラー
MATERIAL cfMaterial : register( c2 ) ; // マテリアルパラメータ
float4 cfFactorColor : register( c5 ) ; // 不透明度等
LIGHT cfLight : register( c32 ) ; // ライトパラメータ

// main関数
PS_OUTPUT main( PS_INPUT PSInput )
{
PS_OUTPUT PSOutput;

//ライト方向の正規化
float3 L = normalize( cfLight.Direction );

// ライト目線によるZ値の再算出
float4 WvpPos = PSInput.Depth / PSInput.Depth.w;
float2 ShadowTex = WvpPos.xy * float2( 0.5, -0.5 ) + 0.5;
float4 ShadowDepth = tex2D(ShadowMapTexture, ShadowTex);

// テクスチャ
float4 D = tex2D( DiffuseMapTexture, PSInput.Tex ) ;

//色
PSOutput.Color0.rgb = float3(1,1,1);

float Depth = (WvpPos.z / WvpPos.w) - 0.0015f;
// 算出点がシャドウマップのZ値よりも大きければ影と判断
if( ShadowDepth.r < Depth){
PSOutput.Color0.rgb = PSOutput.Color0.rgb * 0.5f;
}
PSOutput.Color0.a = cfMaterial.Diffuse.a * D.a * cfFactorColor.a ;

return PSOutput;
}

ただのシャドウならここで色も一緒に描画しますが、ここではモデルは真っ白で影のみ描画しています
使い回しのせいでライトやらマテリアルの設定が残っていますがいりません
テクスチャもアルファを使わないなら削ってColor0.a=1.0f;でいいと思います


ここで描画されたスクリーンをブラーシェーダでぼかします
DrawPrimitive2DToShaderを使用しますので前回のグロー効果でも作った頂点データを予め作っておきます(VERTEX2DSHADER Vert[6];)

描画画面はシャドウぼかし用スクリーン、ディフューズマップテクスチャに↑の影描画用スクリーンを指定します

ブラーシェーダ(ピクセルシェーダ)
Blur_PS.pso
static const float2 PixelKernel[12] = 
{
{-0.326,-0.406},
{-0.840,-0.074},
{-0.696, 0.457},
{-0.203, 0.621},
{ 0.962,-0.195},
{ 0.473,-0.480},
{ 0.519, 0.767},
{ 0.185,-0.893},
{ 0.507, 0.064},
{ 0.896, 0.412},
{-0.322,-0.933},
{-0.792,-0.598}
};

sampler DiffuseMapTexture : register( s0 ) ; // ディフューズマップテクスチャ

// main関数
float4 main( float2 Tex : TEXCOORD0) : COLOR0
{
float4 colour = 0;
float2 TexSize = float2(1.0f/1024.0f, 1.0f/768.0f);

for (int i = 0; i < 12; i++)
{
colour += tex2D( DiffuseMapTexture, Tex + (PixelKernel[i] * TexSize) ).r;
}
colour*=1.0f/12.0f;
colour.a=1;

return colour;
}

一回では効きが悪いので実際はプログラム側にてループで数回適用しています
この部分はDXライブラリさんのブルーム効果基本辺りのぼかしを適用した方がいいと思います
ペチコはめんどくさがって簡単なシェーダを使っただけです


そして最後に通常描画を行いますが、その時に↑でぼかした影を適用します
ここでは前前前回のランバートをベースにしています

プログラム側で、描画用画面を裏画面、↑の影ぼかし用スクリーンをサンプラ1にシャドウマップテクスチャとして渡しています

シャドウ適用ランバート頂点シェーダ
ShadowMap_3VS.vso
// 頂点シェーダーの入力
struct VS_INPUT
{
float4 Pos : POSITION ; // ローカル
float2 Tex : TEXCOORD0 ; // テクスチャUV
float3 Normal : NORMAL0 ; // ローカル
} ;

// 頂点シェーダーの出力
struct VS_OUTPUT
{
float4 Pos : POSITION ; // 射影
float2 Tex : TEXCOORD0 ; // テクスチャUV
float3 Norm : TEXCOORD1 ; // ビュー
float4 SPos : TEXCOORD2 ; // スクリーン
} ;

// C++ 側で設定する定数の定義
float4x4 g_World : register( c94 ) ; // ローカル→ワールド
float4x4 g_View : register( c6 ) ; // ワールド→ビュー
float4x4 g_Proj : register( c2 ) ; // ビュー→射影

// main関数
VS_OUTPUT main( VS_INPUT VSInput )
{
VS_OUTPUT VSOutput;

//ローカル→ワールド→ビュー→射影変換
VSOutput.Pos = mul(mul(mul(VSInput.Pos, g_World), g_View), g_Proj);

// 法線をビューへ
float3 N = mul(VSInput.Normal,(float3x3)g_World);
VSOutput.Norm = mul(N,(float3x3)g_View);

// テクスチャUV
VSOutput.Tex = VSInput.Tex;

//スクリーン用
VSOutput.SPos.x = ( VSOutput.Pos.x + VSOutput.Pos.w)*0.5;
VSOutput.SPos.y = (-VSOutput.Pos.y + VSOutput.Pos.w)*0.5;
VSOutput.SPos.z = VSOutput.Pos.z;
VSOutput.SPos.w = VSOutput.Pos.w;

return VSOutput;
}

シャドウ適用ランバートピクセルシェーダ
ShadowMap_3VS.vso
// ピクセルシェーダーの入力
struct PS_INPUT
{
float2 Tex : TEXCOORD0 ; // テクスチャUV
float3 Norm : TEXCOORD1 ; // 法線(ビュー)
float4 SPos : TEXCOORD2 ; // スクリーン
} ;

// ピクセルシェーダーの出力
struct PS_OUTPUT
{
float4 Color0 : COLOR0 ;
} ;

// マテリアルパラメータ
struct MATERIAL
{
float4 Diffuse ; // ディフューズカラー
float4 Specular ; // スペキュラカラー
float4 Power ; // スペキュラの強さ
} ;

// ライトパラメータ
struct LIGHT
{
float4 Position ; // 座標( ビュー空間 )
float3 Direction ; // 方向( ビュー空間 )
float4 Diffuse ; // ディフューズカラー
float4 Specular ; // スペキュラカラー
float4 Ambient ; // アンビエントカラーとマテリアルのアンビエントカラーを乗算したもの
float4 Range_FallOff_AT0_AT1 ; // x:有効距離 y:スポットライト用FallOff z:距離による減衰処理用パラメータ0 w:距離による減衰処理用パラメータ1
float4 AT2_SpotP0_SpotP1 ; // x:距離による減衰処理用パラメータ2 y:スポットライト用パラメータ0( cos( Phi / 2.0f ) ) z:スポットライト用パラメータ1( 1.0f / ( cos( Theta / 2.0f ) - cos( Phi / 2.0f ) ) )
} ;

// C++ 側で設定するテクスチャや定数の定義
sampler DiffuseMapTexture : register( s0 ) ; // ディフューズマップテクスチャ
sampler ShadowMapTexture : register( s1 ) ; // シャドウマップテクスチャ
float4 cfAmbient_Emissive : register( c1 ) ; // エミッシブカラー + マテリアルアンビエントカラー * グローバルアンビエントカラー
MATERIAL cfMaterial : register( c2 ) ; // マテリアルパラメータ
float4 cfFactorColor : register( c5 ) ; // 不透明度等
LIGHT cfLight : register( c32 ) ; // ライトパラメータ

// main関数
PS_OUTPUT main( PS_INPUT PSInput )
{
PS_OUTPUT PSOutput;

//法線とライト方向の正規化
float3 N = normalize( PSInput.Norm );
float3 L = normalize( cfLight.Direction );

//ライトの強さ
float P = dot(N,-L);
P = clamp( P,0,1 );
//色
float4 C = P * cfLight.Diffuse * cfMaterial.Diffuse + cfLight.Ambient + cfAmbient_Emissive;
//テクスチャ
float4 D = tex2D( DiffuseMapTexture, PSInput.Tex );
float4 Shadow = tex2Dproj( ShadowMapTexture, PSInput.SPos );

PSOutput.Color0.rgb = D * Shadow * C.rgb;
PSOutput.Color0.a = cfMaterial.Diffuse.a * D.a * cfFactorColor.a ;

return PSOutput;
}

通常のランバートシェーダとの違いは、頂点シェーダの時点でスクリーン描画用にfloat4型のSPos(安易なネーミング)をTEXCOORD2に持っている所だけです
その座標を元にピクセルシェーダでシャドウマップを元の色にかけて描画しています
シャドウマップの読み込みは通常のtex2Dではなくtex2Dprojを使ってる所がキモです

tex2Dproj:
射影除算を使用して 2D テクスチャーをサンプリングします。ルックアップが実行される前に、テクスチャー座標が t.w で除算されます

うん。実は良く意味がわかってないんだ
結果良ければ全てよしッのスタンスなのですペチコは


取り敢えずはこれでソフトシャドウが実装できました
色々おかしな所は残ったままだけど、叩き台にはなるはず・・・・ハズ…




ちょっとぼかしすぎたかな?

スポンサーサイト
プロフィール

ペチコートさん

Author:ペチコートさん
大航海時代オンライン
Zephyros、Eurosサーバーに潜伏中

偽ペチコ1号・2号
ペチコの手下達
Zephyrosサーバーに潜伏中

最新記事
月別アーカイブ
カテゴリ
リンク
最新コメント
検索フォーム
QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。