~プログラミング~ DirectX 11で点光源ライティング

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

 

前回は平行光源でのライティングを行いましたが、今回は点光源(ポイントライト)というものに挑戦してみます。平行光源は太陽光のような一定の方向から当たる光でした。対して点光源は電灯などの光を表現します。

 

平行光源との違いは、まず光源に座標があるという事です。平行光源の時と同様に面の向きと光の方向を元に当たり具合を計算しますが、頂点の位置によって光源との角度が変わります。もう一つの違いは、面と光源との距離によって明るさが変わる点です。この2点を考えながら点光源を実装してみます。


定数バッファ

定数バッファは以下のように定義しました。

struct ConstantBuffer {
    XMFLOAT4X4 world;
    XMFLOAT4X4 view;
    XMFLOAT4X4 projection;
    XMFLOAT4   light;
    XMFLOAT4   attenuation;
};

lightは、光の方向ではなく光源の座標を格納します。

attenuationは、距離によってどのように明るさを変化させるかを調整する為の減衰パラメータを格納します。詳細はピクセルシェーダーのところで解説します。


頂点シェーダー

平行光源の時は頂点シェーダーで明るさを計算していました。点光源では距離によって明るさが変わる為よりリアルな表現になるようにピクセルシェーダーで明るさを計算させるようにしています。

 

この為、頂点シェーダーは主にデータを引き渡すだけになっています。

struct VS_IN
{
    float4 pos : POSITION0;
    float4 nor : NORMAL0;
};

struct VS_OUT
{
    float4 pos  : SV_POSITION;
    float4 posw : POSITION0;
    float4 norw : NORMAL0;
};

cbuffer ConstantBuffer
{
    float4x4 World;         //ワールド変換行列
    float4x4 View;          //ビュー変換行列
    float4x4 Projection;    //透視射影変換行列
    float4   Light;
    float4   Attenuation;
}

VS_OUT vs_main( VS_IN input )
{
    VS_OUT output;
    float3 nor;
    float  col;

    output.posw = mul(input.pos,   World);
    output.pos  = mul(output.posw, View);
    output.pos  = mul(output.pos,  Projection);
        
    output.norw = mul(input.nor,   World);

    return output;
}

頂点シェーダーから出力されるVS_OUT構造体は、プロジェクション座標系へ変換された座標posの他に、ワールド座標系の座標poswと、同じくワールド座標系に変換された法線norwを出力しています。


ピクセルシェーダー

ピクセルシェーダーで明るさを計算します。 

こちらでも定数バッファを参照する必要がある為 cbuffer が定義されています。

 

光源との角度による明るさと距離による明るさの減衰を掛け合わせて最終的な色を計算しています。

struct PS_IN
{
    float4 pos  : SV_POSITION;
    float4 posw : POSITION0;
    float4 norw : NORMAL0;
};

cbuffer ConstantBuffer
{
    float4x4 World;         //ワールド変換行列
    float4x4 View;          //ビュー変換行列
    float4x4 Projection;    //透視射影変換行列
    float4   Light;         //光源座標
    float4   Attenuation;   //光源減衰パラメータ
}

float4 ps_main( PS_IN input ) : SV_Target
{
    float3 dir;
    float  len;
    float  colD;
    float  colA;
    float  col;
        
    //点光源の方向
    dir = Light.xyz - input.posw.xyz;

    //点光源の距離
    len = length(dir);

    //点光源の方向をnormalize
    dir = dir / len;

    //拡散
    colD = saturate(dot(normalize(input.norw.xyz), dir));
    //減衰
    colA = saturate(1.0f / (Attenuation.x + Attenuation.y * len + Attenuation.z * len * len));

    col = colD * colA;
    return float4(col, col, col, 1.0f);
}

光源との角度による明るさ計算

光源の方向を毎回計算して求めます。

座標 input.posw と光源の位置 Light から方向 dir を計算します。(26行目)

dir をベクトルの長さで割って正規化します。normalize関数と同じ意味です。(32行目)

dir と法線ベクトル input.norw の内積を計算して明るさを求めます。(35行目)

 

※詳細こちら参照…~プログラミング~ DirectX 11で拡散反射 (Diffuse reflection)

光源との距離による明るさの減衰を計算

物理現象を正しく再現しようとすると明るさの減衰は距離の2乗に反比例するようですが、その通りに計算すると減衰が早すぎて扱いにくいために、それに近い曲線を描くような別の計算式を利用する事が多いようです。

 

今回はDirectX 9の時にも利用されていた以下の計算式を使ってみます。

 

明るさ = 1 / ( att0 + att1 * 距離 + att2 * 距離の2乗 )

 

att0 ・・・ 一定減衰係数

att1 ・・・ 線形減衰係数

att2 ・・・ 2次減衰係数

 

att0 ~ att2 は減衰を調整するパラメータで定数バッファ Attenuationx,y,z 成分を使って引き渡す仕組みです。(37行目)


ピクセルシェーダーにも定数バッファをセット

今回からピクセルシェーダーも定数バッファを利用するようになりました。

ID3D11DeviceContext::PSSetConstantBuffersメソッドで忘れずにセットしましょう。

        m_pImmediateContext->VSSetConstantBuffers( 0, 1, &m_pConstantBuffer );
        m_pImmediateContext->VSSetShader( m_pVertexShader, NULL, 0 );
        m_pImmediateContext->PSSetConstantBuffers( 0, 1, &m_pConstantBuffer );
        m_pImmediateContext->PSSetShader( m_pPixelShader, NULL, 0 );

 

 

うまく出来たらこんな感じに表示されると思います。

(立方体だけでなく床ポリゴンも表示させています)

 

ソースコードはこちら