DXライブラリでランバートシェーダーを作ります。ライティング処理の基本となるやつです(Lambert拡散反射)。DirectX11版。
ランバートシェーダーって何?
ランバート(Lambert)シェーダーとはライトの向きと面の法線の向きから光の量がどれだけ当たっているかを計算する光の反射モデルのこと。Lambert拡散反射とも。この光の量をマテリアルの色に加えることで陰影や光の反射などの表現ができるようになる。
シェーダーを学ぶと最初か2番目に出てくるであろう基本となるシェーダー。他にもトゥーンシェーダーなどいろいろなシェーダーがあるが光の計算についてはこのランバート処理を用いているものが多く、シェーダーには欠かせない存在。
実際の光の計算ではライトと面の法線の成す角度ではなく内積を用いて計算している。理由は処理が軽いのと値が-1~1の間に収まるので扱いやすいため。
サンプルコード
それでは実装。前回作ったライティング処理なしのシェーダーにコードを追加していく。
まず頂点シェーダーで頂点シェーダーの出力を扱う構造体 VS_OUTPUT を以下のように変更する。
頂点シェーダー
// 頂点シェーダーの出力を以下のように変更
struct VS_OUTPUT
{
float2 TexCoords0 : TEXCOORD0; // テクスチャ座標
float3 VPosition : TEXCOORD1; // 座標( ビュー空間 )
float3 VNormal : TEXCOORD2; // 法線( ビュー空間 )
float4 Position : SV_POSITION; // 座標( プロジェクション空間 )
};
続けて頂点シェーダーのmain関数に以下の処理を追加する。
//main
VS_OUTPUT main(VS_INPUT VSInput)
{
//以下の変数を追加
float3 lWorldNrm;
float3 lViewNrm;
// 頂点座標変換後に以下を追加
// 法線をビュー空間の角度に変換 =====================================================( 開始 )
// ローカルベクトルをワールドベクトルに変換
lWorldNrm.x = dot(VSInput.Normal, lLocalWorldMatrix[0].xyz);
lWorldNrm.y = dot(VSInput.Normal, lLocalWorldMatrix[1].xyz);
lWorldNrm.z = dot(VSInput.Normal, lLocalWorldMatrix[2].xyz);
// ワールドベクトルをビューベクトルに変換
lViewNrm.x = dot(lWorldNrm, g_Base.ViewMatrix[0].xyz);
lViewNrm.y = dot(lWorldNrm, g_Base.ViewMatrix[1].xyz);
lViewNrm.z = dot(lWorldNrm, g_Base.ViewMatrix[2].xyz);
// 法線をビュー空間の角度に変換 =====================================================( 終了 )
// 出力パラメータセット ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
// 頂点座標を保存
VSOutput.VPosition = lViewPosition.xyz;
// 法線を保存
VSOutput.VNormal = lViewNrm;
// 略
// 出力パラメータセット ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )
//出力パラメーターを返す
return VSOutput;
}
ピクセルシェーダーのmain関数を以下のように変更する。
// main関数
PS_OUTPUT main(PS_INPUT PSInput)
{
PS_OUTPUT PSOutput;
float4 TextureDiffuseColor;
float3 SpecularColor;
float3 Normal;
float DiffuseAngleGen;
float3 TotalDiffuse;
float3 TempF3;
float Temp;
float3 lLightDir;
// 法線の準備
Normal = normalize(PSInput.VNormal);
// ディフューズカラーの蓄積値を初期化
TotalDiffuse = float3(0.0f, 0.0f, 0.0f);
// ディレクショナルライトの処理 +++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
// ライト方向ベクトルのセット
lLightDir = g_Common.Light[0].Direction;
// ディフューズ色計算
// DiffuseAngleGen = ディフューズ角度減衰率計算
DiffuseAngleGen = saturate(dot(Normal, -lLightDir));
// ディフューズカラー蓄積値 += ライトのディフューズカラー * マテリアルのディフューズカラー * ディフューズカラー角度減衰率
TotalDiffuse += g_Common.Light[0].Diffuse * g_Common.Material.Diffuse.xyz * DiffuseAngleGen;
// ディレクショナルライトの処理 +++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )
// 出力カラー計算 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
// 出力カラー = TotalDiffuse * テクスチャカラー
TextureDiffuseColor = g_DiffuseMapTexture.Sample(g_DiffuseMapSampler, PSInput.TexCoords0);
PSOutput.Color0.rgb = TextureDiffuseColor.rgb * TotalDiffuse;
// アルファ値 = テクスチャアルファ * マテリアルのディフューズアルファ * 不透明度
PSOutput.Color0.a = TextureDiffuseColor.a * g_Common.Material.Diffuse.a * g_Base.FactorColor.a;
// 出力カラー計算 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )
// 出力パラメータを返す
return PSOutput;
}
ライトと面の法線の内積を求めるときにライトにマイナスをかけているのは法線と逆向きの状態になっているのを直すため。これで1に近いほど明るく、-1に近いほど暗くなるという直感にあった結果になる。
HLSLのシェーダーについてきちんと学びたい人はこの本がおすすめ。
描画が暗い、陰影がきつい場合
ランバートシェーダーを実装してみたが実際に使ってみると「全体的に暗い」「陰影がきつい」という結果になるかもしれない。そういった時の対策としてハーフランバートという処理がある。
ライトと面の法線の内積(上のコードでは DiffuseAngleGen )を以下のように補正する。
DiffuseAngleGen = DiffuseAngleGen * 0.5f + 0.5f;
こうすることで陰影の変化がゆるやかになる。他にも
DiffuseAngleGen = pow(DiffuseAngleGen * 0.5f + 0.5f, 2.0f);
結果を2乗する方法もある。こちらの方は上よりもピーキーな結果になる。
それでも暗いよ!という人はライトの設定を見直す、DxLibModelViewerでマテリアルの拡散光の設定を見直してみるといいかもしれない。

コメント