スポンサーサイト

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

DXライブラリで視差マッピング

こんにちは、DOLとは無縁のペチコです。
迷走だいすき。


前回のコグを動かすべくDXライブラリさんに手を出してみました。
DirectX直に扱うのに比べてなんて楽なんでしょ。すばらしいです。

しかもオリジナルシェーダを使うこともできるということで至れり尽くせりですね。

ということで、DXライブラリで視差マッピング(パララックスマッピング)をやってみようと思います。
わたしも色々調べてサンプルに助けられてますので、少しでも還元できれば幸いです。

視差マッピングとはバンプマッピングのすごいのってカンジ。
うんテキトーです。

バンプマッピング視差マッピング
バンプマッピング視差マッピング


サムネイルではわかりづらいですが、クリックしておっきくすると違いがわかると思います。


DXライブラリでHLSLはtechniqueを使えないので、頂点シェーダとピクセルシェーダを分けて書かないといけません。
ライブラリの方で定数レジスタは予め入れてくれてますが、ライトの方向ベクトルがビュー空間なのでcppの方でローカルを渡しています。

HLSLコードはDXライブラリ様のノーマルマップのサンプルを参考にしています。
C++側はサンプルのまま(一部付記しますが)で、モデルにノーマルマップとハイトマップを付加すればシェーダの変更だけで動きます。


ParallaxVS.fx

//頂点シェーダの入力
struct VS_INPUT
{
float4 Pos : POSITION ; // ローカル
float3 Tan : TANGENT0 ; // ローカル
float3 Bin : BINORMAL0 ; // ローカル
float3 Normal : NORMAL0 ; // ローカル
float2 Tex : TEXCOORD0 ; // テクスチャ座標
} ;

// 頂点シェーダーの出力
struct VS_OUTPUT
{
float4 Pos : POSITION ; // 射影
float2 Tex : TEXCOORD0 ; // テクスチャ座標
float3 L : TEXCOORD1; // ライト方向ベクトル(接空間)
float3 E : TEXCOORD2; // 視線ベクトル(接空間)
} ;

// C++ 側で設定する定数の定義
float4x4 g_World : register( c94 ) ; // ローカル→ワールド
float4x4 g_View : register( c6 ) ; // ワールド→ビュー
float4x4 g_Proj : register( c2 ) ; // ビュー→射影
float4 LightDirection : register( c15 ) ; //ライトの方向(ローカル)
float4 EyePos : register( c43 ) ; //視線ベクトル(ローカル)

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

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

// 視線ベクトルをローカル→接空間へ
float3 E = EyePos.xyz - VSInput.Pos.xyz;
VSOutput.E.x = dot( E, VSInput.Tan ) ;
VSOutput.E.y = dot( E, VSInput.Bin ) ;
VSOutput.E.z = dot( E, VSInput.Normal ) ;

// ライト方向ベクトルをローカル接空間へ
float3 L = LightDirection.xyz;
VSOutput.L.x = dot( L, VSInput.Tan ) ;
VSOutput.L.y = dot( L, VSInput.Bin ) ;
VSOutput.L.z = dot( L, VSInput.Normal ) ;

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

return VSOutput;
}

ParallaxPS.fx
 
// ピクセルシェーダの入力
struct PS_INPUT
{
float2 Tex : TEXCOORD0 ; // テクスチャ
float3 L : TEXCOORD1; //ライト方向(接空間)
float3 E : 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 ;
float4 AT2_SpotP0_SpotP1 ;
} ;

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

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

//視線ベクトルを正規化
float3 E = normalize( PSInput.E ) ;

//ライト方向ベクトル
float3 L = PSInput.L;

// テクスチャ座標
float2 Tex = PSInput.Tex;

//ノーマルマップのアルファから高さ情報の読み込み
float P = tex2D( NormalMapTexture, Tex ).a;
Tex = Tex + 0.015 * P * E.xy;

//ノーマルマップを-1.0~1.0の間に変換
float3 N = 2.0f * tex2D( NormalMapTexture, Tex ).rgb - 1.0f;

// ディフューズカラーとスペキュラカラーの蓄積値を初期化
float4 TotalDiffuse = float4( 0.0f, 0.0f, 0.0f, 0.0f ) ;
float4 TotalSpecular = float4( 0.0f, 0.0f, 0.0f, 0.0f ) ;

// DiffuseAngleGen = ディフューズ角度減衰率計算
float DiffuseAngleGen = saturate( dot( N, -L ) ) ;

// ディフューズカラー蓄積値 += ライトのディフューズカラー * マテリアルのディフューズカラー * ディフューズカラー角度減衰率 + ライトのアンビエントカラーとマテリアルのアンビエントカラーを乗算したもの
TotalDiffuse += cfLight.Diffuse * cfMaterial.Diffuse * DiffuseAngleGen + cfLight.Ambient ;

// ハーフベクトルの計算
float3 H = normalize( E - L );
// Temp = pow( max( 0.0f, N * H ), cfMaterial.Power.x )
float Temp = pow( max( 0.0f, dot( N, H ) ), cfMaterial.Power.x ) ;

// スペキュラカラー蓄積値 += Temp * ライトのスペキュラカラー
TotalSpecular += Temp * cfLight.Specular ;

// TotalDiffuse = ライトディフューズカラー蓄積値 + ( マテリアルのアンビエントカラーとグローバルアンビエントカラーを乗算したものとマテリアルエミッシブカラーを加算したもの )
TotalDiffuse += cfAmbient_Emissive ;
// SpecularColor = ライトのスペキュラカラー蓄積値 * マテリアルのスペキュラカラー
float4 SpecularColor = TotalSpecular * cfMaterial.Specular ;
// 出力カラー = TotalDiffuse * テクスチャカラー + SpecularColor
float4 TextureDiffuseColor = tex2D( DiffuseMapTexture, Tex ) ;

PSOutput.Color0.rgb = TextureDiffuseColor.rgb * TotalDiffuse.rgb + SpecularColor.rgb ;
// アルファ値 = テクスチャアルファ * マテリアルのディフューズアルファ * 不透明度
PSOutput.Color0.a = TextureDiffuseColor.a * cfMaterial.Diffuse.a * cfFactorColor.a ;

return PSOutput;
}

cppに追加

//カメラの位置
VECTOR C_Pos = GetCameraPosition() ;
//描画するモデルハンドルのワールド変換行列(Model_Handle部分を対応するものにする)
MATRIX MatWorld = MV1GetLocalWorldMatrix(Model_Handle);
//逆行列
MATRIX InvMatWorld = MInverse( MatWorld );

//視線ベクトル(ワールド→ローカル)
C_Pos = VTransform( C_Pos, InvMatWorld);
FLOAT4 cp = { C_Pos.x,C_Pos.y,C_Pos.z,1.0f };
SetVSConstF(43, cp);

//ライトの方向ベクトル(ワールド→ローカル)
VECTOR V_ld = GetLightDirection() ;
V_ld = VTransformSR( V_ld, InvMatWorld);
V_ld= VNorm( V_ld );
FLOAT4 ld = { V_ld.x,V_ld.y,V_ld.z,1.0f };
SetVSConstF(15, ld);

細かい説明は詳しく解説しているサイト様が色々ございますので割愛。
細々とした表記がライブラリ様と違いますがキニシナイ。

ピクセルシェーダ後半のフォン照明部分はほぼDXライブラリ様のサンプル通りです。
先様のサンプルはビュー座標から接空間に変換を行っていますが、こちらではローカル座標から同様のことをしております。

DXライブラリ様のノーマルマップのサンプルとなぜ違うのかといいますと、
GPUの行列計算は速いのですが、頂点シェーダなら頂点の数分、ピクセルシェーダなら解像度にもよりますが画面ピクセル分(場合によっては100万↑)の計算となるので、なるべく計算数が少なくなるように行列計算は頂点シェーダ内でと慮った次第でございます。

実際どのくらいの差がでるのか微妙なところですが、コードが短くなる(これが本音)のでよしとします。

C++側でライトと視線をローカルに変換(1フレーム1回)、頂点シェーダでそれを接空間に(頂点数回)、ピクセルシェーダはテクスチャ描画と照明計算のみになっています。

肝心の視差マッピングについてですが、まずノーマルマップのアルファ値にハイトマップ(高さ情報)が埋め込まれていることが前提となります。
この辺は「ハイトマップの作り方」とかで検索してください。

またモデルのmqoファイルはDXライブラリ様のDxLibModelViewerにてmv1に変換しておくと良さそげです。
バンプでなくノーマルマップ(法線マップ:青っぽいマップ画像)をお使いの場合は、その際に[マテリアル]→[凸凹マップ]から[法線マップ]にしておきましょう。

ピクセルシェーダ中の
float P = tex2D( NormalMapTexture, Tex ).a;
Tex = Tex + 0.015 * P * E.xy;

この2行が視差マッピング部分です。
ハイトマップを読み込んで、高さを付けています。
"0.015"となっている部分を変更することで高さが変わります。
ちなみにこの2行をコメントアウトするとバンプマッピングとなります。


ところで先ほどからベクトルだの行列だのエラソーに語ってますが、おばかなペチコは実は全然わかっていません。
元々文系ですし、学生時代の数学の偏差値は40台という輝かしい成績を残しています。
もうね、分数の掛け算すらわからないレヴェル。

ただ世の中には偉大な先人様達がたくさんいて、わかりやすい解説やサンプルを遺してくれてますので、こんなペチコでも遊べるというお話でした。


まだまだ勉強中なのでコグはいつになったら海に浮かべることができるのでしょうか…



コメントの投稿

非公開コメント

プロフィール

ペチコートさん

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

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

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