ライトプリパスレンダリング Part1

投稿日:

ライトプリパスレンダリングの基本的な処理を作成します。ジオメトリバッファの作成、光源計算、最終レンダリングの3つのパスでレンダリングします。それとこれまでに作成した、輪郭線とステンシルシャドウも組み込んでみます。

左上:法線 右上:Specular 左下:最終結果  右下:Diffuse
左上:法線 右上:Specular
左下:最終結果 右下:Diffuse&Shadow

レンダーターゲットの作成やMRT設定、シェーダからのアクセスなどの詳細は
ライトプリパスレンダリング Part0
を見てください。


パス1:ジオメトリバッファ作成

最初のパスでジオメトリ情報(法線、デプス、スペキュラ強度)をレンダリングします。この情報を使用し、光源計算や影を落とします。3次元の位置情報も必要ですが、デプス値から計算できるため省略(簡単な計算で求まる)。法線とスペキュラ強度はマルチレンダーターゲットにして同時に生成。

MRT0:法線バッファ DXGI_FORMAT_R11G11B10_FLOAT

ポリゴンの法線ベクトルを書き込むバッファ。精度とサイズを考えてフォーマットは「R11G11B10_FLOAT」。ただし負の値は表現できないため、書込時に-1~+1を0~1に正規化する。光源計算の簡略化のため法線ベクトルをView座標系に変換(View座標系では、視点位置(0,0,0)、ある点への視線方向=点の位置)。

MRT1:スペキュラ強度 DXGI_FORMAT_R8_UNORM

鏡面反射(フォン)での反射の鋭さを表すパラメータ。0~最大値(255)を0~1に変換して出力。

デプスバッファ DXGI_FORMAT_D24_UNORM_S8_UINT

デプステスト用のバッファですが、ピクセルの3次元位置を計算するためにも使用します。精度が要求されるため、DXGI_FORMAT_D32_FLOATや線形Zの利用も考えておく(現在D24、1/Zの非線形)。
レンダーターゲット作成時の注意点、テクスチャはDXGI_FORMAT_R24G8_TYPELESSで作成、RenderTargetViewはDXGI_FORMAT_D24_UNORM_S8_UINTで作成。

ピクセルシェーダ
//-------------
//MRT0に法線、MRT1にスペキュラ強度を出力。
struct PS_OUTPUT_GEOM
{
    float4 Nor : SV_Target0;
    float4 SpcPow : SV_Target1;
};

// ジオメトリバッファ生成
PS_OUTPUT_GEOM psLPPGeometry( PS_INPUT input )
{
    PS_OUTPUT_GEOM o = (PS_OUTPUT_GEOM)0;

    // View座標系に変換
    float3 nor_view = normalize( mul(input.Nor, (float3x3)Scene.mtxView) );
	
    //法線 -1~+1を0~1に
    o.Nor = float4(0.5*(nor_view+1),0);

    // Specular Pow 0~255 ⇒ 0.0~1.0
    o.SpcPow.x = max(Material.Specular.w, 1)/255.0;// Specular Pow
    return o;
}

読取専用DepthStencilView

パス1で作成したデプス値を光源計算のシェーダなどで利用しますが、それと同時に同じデプスバッファをデプステストを行うためレンダーターゲットに割り当てます。しかし、シェーダからのアクセス(入力)、レンダーターゲット(出力)が同じバッファになるためDirectXの不正処理防止機能が働き、シェーダからのアクセス(シェーダリソースView)が無効化されます。GPU処理で入出力に同じバッファを指定した場合、画面が点滅など不定動作(並列処理やキャッシュの影響でメモリアクセス順序が保障されない)。
光源計算などパス1以降ではデプス値を更新しないため、DepthStencilViewを読取専用として作成することでこの問題を回避できます。
//読み取り専用DepthStencilViewの作成
ID3D11DepthStencilView* dsview_readonly;

D3D11_DEPTH_STENCIL_VIEW_DESC dsv_desc;
ZeroMemory( &dsv_desc, sizeof(dsv_desc) );
dsv_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;

// 読み取り専用フラグ
dsv_desc.Flags = D3D11_DSV_READ_ONLY_DEPTH | D3D11_DSV_READ_ONLY_STENCIL;

Device->CreateDepthStencilView( texture, &dsv_desc, &dsview_readonly );

パス2:光源計算(平行光源と環境光)

ジオメトリバッファを使い、全ピクセルに対して光源計算を行います(点光源など有限範囲の光源計算は次回予定)。法線ベクトル、平行光源の方向などはView座標系に変換、View座標系で計算を行うので、視点位置は原点(0,0,0)、視線方向=ピクセルの3次元位置。

MRT0:Diffuse DXGI_FORMAT_R16G16B16A16_FLOAT
MRT1:Specular DXGI_FORMAT_R16G16B16A16_FLOAT

光源計算結果を出力するバッファ。MRTでDiffuse、Specularを同時に書き込みます。精度に問題がないか速度重視ならDXGI_FORMAT_R11G11B10_FLOATの利用も考える。

ピクセルシェーダ
//----------------
struct PS_OUTPUT
{
    float4 diff : SV_Target0;
    float4 spec : SV_Target1;
};

//-------------------------
//ピクセルシェーダ
//画面全体のスプライト描画で全ピクセルに対して光源計算
PS_OUTPUT psMain( PS_INPUT input )
{
    PS_OUTPUT o = (PS_OUTPUT)0;
	
    // 位置(view座標系) = -視線方向
    float3 vpos;
    float dep = txDepth.Sample( samDepth, input.Tex );
    float2 spos =  input.NormPos;
    vpos.z = Scene.mtxProj._m32/(dep - Scene.mtxProj._m22);
    vpos.xy = (spos.xy*vpos.z)/float2(Scene.mtxProj._m00,Scene.mtxProj._m11);
	
    // 法線(view座標系) 0~1⇒-1~+1
    float3 nor = 2.0*txNormal.Sample( samLinear, input.Tex) - 1;
	
    // 平行光源の方向(view座標系)
    float3 dir = mul( Light.vDirLight[0].xyz, (float3x3)Scene.mtxView );

    // 拡散
    float d = saturate( dot(nor,dir) );
	
    // 鏡面
    float3 h = normalize( dir - normalize(vpos) );//ハーフベクトル
    float spec_pow = 255*txSpecPow.Sample( samSpecPow, input.Tex ).x;
    float3 s = pow( saturate( dot(nor,h) ), spec_pow );

    // 半球ライト
    float hemi = dot(nor, mul(Light.vHemiDir, (float3x3)Scene.mtxView));
    float3 hemi_rgb = 0.5*( (1+hemi)*Light.colHemiSky + (1-hemi)*Light.colHemiGrd );
	
    o.diff = float4( (Light.colDirLight[0]*d + hemi_rgb), 1);
    o.spec = float4( Light.colDirLight[0]*s, 1);

    return o;
}
パス2-2:ステンシルシャドウ

ジオメトリバッファ生成で作成したデプス値を使い、ステンシルシャドウを行います。特別な処理はありません。シャドウボリュームをステンシルテストを有効にしてレンダリング、ステンシル値!=0の部分が影になる。パス3の最終レンダリングでステンシル値を読み取って影の描画を行います。


パス3:最終レンダリング

パス2の光源計算とステンシルを使って、最終結果をレンダリングします。レンダリング方法は、背景とキャラクターで変えています(ライトプリパスレンダリングの利点、柔軟性が高い)。

光源計算結果とステンシル値の取得
3次元のポリゴン描画時に対応する光源計算結果のテクスチャ座標を求める必要があります。
//-------------
struct PS_INPUT
{
    float4 Pos : SV_POSITION;
};

float4 psLPPMain( PS_INPUT input ) : SV_Target0
{
  // 光源計算結果取得のためのテクスチャ座標

  // input.Pos 左上0,0 ピクセル単位の値
  // 0~1のテクスチャ座標に変換
  float2 viewport = (レンダーターゲットの幅,高さ)
  float2 texcrd = input.Pos.xy/viewport.xy;

}
キャラクターのレンダリング

光源計算結果は無視して、平行光源とトゥーンテクスチャを使ったシェーディング。ステンシル値!=0の影の部分は、色を暗くせずトゥーンテクスチャの暗い部分を参照することで自然な影を実現。

背景のレンダリング

光源計算結果を使ったレンダリング。光源計算がないため、単純なシェーダにできます。マテリアルに影の情報を設定すれば、ステンシルシャドウの影色や濃さ、影の有無が制御可能(今回はシェーダで固定値)。


サンプルプログラム

“ライトプリパスPart1” をダウンロード dx11LightPrePass1.zip – 705 回のダウンロード – 82 KB

PMXモデル対応

背景を描画するために必要最小限の機能のみ対応。


リンクなど

PMX仕様

PMXエディタ 0.2.1.8 / とある工房

スクリーンショットのモデルとモーション

キャラクターモデル

ままま式GUMIβ版 / 3xma

モーション

【第9回MMD杯Ex】星間飛行GUMI/ニコニコ動画

背景モデルデータ

ぴくちぃ式 背景パック 1.2 M5ミクちゃん部屋 / MshZ

コメントを残す

メールアドレスが公開されることはありません。

認証:数字を入力してください(必須) * Time limit is exhausted. Please reload CAPTCHA.