ライトの種類
- ディレクショナルライト
- ポイントライト
- スポットライト
基本的なライトはこの3つです。
反射:Phong反射モデル
ライトは、物体に光が反射したり影を作ることで初めて意味を持ちます。
今回はPhongの反射モデルについて学習します。
◇Lambert拡散反射光
Lambert拡散反射モデルは拡散反射を計算するためのモデル。光が強く当たっている部分は明るく、あまり当たっていないサーフェースは、暗くなっている。
■法線
Lambert拡散反射光では、サーフェースに当たっている光の強さを計算に法線というデータを使用する。
法線とライトの方向の内積を計算することでライトの強さを計算する。
反射光を求める際には、内積の値が-1で帰ってくるものが一番明るくなります。
- ライトの方向と、サーフェースの法線とで内積の計算
- 1で求めた結果に-1を乗算しライトの強さを求める。
- 2.で求めたライトの強さを使ってライティングを行う。
この手順でランバート拡散反射光の値を求めていきます。
ランバート拡散反射モデルの実装
C++側での実装は省き、HLSLでの実装方法について書いていきます。
◇ディレクショナルライトのデータを受け取る定数バッファーを用意する。
// step-1 ディレクションライト用の構造体を定義する
struct DirectionalLight
{
Vector3 LightDirection;// ライトの向き
//HLSL側の定数バッファーであるfloat3型の変数は、16の倍数のアドレスに配置されるためバインディングを埋めておく
float pad;
Vector3 LightColor; // ライトの色
};
C++側の構造体にはpadという、無駄なデータが入っている。
実はこのデータはパディングという明示的にデータの隙間を表すためのデータ。
HLSLにおいて、float3などのベクトルデータなど定数バッファー中のデータは16の倍数のアドレスに配置される。
定数バッファーのルールとして、1レジスタ=16バイト。変数はそのレジスタの中に順番に配置されます。ただし、16 バイトをまたぐときは新しいレジスタに移るというルールがある。
GPUはfloat4単位で読み込み法が早い。途中でデータがずれていると効率が悪くなるため、見えない隙間をいれて16バイトごとに揃えることで読み込み速度を上げる。
◇頂点の法線をピクセルシェーダーに渡す
psIn.normal = mul(mWorld, vsIn.normal);
渡す際には、モデル回転に合わせて回す必要があるため、モデルのワールド行列を利用して回転させる。
◇ピクセルシェーダーでの処理
// step-7 ピクセルの法線とライトの方向の内積を計算する
float t = mul(psIn.normal, lightDirection);
t *= -1;
// step-8 内積の結果が0以下なら0にする
if(t<=0)
{
t = 0;
}
// step-9 ピクセルが受けているライトの光を求める
float3 DiffuseLight = lightColor * t;
ここで、内積の計算を行い、ライトのカラーと内積の結果をもとめることでピクセルが受けるライトの影響を求めることが出来る。
これで拡散反射光のライトのシェーディングができるようになった。
Phong鏡面反射光
引用:https://nn-hokuson.hatenablog.com/entry/2016/11/04/104242
Phong鏡面反射の強さは、「反射した光がどれだけ目に飛び込んでくるか」を計算することで求められる。
鏡面反射の強さを求めるためには下記処理を行う。
- ライトがサーフェースに入射して反射したベクトルを求める。
- 光が入射したサーフェースから視点に向かって伸びるベクトルを求める
- 1.2で求めたベクトルの内積を使って鏡面反射の強さを求める。
- 3で求めた鏡面反射の強さを絞って、最終的な鏡面反射の強さを求める。
■ ライトがサーフェースに入射して反射したベクトルを求める
反射ベクトルを計算するためには、入射したサーフェイスの法線を使用する。
ライトのベクトルをL、法線をNとしたとき、反射ベクトルをRは次の計算で求める。
$$
R = 2*(-N・L)×N
$$
この式で反射ベクトルを求められるらしい。今回は、reflect()関数を使用して反射ベクトルを求める。
■光が入射したサーフェースから視点に向かって伸びるベクトルを求める
視点に向かって伸びるベクトルを求めるには、サーフェースのワールド座標と、視点の座標を使って計算することで求められる。この際、計算結果は正規化させることを忘れずに。
■1と2で求めたベクトルの内積を使って鏡面反射の強さを求める
この2つのベクトルが近ければ近いほど目にたくさんの光が飛び込んでくることになる。
ここで内積を使って出た値が鏡面反射の強さとなる。
また、鏡面反射の強度を絞りを入れることで、ハイライトの大きさを調整できる。
鏡面反射を実装
C++での実装内容は、定数バッファーに視点の位置を設定するなどの対応のため省きます。
◇ HLSL側での対応
// ディレクションライト用のデータを受け取るための定数バッファーを用意する
cbuffer DirectionLightCb : register(b1)
{
float3 ligDirection; // ライトの方向
float3 ligColor; // ライトのカラー
// step-3 視点のデータにアクセスするための変数を定数バッファーに追加する
float3 eyePos; // 視点の位置
};
C++側で設定した視点データにアクセスするために変数を定数バッファーに追加しました。
次は、反射ベクトルを求めます。
// step-4 反射ベクトルを求める
float3 reflectVec = reflect(ligDirection, psIn.normal);
そして、視点に延びるベクトルを求めます。
// step-5 光が当たったサーフェイスから視点に伸びるベクトルを求める
float3 viewVec = normalize(eyePos - psIn.normal);
反射ベクトルと視点ベクトルが出たので鏡面反射を求めます。
// step-6 鏡面反射の強さを求める
t = dot(reflectVec, viewVec);
if(t<0.0f)
{
t = 0.0f;
}
// step-7 鏡面反射の強さを絞る
t = pow(t, 5.0f);
これで、鏡面反射光をカラーに出すことが出来ました。
環境光
この章の環境光はかなりシンプルな実装のため記録はしません。