光源計算&定数バッファ

光源計算を行います。シェーダに光源情報を渡すために定数バッファを使用しますが、ここで定数バッファの構造をある程度決めておきたいと思います。

光源計算 半球+平行+点光源
光源計算 半球+平行+点光源


定数バッファの構造

DirectX11(シェーダーモデル4)では、定数バッファ(0~15までのスロット、各スロットに4096ベクトルまで)をシェーダに渡すことができます。内容の更新は、スロット単位で行うため
定数バッファの1スロット = C++構造体
という対応付けをして、更新頻度でスロット(構造体)を分類します。
(※DirectX11.1ではスロットの部分変更可能)

構造体と定数バッファスロットの対応

C++でシェーダに渡すための構造体を定義、その構造体サイズのバッファ(ID3D11Buffer)を作成、UpdateSubresourceで内容を更新、VSSetConstantBuffersなどでシェーダに渡します。シェーダ側は、C++構造体と同じ構造体を定義し指定されたスロット番号で値を受け取ります。
光源情報を例にすると

C++での構造体定義

// 光源パラメータ構造体(C++)
struct SCBLight
{
	// 平行光源
	static const zgU32 DIRLIGHT_NUM = 4;
	XMVECTOR vDirLight[DIRLIGHT_NUM];
	XMVECTOR colDirLight[DIRLIGHT_NUM];

	//半球ライティング
	XMVECTOR vHemiDir;
	XMVECTOR colHemiSky;
	XMVECTOR colHemiGrd;

	//点光源
	static const zgU32 PLIGHT_NUM = 4;
	XMVECTOR posPLight[PLIGHT_NUM];
	XMVECTOR colPLight[PLIGHT_NUM];
	XMVECTOR prmPLight[PLIGHT_NUM];
};

バッファの作成と内容更新

	D3D11_BUFFER_DESC bd;
	ZeroMemory( &bd, sizeof(bd) );
	// 定数バッファ作成
	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(SCBLight);
	bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	bd.CPUAccessFlags = 0;
	hr = renctx.pDev->CreateBuffer( &bd, NULL, GSET_DXPTR(renctx.pCBLight) );

	// バッファの内容更新
	SCBLight& scblight = renctx.scbLight;
	ZeroMemory( &scblight, sizeof(scblight) );
	scblight.vDirLight[0] = XMVectorSet(0.577f,0.577f,-0.577f, 0);
	scblight.colDirLight[0] = XMVectorSet(0.7f,0.7f,0.7f,0);
	scblight.vHemiDir = XMVectorSet(0,1,0,0);
	scblight.colHemiSky = XMVectorSet(0.2f,0.2f,0.3f,0.0f);
	scblight.colHemiGrd = XMVectorSet(0.3f,0.3f,0.2f,0.0f);

	float plmin = 5.0f;//点光源の影響が1になる距離
	float plmax = 250.0;//点光源の影響が0になる距離
	scblight.posPLight[0] = XMVectorSet(100,50,0,0);
	scblight.colPLight[0] = XMVectorSet(0.0f,0.8f,1,0);
	scblight.prmPLight[0] = XMVectorSet(plmin, plmax, 1/(plmin-plmax), -plmax/(plmin-plmax));
	scblight.posPLight[1] = XMVectorSet(-100,50,0,0);
	scblight.colPLight[1] = XMVectorSet(1.0f,0.8f,0,0);
	scblight.prmPLight[1] = XMVectorSet(plmin, plmax, 1/(plmin-plmax), -plmax/(plmin-plmax));
	renctx.pImCtx->UpdateSubresource( renctx.pCBLight.get(), 0, NULL, &scblight, 0, 0 );
	
	//作成、更新したバッファをVSSetConstantBuffersやPSSetConstantBuffersで
	//スロット番号を指定してシェーダへ

シェーダでの構造体定義とスロット番号指定

//構造体定義
struct SCBLight
{
    float4 vDirLight[4];
    float4 colDirLight[4];
	float4 vHemiDir;
	float4 colHemiSky;
	float4 colHemiGrd;
	float4 posPLight[4];
	float4 colPLight[4];
	float4 prmPLight[4];
};

//定数バッファ定義 register( b2 )の2がスロット番号
cbuffer CB2 : register( b2 )
{
	SCBLight Light;
};

//光源計算 定数バッファ使用 Light.でアクセス
float4 psMain( PS_INPUT input ) : SV_Target
{
	float3 nor = normalize(input.Nor);
	
	// 半球
	float hemi = dot(nor, Light.vHemiDir);
	float3 rgb = 0.5*( (1+hemi)*Light.colHemiSky + (1-hemi)*Light.colHemiGrd );
	
	//平行光源
	for(int i=0;i<4;++i){
		float l = saturate( dot(nor, Light.vDirLight[i].xyz) );
		rgb += l*Light.colDirLight[i];
	}
	
	//点光源
	for(int i=0;i<4;++i){
		float3 pdir = Light.posPLight[i].xyz - input.PosW.xyz;
		float r = length(pdir);
		pdir = normalize(pdir);
		rgb += Light.colPLight[i].xyz
				*saturate(Light.prmPLight[i].z*r + Light.prmPLight[i].w)
				*saturate(dot(pdir,nor));
	}
	
	return float4(rgb,1)*txDiffuse.Sample( samLinear, input.Tex)*vDiffuse;
}

定数バッファ(構造体)の分類(仮)

更新頻度や情報の種類で定数バッファ(構造体)を分類しておきます。プログラム作成時には、この分類に沿って定数バッファの作成、更新、シェーダへの受け渡しを行う予定です。

定数バッファ(構造体)の分類(仮)
固定パラメータ 変更されないパラメータ、デバッグ情報など
シーンパラメータ カメラ情報、透視変換行列、レンダリングターゲット情報など
光源パラメータ 光源計算の情報など
モデルパラメータ 3Dモデル全体に対する情報、キャラクターの透明度など
オブジェクトパラメータ 姿勢(ワールド)行列など
マテリアルパラメータ ディフューズ、スペキュラーなど
ローカルパラメータ シェーダ固有の情報

サンプルプログラム

ダウンロード
光源計算(半球+平行+点)

光源計算

定数バッファの分類を行うことが目的のため、光源の情報は仮です。よく使われそうな光源を追加してみました(半球ライティング、平行光源×4、点光源×4)。
半球ライティング、平行光源は一般的な計算方法ですが、点光源だけは少し計算方法を変えています。点光源の距離による減衰は距離の2乗の逆数ですが、それだと減衰が早すぎるので、物理法則を無視した直線的に減衰する計算式にしました。減衰して影響0になる距離をパラメータとするので設定が直感的にできます。
Rmin:減衰が始まる距離
Rmax:影響が0になる距離
R :点光源までの距離(Rmin<R<Rmax)
光源の影響 = R/(Rmin - Rmax) - Rmax/(Rmin - Rmax)
特に問題が起きなければこのまま使い続ける予定。

シェーダコードの#include対応

定数バッファようにC++とシェーダで構造体を定義しますが、シェーダ毎に定義してしまうと構造体やスロット番号の変更時にすべてのシェーダコードを書き換えることになるので、シェーダでの構造体と定数バッファ定義をヘッダファイルに分離してincludeするようにします。

シェーダコンパイルの変更

// シェーダのコンパイル
HRESULT CompileShaderFromFile( WCHAR* fname, LPCSTR entry_point, LPCSTR shd_model, ID3DBlob** pp_blob_out )
{
	HRESULT hr = S_OK;

	DWORD shd_flags = D3DCOMPILE_ENABLE_STRICTNESS;
#if defined( DEBUG ) || defined( _DEBUG )
	shd_flags |= D3DCOMPILE_DEBUG;
#endif

	ID3DBlob* err_blob;
	hr = D3DCompileFromFile( fname, NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE,
					entry_point, shd_model, 
					shd_flags, 0, pp_blob_out, &err_blob );
	if( FAILED(hr) ){
		if( err_blob != NULL ){
			OutputDebugStringA( (char*)err_blob->GetBufferPointer() );
		}
		if( err_blob ) err_blob->Release();
		return hr;
	}
	if( err_blob ){
		err_blob->Release();
	}
	return S_OK;
}

#includeを有効にするには、D3DCompileFromFileにincludeの処理を行うID3DIncludeインターフェイスを指定します。通常のinclude処理であれば用意されたマクロD3D_COMPILE_STANDARD_FILE_INCLUDEを指定するだけでOK。

シェーダコードでのinclude
C++と同じように#includeするだけ。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

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