Nanashi-softプログラマ専用DirectX11開発


◇DirectX11プログラミング -法線ベクトルとかいうのを追加する-

会社で複雑な地形データをテスト表示した時のこと
「それ法線ベクトルが反映されていませんね」
新しい専門用語キタ~('-'*) ←

簡単に説明を聞くと,面の色がマテリアルなら,その境界になる頂点の色が法線ベクトル,だそうです
これがないと,どう色を付けていけばよいのかわからないとか?('-'*)?(←まるでわかっていない)

で,どうすれば良いのか?
前より少しは成長しているので,シェーダーで処理するものならセマンティクスにあるよね? と言うことで調べてみる
ありました。法線のセマンティクスは NORMALです
これがあると言うことは,シェーダーHLSLで記述しなければならない事項なのだとわかります
更に,セマンティクスは定数なので,『HLSL NORMAL』でググればサンプルが探せる事もわかります

どうやらインターフェースから変更する必要があるようです
頂点データに,法線ベクトルデータも付けてシェーダーに送り,ピクセルシェーダーで処理を行うようです


まずは,頂点構造体とレイアウトに法線ベクトルを追加
	//ポリゴン頂点構造体
struct Vertex3D {
float pos[3]; //頂点データ(x-y-z)
float col[4]; //マテリアル(r-g-b-a)
float tex[2]; //テクスチャー(x-y)
float nor[3]; //法線ベクトル(x-y-z)
};

//頂点レイアウト
//5番目のパラメータは先頭からのバイト数なので,COLORにはPOSITIONのfloat型4バイト×3を記述
D3D11_INPUT_ELEMENT_DESC hInElementDesc[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 4*3, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 4*3 + 4*4, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 4*3 + 4*4 + 4*2, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};
PMDからの読み込みデータ中には既に法線ベクトルはあるので,セットする部分を追加
		for(int i=0; i < TYOUTEN; i++){
hVectorData[i].pos[0] = modeldata->vertex[i].pos[0];
hVectorData[i].pos[1] = modeldata->vertex[i].pos[1];
hVectorData[i].pos[2] = modeldata->vertex[i].pos[2];
hVectorData[i].col[0] = 0.0f;
hVectorData[i].col[1] = 0.0f;
hVectorData[i].col[2] = 0.0f;
hVectorData[i].col[3] = 0.0f;
hVectorData[i].tex[0] = modeldata->vertex[i].uv[0];
hVectorData[i].tex[1] = modeldata->vertex[i].uv[1];
hVectorData[i].nor[0] = modeldata->vertex[i].normal_vec[0];
hVectorData[i].nor[1] = modeldata->vertex[i].normal_vec[1];
hVectorData[i].nor[2] = modeldata->vertex[i].normal_vec[2];
}
法線ベクトルの計算は,光が当たったところと,影になったところを表すものだそうです
なので,光源が必要です
頂点シェーダーに光源データを送れるようにします

既に,ワールド変換行列などを送っているので,そこに追加します
	struct ConstantBuffer
{
XMMATRIX mWorld;
XMMATRIX mView;
XMMATRIX mProjection;
XMFLOAT4 mLight;
};
メインループで,光源も送り込みます
				XMFLOAT4 LightPos = XMFLOAT4(0.0f, 40.0f, -70.0f, 1.0f);
hConstantBuffer.mLight = LightPos;
……で,どういう計算をすればいいのか?
一番簡単なのは『平行光源』だということで,これをやってみます
太陽のように満遍なく物体に光が当たっている状態です

平行光源は,法線ベクトルと光源ベクトルとの内積だそうです('-'*)
なぜかはわからなくていいです。私もわかりません(キッパリ) ←

HLSL言語にdot関数という『2つのベクトルの内積を返す』,そのままズバリの関数が用意されています

値 = dot(法線ベクトル, 光源ベクトル);
この値を,ピクセルシェーダーでカラー値に掛け合わせればいいです


・頂点シェーダー(vs.fx)
//入力用
struct vertexIn
{
float4 pos : POSITION0;
float4 col : COLOR0;
float2 tex : TEXCOORD0;
float3 nor : NORMAL0; //法線ベクトル
};

//出力用
struct vertexOut
{
float4 pos : SV_POSITION;
float4 col : COLOR;
float2 tex : TEXCOORD0;
float4 col2: COL2; //計算結果をピクセルシェーダーに送る用
};

//変換用行列
cbuffer ConstantBuffer : register( b0 )
{
matrix World; //ワールド変換行列
matrix View; //ビュー変換行列
matrix Projection; //透視射影変換行列
float4 Light; //ライト
}

vertexOut vs_main(vertexIn IN)
{
vertexOut OUT;

//頂点処理
OUT.pos = mul(IN.pos, World);
OUT.pos = mul(OUT.pos, View);
OUT.pos = mul(OUT.pos, Projection);
OUT.col = IN.col;
OUT.tex = IN.tex;

//光源処理
float3 L = normalize(Light.xyz);

//法線ベクトル処理
float3 nor;
//ワールド変換
nor = mul(IN.nor, (float3x3)World);
nor = normalize(nor);

//光源と法線の内積を求める
OUT.col2 = dot(nor, L);

return OUT;
}
次に,ピクセルシェーダーですが,マテリアルもテクスチャーもかければいいっぽいです(本当かどうかは不明)

前は明るすぎたので,強引に暗くする処理を入れていましたが,きちんと光源ができた為に不要になりました
なんかこう,スッキリした感じになりました

・ピクセルシェーダー(ps.fx)
//入力用
struct pixcelIn
{
float4 pos : SV_POSITION;
float4 col : COLOR;
float2 tex : TEXCOORD0;
float4 col2: COL2;
};

Texture2D txDiffuse : register( t0 );
SamplerState samLinear : register( s0 );

float4 ps_main(pixcelIn IN) : SV_Target
{
pixcelIn OUT;

OUT.col = saturate(IN.col2) * txDiffuse.Sample(samLinear, IN.tex);

return OUT.col;
}

float4 ps_main2(pixcelIn IN) : SV_Target
{
pixcelIn OUT;

OUT.col = saturate(IN.col2) * IN.col;

return OUT.col;
}

まずは,法線ベクトルを当てる前

このままで良いような気がしますが,目標はリアルなファンタジー世界なので,陰影は必要です

法線ベクトルを反映したところ

なんか光を当てたところが透けていますww
もっと光を強くするにはどうすればいいんだろうか(違
素体がきちんとあるモデルデータの服に当てないと(ぉ ←

色々調べてみたが,原因がわからなかった
要するに光源も法線も float3型なのに対して,カラーは float4型なので,一番後ろのα値部分に問題が出るのだろうと予想

頂点シェーダーの最後で,α値に 1.0fを放り込んでみた
	//光源と法線の内積を求める
OUT.col2 = dot(nor, L);

OUT.col2.a = 1.0f; //α値を無効にする
return OUT;

そうするとちょっと残念ですが透けずに陰影が付けられました


TOPプログラマ専用DirectX11開発

Melonbooks DL