DXライブラリでポイントライトを実装する方法について説明しています。ディレクショナルライトと異なり位置情報を持つライトで光源やオブジェクトの位置によって光の当たり方が変わります。
ポイントライトとは
ポイントライトとは位置情報を持つライトのこと。ディレクショナルライトとは異なり光源やオブジェクトが移動して位置が変わると光の当たり方が変わる。位置情報があるのでライトと物体の距離から光の減衰も計算することもできる。現実世界のライトと同じ存在。

実際に実装する場合はポイントライトからの光の入射方向と距離を求めて光の当たり方を計算する。
なおポイントライトは全方向に光を発するためディレクショナルライトのように向きの情報は持たない。
サンプルコード
それでは実装。ポイントライトを実際に使う場合は他のライトと併用して使うことが多いのでディレクショナルライトとポイントライトがそれぞれ1つずつある状態を想定したコードを用意。
まずDXライブラリでは最初にディレクショナルライト1つのみ用意されているのでポイントライトを使う場合は CreatePointLightHandle() でポイントライトを作成しておく。
LightMng.cpp
//以下の変数・関数を追加および変更
static int PointLightHandle; //自分で作成するポイントライト
//初期化
void LightMng_Initialize() {
// 略
// ポイントライトを作成
PointLightHandle = CreatePointLightHandle(VGet(0.0f, 1.0f, 0.0f), 3.0f, 0.0f, 1.0f, 0.0f);
// ポイントライトの色を青色にする(わかりやすくするための処理)
SetLightDifColorHandle(PointLightHandle, GetColorF(0.0f, 0.0f, 1.0f, 0.0f));
}
ここからピクセルシェーダー側の処理。まずライトの種類ごとに関数化する。
まずはディレクショナルライトの処理を関数化する。スペキュラー計算も合わせて行っている。
//ディレクショナルライトを計算する関数
inline void CalcDirectionalLight(const in PS_INPUT PSInput, const in int LightNo, const in float3 Normal, inout float3 TotalDiffuse, inout float3 TotalSpecular)
{
float3 lLightDir; //ライトの方向
float DiffuseAngleGen; //ディフューズカラー角度減衰率
float3 refDir; //反射ベクトル
float3 Surface_to_Eye; //面から視点へのベクトル
float3 TempF3;
float Temp;
// ライト方向ベクトルのセット
lLightDir = g_Common.Light[LightNo].Direction;
// ディフューズカラー値計算 =======================================================( 開始 )
// DiffuseAngleGen = ディフューズ角度減衰率計算
DiffuseAngleGen = saturate(dot(Normal, -lLightDir));
DiffuseAngleGen = DiffuseAngleGen * 0.5f + 0.5f;
// ディフューズカラー蓄積値 += ライトのディフューズカラー * マテリアルのディフューズカラー * ディフューズカラー角度減衰率 + ライトのアンビエントカラーとマテリアルのアンビエントカラーを乗算したもの
TotalDiffuse += g_Common.Light[LightNo].Diffuse * g_Common.Material.Diffuse.xyz * DiffuseAngleGen + g_Common.Light[LightNo].Ambient.xyz;
// ディフューズカラー値計算 =======================================================( 終了 )
// スペキュラーカラー値計算 =======================================================( 開始 )
// 頂点から視点へのベクトルを求める
Surface_to_Eye = normalize(-PSInput.VPosition);
//---------- ハーフベクトルを利用したスペキュラー計算 ----------
//ハーフベクトルの計算
//TempF3 = normalize(Surface_to_Eye - lLightDir);
//スペキュラーを求める pow( max( 0.0f, N * H ), g_Common.Material.Power )
//Temp = pow(max(0.0f, dot(Normal, TempF3)), g_Common.Material.Power);
// ---------- 反射ベクトルを厳密に求めてスペキュラー計算 ----------
// 反射ベクトルを求める
refDir = normalize(reflect(lLightDir, Normal));
// スペキュラの強さを求める
Temp = pow(saturate(dot(refDir, Surface_to_Eye)), g_Common.Material.Power);
// スペキュラカラー蓄積値 += Temp * ライトのスペキュラカラー
TotalSpecular += Temp * g_Common.Light[0].Specular;
// スペキュラーカラー値計算 =======================================================( 終了 )
}
引数にある LightNo はライトの番号。詳しい説明は後述。
次にポイントライトを実装する。もちろん関数化しておく。
//ポイントライトを計算する関数
inline void CalcPointLight(const in PS_INPUT PSInput, const in int LightNo, const in float3 Normal, inout float3 TotalDiffuse, inout float3 TotalSpecular)
{
float3 lLightDir; //ライトの方向
float Distance; //頂点とライトの距離
float3 lLightGen; //減衰率
float DiffuseAngleGen; //ディフューズカラー角度減衰率
// 頂点からライトへの方向ベクトルの計算
lLightDir = normalize(PSInput.VPosition.xyz - g_Common.Light[LightNo].Position.xyz);
// 距離減衰値計算 =======================================================( 開始 )
// 頂点とライトの距離を求める
Distance = length(PSInput.VPosition.xyz - g_Common.Light[LightNo].Position.xyz);
// 減衰率の計算
lLightGen = 1.0f - 1.0f / g_Common.Light[LightNo].RangePow2 * Distance;
// 減衰率がマイナスにならにように補正をかける
lLightGen *= step(0.0f, lLightGen);
// 減衰率を指数関数的にする
lLightGen = pow(lLightGen, 2.0f);
// 距離減衰値計算 =======================================================( 終了 )
// DiffuseAngleGen = ディフューズ角度減衰率計算
DiffuseAngleGen = saturate(dot(Normal, -lLightDir));
// ディフューズカラー蓄積値 += ( ライトのディフューズカラー * マテリアルディフューズカラー * ディフューズカラー角度減衰率 + ライトのアンビエントカラーとマテリアルのアンビエントカラーを乗算したもの ) * 距離・スポットライトの角度減衰率
TotalDiffuse += (g_Common.Light[LightNo].Diffuse * g_Common.Material.Diffuse.xyz * DiffuseAngleGen + g_Common.Light[LightNo].Ambient.xyz) * lLightGen;
}
処理の流れとしては以下の通り。
- 各頂点からポイントライトへの方向ベクトルを計算する
- 各頂点からポイントライトの距離を計算する
- 距離を元に光の減衰率を求める
- ディレクショナルライトと同じくランバート拡散反射光(ディフューズ角度減衰率)を求める
- 4の結果に3の光の減衰率を乗算する
ここではスペキュラーの計算はしていないがもしポイントライトによるスペキュラーの計算をする場合はディレクショナルライトでのスペキュラー計算をそのままコピーすればOK。
そしてピクセルシェーダーのメイン関数を以下のようにする。
// main関数
PS_OUTPUT main(PS_INPUT PSInput)
{
PS_OUTPUT PSOutput;
float4 TextureDiffuseColor;
float3 Normal;
float DiffuseAngleGen;
float3 TotalDiffuse;
float3 TotalSpecular;
float3 SpecularColor;
// 法線の準備
Normal = normalize(PSInput.VNormal);
// ディフューズカラーの蓄積値を初期化
TotalDiffuse = float3(0.0f, 0.0f, 0.0f);
// スペキュラーの蓄積値を初期化
TotalSpecular = float3(0.0f, 0.0f, 0.0f);
// ディレクショナルライトの処理
CalcDirectionalLight(PSInput, 0, Normal, TotalDiffuse, TotalSpecular);
// ポイントライトの処理
CalcPointLight(PSInput, 1, Normal, TotalDiffuse, TotalSpecular);
// 出力カラー計算 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 開始 )
// ライトディフューズカラー蓄積値 + ( マテリアルのアンビエントカラーとグローバルアンビエントカラーを乗算したものとマテリアルエミッシブカラーを加算したもの )
//TotalDiffuse += g_Common.Material.Ambient_Emissive.xyz;
// SpecularColor = ライトのスペキュラカラー蓄積値 * マテリアルのスペキュラカラー
SpecularColor = TotalSpecular * g_Common.Material.Specular.xyz;
// 出力カラー = TotalDiffuse * テクスチャカラー + SpecularColor
TextureDiffuseColor = g_DiffuseMapTexture.Sample(g_DiffuseMapSampler, PSInput.TexCoords0);
PSOutput.Color0.rgb = TextureDiffuseColor.rgb * TotalDiffuse + SpecularColor;
// アルファ値 = テクスチャアルファ * マテリアルのディフューズアルファ * 不透明度
PSOutput.Color0.a = TextureDiffuseColor.a * g_Common.Material.Diffuse.a * g_Base.FactorColor.a;
// 出力カラー計算 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++( 終了 )
// 出力パラメータを返す
return PSOutput;
}
ディレクショナルライトおよびポイントライトの処理を行う関数を書くのだが、これらの関数にて渡すライトの番号 LightNo は基本ディレクショナルライトが0、ポイントライトが1となる。
厳密にはディレクショナルライト→(スポットライト)→ポイントライトの順の番号になる。同じライト同士なら作成順に番号が振られるが違うライトだと先ほどの順番が優先されるので注意。

コメント