~プログラミング~ DirectX 11で鏡面反射 (Specular reflection)

C++言語でDirectX 11を使ったアプリケーションを開発してみよう。

今回はライティングの手法の一つ鏡面反射についてまとめてみようと思います。

 

金属などツルツルした物体に光が当たると鏡のように光を反射します。

正確に表現しようとすると電灯など光源の形が写り込む感じになりますが、そこまでやろうとすると計算量が膨大になってしまいます。そこで光源を点と考えハイライトを付ける事で似たような表現を再現します。

 

物体に光が当たると光が反射します。反射する方向は光の当たった面に対して反対方向になります。

この反射した光がどのくらいカメラに向かっているかを計算してハイライトを加えていきます。

鏡面反射 (Specular reflection)

鏡面反射はツルツルした表面で鏡のように反射する光の反射を表現します。

物体に当たった光は反射して反対方向に進みます。

完全な鏡では正反射方向のみですが、ツルツルぐあいによって正反射を中心にした範囲に広がっていきます。

 

見ている人の位置が正反射方向にある場合光がよく見え、それ以外の場合光はあまり見えません。つまり鏡面反射の計算にはカメラの座標が必要になります。

反射ベクトルを使った鏡面反射

まず正反射ベクトルを計算します。

正規化した正反射ベクトルと正規化視線ベクトルが作る角度θによって求める事ができます。

右の図を見ると正反射ベクトルは光源ベクトルの逆向き-LDの2倍足す事で求められそうです。

 

は法線ベクトルDの距離を掛けることで求まります。

Dの距離は法線ベクトルと光源ベクトル内積(cosθ)となるので D = N *(N・L)で計算できます。

 

 

以上から正反射ベクトルは R = 2 * N *(N・L)- L で求める事ができます。

 

最終的な計算式は以下のようになります。

 

 =((R・V)^ α)* KS * IS

 

正規化正反射ベクトルと正規化視線ベクトルの内積を取り、更に光沢度という係数αべき乗します。この係数を変化させる事で物体の質感を調整する事が出来るようになっています。

最後に面の色Sと入射光の輝度Sを掛け合わせています。

 

※内積の結果がマイナスの場合は鏡面反射は起こらない状態。マイナスのままではなく0にして計算する必要がある

ピクセルシェーダー

上記の計算をHLSLで書くとこんな感じになります。

struct PS_IN
{
    float4 pos  : SV_POSITION;
    float4 posw : POSITION0;    //ワールド座標系の座標
    float4 norw : NORMAL0;      //ワールド座標系の法線
};

cbuffer ConstantBuffer
{
    float4 eyePos;            //視点座標
    float4 pntlightPos;       //点光源座標
    float4 pntlightCol;       //点光源の色
    float4 materialSpecular;  //物体の色(r,g,b,光沢度係数)
}

float4 ps_main( PS_IN input ) : SV_Target
{
    float3 l;
    float3 n;
    float3 r;
    float3 v;
    float  i;

    l = normalize(pntlightPos.xyz - input.posw.xyz);
    n = normalize(input.norw.xyz);
    r = 2.0 * n * dot(n, l) - l;
    v = normalize(eyePos.xyz - input.posw.xyz);
    i = pow(saturate(dot(r, v)), materialSpecular.w);

    return float4(i * materialSpecular.xyz * pntlightCol.xyz, 1.0);
}

ハーフベクトルを使った鏡面反射

反射ベクトルを求める計算式は少し複雑です。これよりも計算コストを抑えたハーフベクトルというものを使う方法もあります。

ハーフベクトルとは、光源ベクトルと視線ベクトルのちょうど間のベクトルです。このハーフベクトルと法線ベクトルの作る角度θを用いる方法です。

 

ハーフベクトルは H=L+V で求める事が出来ます。

※LとVは正規化ベクトルである事

 

 

最終的な計算式は以下のようになります。

 

I =((H・N)^ α)* S * IS

 

正規化ハーフベクトルと正規化法線ベクトルの内積を取り、あとは反射ベクトルの時と同じです。

光沢度係数αべき乗し、面の色Sと入射光の輝度Sを掛け合わせています。

ピクセルシェーダー

上記の計算をHLSLで書くとこんな感じになります。

struct PS_IN
{
    float4 pos  : SV_POSITION;
    float4 posw : POSITION0;    //ワールド座標系の座標
    float4 norw : NORMAL0;      //ワールド座標系の法線
};

cbuffer ConstantBuffer
{
    float4 eyePos;            //視点座標
    float4 pntlightPos;       //点光源座標
    float4 pntlightCol;       //点光源の色
    float4 materialSpecular;  //物体の色(r,g,b,光沢度係数)
}

float4 ps_main( PS_IN input ) : SV_Target
{
    float3 l;
    float3 v;
    float3 h;
    float3 n;
    float  i;

    l = normalize(pntlightPos.xyz - input.posw.xyz);
    v = normalize(eyePos.xyz - input.posw.xyz);
    h = normalize(l + v);
    n = normalize(input.norw.xyz);
    i = pow(saturate(dot(h, n)), materialSpecular.w);

    return float4(i * materialSpecular.xyz * pntlightCol.xyz, 1.0);
}