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

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

 

今回はライティングに挑戦してみます。

光の当たり具合によって濃淡を付ける事でよりリアルな表現になるので重要な要素ですね。

 

DirectX 9の時はライティングに関するメソッドがありましたが DirectX 11にはありません。シェーダーの中で光の当たり方を計算させる必要があります。

 

ここでは一番シンプルな平行光源(ディレクショナルライト)という手法で光を当ててみます。平行光源とは、太陽光のような一定の方向から当たる光を表現するものになります。


頂点データに法線ベクトルを持たせる

法線ベクトルとは、頂点がどちらの方向を向いているかを示すベクトルデータです。

この頂点の向き(面の向き)と光の方向を元に光の当たり具合が計算されます。

struct Vertex {
        float pos[ 3 ];
        float nor[ 3 ];
};

Vertex g_VertexList[] {
        { { -0.5f,  0.5f, -0.5f }, {  0.0f,  0.0f, -1.0f } },
        { {  0.5f,  0.5f, -0.5f }, {  0.0f,  0.0f, -1.0f } },
        { { -0.5f, -0.5f, -0.5f }, {  0.0f,  0.0f, -1.0f } },
        { {  0.5f, -0.5f, -0.5f }, {  0.0f,  0.0f, -1.0f } },

        { { -0.5f,  0.5f,  0.5f }, {  0.0f,  0.0f,  1.0f } },
        { { -0.5f, -0.5f,  0.5f }, {  0.0f,  0.0f,  1.0f } },
        { {  0.5f,  0.5f,  0.5f }, {  0.0f,  0.0f,  1.0f } },
        { {  0.5f, -0.5f,  0.5f }, {  0.0f,  0.0f,  1.0f } },

        { { -0.5f,  0.5f,  0.5f }, { -1.0f,  0.0f,  0.0f } },
        { { -0.5f,  0.5f, -0.5f }, { -1.0f,  0.0f,  0.0f } },
        { { -0.5f, -0.5f,  0.5f }, { -1.0f,  0.0f,  0.0f } },
        { { -0.5f, -0.5f, -0.5f }, { -1.0f,  0.0f,  0.0f } },

        { {  0.5f,  0.5f,  0.5f }, {  1.0f,  0.0f,  0.0f } },
        { {  0.5f, -0.5f,  0.5f }, {  1.0f,  0.0f,  0.0f } },
        { {  0.5f,  0.5f, -0.5f }, {  1.0f,  0.0f,  0.0f } },
        { {  0.5f, -0.5f, -0.5f }, {  1.0f,  0.0f,  0.0f } },

        { { -0.5f,  0.5f,  0.5f }, {  0.0f,  1.0f,  0.0f } },
        { {  0.5f,  0.5f,  0.5f }, {  0.0f,  1.0f,  0.0f } },
        { { -0.5f,  0.5f, -0.5f }, {  0.0f,  1.0f,  0.0f } },
        { {  0.5f,  0.5f, -0.5f }, {  0.0f,  1.0f,  0.0f } },

        { { -0.5f, -0.5f,  0.5f }, {  0.0f, -1.0f,  0.0f } },
        { { -0.5f, -0.5f, -0.5f }, {  0.0f, -1.0f,  0.0f } },
        { {  0.5f, -0.5f,  0.5f }, {  0.0f, -1.0f,  0.0f } },
        { {  0.5f, -0.5f, -0.5f }, {  0.0f, -1.0f,  0.0f } },
};

WORD g_IndexList[] {
         0,  1,  2,     3,  2,  1,
         4,  5,  6,     7,  6,  5,
         8,  9, 10,    11, 10,  9,
        12, 13, 14,    15, 14, 13,
        16, 17, 18,    19, 18, 17,
        20, 21, 22,    23, 22, 21, 
};

D3D11_INPUT_ELEMENT_DESC g_VertexDesc[] {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,                            0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};

頂点データは座標 x,y,z と法線ベクトル x,y.z を持つ構造にしています。

法線ベクトルの場合、D3D11_INPUT_ELEMENT_DESCのセマンティクスは "NORMAL" を指定します。


定数バッファに光の方向を追加する

どの方向から光が当たるかという情報をシェーダーへ渡す為、定数バッファへ平行光源のベクトルを追加します。

 

定数バッファへ引き渡すための構造体を以下のように定義してみました。

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

lightというのがそれです。

 

※必要なのはx,y,zなのでXMFLOAT3でもいいのかなぁと思ったけど実行するとUpdateSubresourceで例外が出てしまう

※たぶんパッキングサイズなどが影響してるとおもわれるのでXMFLOAT4で定義している

 参考:定数変数のパッキング規則

 

定数バッファへの値セットは以下のような感じです。

    XMVECTOR light = XMVector3Normalize( XMVectorSet( 0.0f, 0.5f, -1.0f, 0.0f ) );

    ConstantBuffer cb;
    XMStoreFloat4x4( &cb.world,      XMMatrixTranspose( worldMatrix ) );
    XMStoreFloat4x4( &cb.view,       XMMatrixTranspose( viewMatrix ) );
    XMStoreFloat4x4( &cb.projection, XMMatrixTranspose( projMatrix ) );
    XMStoreFloat4(   &cb.light,      light );
    m_pImmediateContext->UpdateSubresource( m_pConstantBuffer, 0, NULL, &cb, 0, 0 );

lightが光源の方向を表すベクトルで、XMVector3Normalize関数で正規化しています。

光の当たり方の計算に使用するベクトルは正規化されている必要がある為です。


頂点シェーダーでライティングを計算する

頂点シェーダーは以下のようになっています。

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

struct VS_OUT
{
    float4 pos : SV_POSITION;
    float4 col : COLOR0;
};

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

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

    output.pos = mul(input.pos,  World);
    output.pos = mul(output.pos, View);
    output.pos = mul(output.pos, Projection);
        
    nor        = mul(input.nor, World).xyz;
    nor        = normalize(nor);
        
    col        = saturate(dot(nor, (float3)Light));
    col        = col * 0.5f + 0.5f;
        
    output.col = float4(col, col, col, 1.0f);
    return output;
}

Lightベクトルはワールド座標系の値という想定です。

法線ベクトルはローカル座標なのでワールド変換行列をmulしてワールド座標系にします。

光の当たり方の計算に使用するベクトルは正規化されている必要があるのでnormalizeします。

(正規化する時 float4 だと都合が悪いので xyz の float3 の型にしています)

 

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

 

 

Lightベクトルと法線ベクトルの内積 (dot) を計算すると光の当たり具合が -1~1 の範囲で求まります。

(マイナスの時は裏側、0の時は光に対して垂直という感じ)

ここでは、光が当たらない面も少し明るくする為、変化量を0.5にして0.5をプラスしています。

 

 

 

 

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

 

ソースコードはこちら