CubeMapGS サンプル
CubeMapGS サンプルは、Direct3D 10 の 2 つの新機能であるレンダー ターゲット配列とジオメトリ シェーダーを使用して、1 回の DrawIndexed() の呼び出しでキューブ テクスチャーのレンダー ターゲットをレンダリングする方法を示しています。レンダー ターゲット配列を使用すると、複数のレンダー ターゲット テクスチャーと深度ステンシル テクスチャーを同時にアクティブにすることができます。キューブ テクスチャーの各面につき 1 つ、合計で 6 つのレンダー ターゲットの配列を使用する場合、キューブの 6 つの面すべてをまとめてレンダリングできます。ジオメトリ シェーダーが三角形を出力する場合、配列内のどのレンダー ターゲットで三角形をラスター化するかを制御できます。ジオメトリ シェーダーに渡される三角形ごとに 6 つの三角形がジオメトリ シェーダーで生成され、各レンダー ターゲットにつき 1 つの三角形がピクセル シェーダーに出力されます。
Path
ソース : | (SDK ルート)\Samples\C++\Direct3D10\CubeMapGS |
実行可能ファイル : | (SDK ルート)\Samples\C++\Direct3D10\Bin\x86 or x64\CubeMapGS.exe |
サンプルの概要
環境マッピングは、3D グラフィックで広く使用され、サポートも充実している手法です。従来は、動的キューブ環境マップを作成する場合、キューブ テクスチャーの各面のサーフェスを取得し、それらのサーフェスをレンダー ターゲットとして設定することによって、キューブの各面ごとに 1 回ずつシーンをレンダリングしていました。次に、このキューブ テクスチャーを使用して、環境マップが適用されたオブジェクトをレンダリングできます。この方法は、正常に機能しますが、レンダリング パスの数が 1 回から 7 回に増加し、アプリケーションのフレーム レートが大幅に減少します。Direct3D 10 では、アプリケーションでジオメトリ シェーダーとレンダー ターゲット配列を使用して、この問題を軽減できます。
サンプルが動作するしくみ
Direct3D 10 のジオメトリ シェーダーは、レンダリングする各プリミティブに対して実行するコードです。ジオメトリ シェーダーの呼び出しごとに、ラスター化してピクセル シェーダーで処理するプリミティブ (複数可) を出力することができます。また出力しないように選択することもできます。また、ジオメトリ シェーダーでは、出力するプリミティブをレンダー ターゲット配列のどの要素スライスに出力するか制御できます。
Direct3D 10 のレンダー ターゲット配列は、アプリケーションが複数のレンダー ターゲットにプリミティブ レベルで同時にレンダリングできるようにするための機能です。アプリケーションでは、ジオメトリ シェーダーを使用して、プリミティブを出力し、プリミティブを受け取る配列内のレンダー ターゲットを選択します。このサンプルでは、キューブ テクスチャーの 6 つの面に対応する 6 つの要素を持つレンダー ターゲット配列を使用しています。次のコード例は、キューブ テクスチャーのレンダリングに使用するレンダー ターゲット ビューを作成します。
// Create the 6-face render target view
D3D10_RENDER_TARGET_VIEW_DESC DescRT;
DescRT.Format = dstex.Format;
DescRT.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2DARRAY;
DescRT.Texture2DArray.FirstArraySlice = 0;
DescRT.Texture2DArray.ArraySize = 6;
DescRT.Texture2DArray.MipSlice = 0;
pd3dDevice->CreateRenderTargetView( g_pEnvMap, &DescRT, &g_pEnvMapRTV );
DescRT.TextureCube.FirstArraySlice を 0、DescRT.TextureCube.ArraySize を 6 に設定することで、このレンダー ターゲット ビューが、キューブ テクスチャーの各面につき 1 つ、合計 6 つのレンダー ターゲットの配列であることを表します。このサンプルでは、キューブ マップへのレンダリング時に、テクスチャーの 6 つのすべての面を同時にレンダリングできるように、ID3D10Device::OMSetRenderTargets() を呼び出し、このレンダー ターゲット ビューをアクティブなビューとして設定します。
// Set the env map render target and depth stencil buffer
ID3D10RenderTargetView* aRTViews[ 1 ] = { g_apEnvMapOneRTV[view] };
pd3dDevice->OMSetRenderTargets( sizeof(aRTViews) / sizeof(aRTViews[0]),
aRTViews, g_pEnvMapOneDSV );
CubeMap のレンダリング
トップ レベルでは、レンダリングを Render() から開始し、Render() が RenderSceneIntoCubeMap()、RenderScene() を順に呼び出します。RenderScene() は、ビュー行列または射影行列を取得し、次に現在のレンダー ターゲットにシーンをレンダリングします。RenderSceneIntoCubeMap() は、キューブ テクスチャーへのシーンのレンダリングを処理します。次に、このテクスチャーは、環境マップされたオブジェクトをレンダリングするために RenderScene() で使用されます。
RenderSceneIntoCubeMap() では、最初に、キューブ テクスチャーの 6 つの面へのレンダリングのために、6 つのビュー行列を計算する必要があります。これらの行列は、カメラ位置の視点と、-X、+X、-Y、+Y、-Z および +Z 方向の視野方向が格納されます。ブール フラグ m_bUseRenderTargetArray は、キューブ マップのレンダリングに使用する手法を示します。false の場合、for ループがキューブ マップの面に対して繰り返し処理を行い、キューブ マップの各面に対して 1 回ずつ RenderScene() を呼び出してシーンをレンダリングします。ジオメトリ シェーダーはレンダリングには使用されません。この手法は本質的に、Direct3D 9 以前で使用されていた従来の方法です。m_bUseRenderTargetArray が true の場合、キューブ マップは RenderCubeMap エフェクトの手法でレンダリングされます。この手法では、ジオメトリ シェーダーを使用して、各プリミティブを 6 つのすべてのレンダー ターゲットに出力します。このため、RenderScene() を 1 回呼び出すだけで、キューブ マップの 6 つの面すべてを描画できます。
次に示すように、キューブ テクスチャーへのレンダリングに使用される頂点シェーダーは、VS_CubeMap です。このシェーダーは、頂点位置をオブジェクト空間からワールド空間にトランスフォームするという最小限の処理を行います。ワールド空間の位置は、ジオメトリ シェーダーで必要となります。
struct VS_OUTPUT_CUBEMAP
{
float4 Pos : SV_POSITION; // World position
float2 Tex : TEXCOORD0; // Texture coord
};
VS_OUTPUT_CUBEMAP VS_CubeMap( float4 Pos : POSITION, float3 Normal : NORMAL, float2 Tex : TEXCOORD )
{
VS_OUTPUT_CUBEMAP o = (VS_OUTPUT_CUBEMAP)0.0f;
// Compute world position
o.Pos = mul( Pos, mWorld );
// Propagate tex coord
o.Tex = Tex;
return o;
}
このサンプルの 2 つのジオメトリ シェーダーのうちの 1 つである GS_CubeMap を以下に示します。このシェーダーは、VS_CubeMap によって処理されたプリミティブにつき 1 回実行されます。このジオメトリ シェーダーが出力する頂点フォーマットは、GS_OUTPUT_CUBEMAP です。この構造体の RTIndex フィールドには、専用のセマンティクスSV_RenderTargetArrayIndex が格納されています。このセマンティクスにより、RTIndex フィールドで、プリミティブが出力されるレンダー ターゲットを制御できます。プリミティブの先頭頂点だけでレンダー ターゲット配列インデックスを指定できることに注意してください。同じプリミティブのそれ以外のすべての頂点では、RTIndex は無視され、先頭頂点の値が使用されます。たとえば、このジオメトリ シェーダーで、RTIndex の値が 2 の 3 つの頂点を構築して出力する場合、このプリミティブはレンダー ターゲット配列の要素 2 になります。
トップ レベルでは、シェーダーは、キューブの各面につき 1 回ずつ、合計で 6 回ループする for ループで構成されます。このループ内部では、別のループがキューブ マップごとに 3 回実行され、三角形プリミティブの 3 つの頂点が作成されて出力されます。RTIndex フィールドは、外側のループの制御変数 f に設定されます。これにより、外側のループの各反復処理で、配列内の個々のレンダー ターゲットにプリミティブが出力されます。頂点の出力前に実行する必要があるもう 1 つのタスクは、出力頂点構造体の Pos フィールドを計算することです。Pos のセマンティクスは、SV_POSITION です。これは、ラスタライザーが三角形を適切にラスター化するために必要な頂点の射影座標を表します。頂点シェーダーはワールド空間の位置を出力するため、ジオメトリ シェーダーはビュー行列と射影行列によって位置をトランスフォームする必要があります。ループ内で頂点のトランスフォームに使用されるビュー行列は、g_mViewCM[f] です。この行列の配列にはサンプルが格納されており、環境マップが適用されたオブジェクトから見たキューブ マップの 6 つの面をレンダリングするビュー行列が含まれています。したがって、各反復処理で、異なるビュー トランスフォーム行列を使用して別々のレンダー ターゲットに頂点が出力されます。これにより、DrawIndexed() を複数回呼び出すことなく、1 回のパスで、1 つの三角形が 6 つのレンダー ターゲット テクスチャーにレンダリングされます。
struct GS_OUTPUT_CUBEMAP
{
float4 Pos : SV_POSITION; // Projection coord
float2 Tex : TEXCOORD0; // Texture coord
uint RTIndex : SV_RenderTargetArrayIndex;
};
[maxvertexcount(18)]
void GS_CubeMap( triangle VS_OUTPUT_CUBEMAP In[3], inout TriangleStream<GS_OUTPUT_CUBEMAP> CubeMapStream )
{
for( int f = 0; f < 6; ++f )
{
// Compute screen coordinates
GS_OUTPUT_CUBEMAP Out;
Out.RTIndex = f;
for( int v = 0; v < 3; v++ )
{
Out.Pos = mul( In[v].Pos, g_mViewCM[f] );
Out.Pos = mul( Out.Pos, mProj );
Out.Tex = In[v].Tex;
CubeMapStream.Append( Out );
}
CubeMapStream.RestartStrip();
}
}
ピクセル シェーダー PS_CubeMap は、比較的単純です。このシェーダーは、ディフューズ テクスチャーをフェッチしてメッシュに適用します。このテクスチャーにはライティングが書き込まれるため、ピクセル シェーダーではライティングは実行されません。
反射オブジェクトのレンダリング
シーンの中央の反射オブジェクトのレンダリングには、3 つの手法が使用されます。3 つの手法はすべて、キューブマップからテクセルをフェッチします。これらは、1 回のパスでレンダリングされないキューブマップからフェッチする手順と同じです。つまり、これらの手法は、生成されるキューブマップを使用する方法と直行します。これら 3 つの手法は主な違いは、キューブマップのテクセルを使用する方法にあります。RenderEnvMappedScene は、近似フレネル反射関数を使用して、車の塗料の色をキューブマップからの反射とブレンドします。RenderEnvMappedScene_NoTexture は、塗装のマテリアルなしで同じ処理を行います。RenderEnvMappedGlass は、RenderEnvMappedScene_NoTexture に透明度を追加します。
高次の法線補間
従来の法線補間は線形でした。そのため、頂点シェーダーで計算された法線は、三角形の面に沿って線形補間されます。これにより、法線の方向がポリゴンの面に沿って急激に変化する場合は、車の反射はずれて表示されます。この問題を軽減するために、このサンプルでは 2 次法線補間を使用しています。この場合、ジオメトリ シェーダーで 3 つの追加法線が計算されます。これらの法線は、三角形の各エッジの中央に配置され、そのエッジを構成する頂点の 2 つの法線の平均になります。また、ジオメトリ シェーダーは各頂点の重心座標を計算します。これは、6 つの法線間の補間に使用するためにピクセル シェーダーに渡されます。
ピクセル シェーダーでは、これら 6 つの法線の重みが 6 つの基底関数によって決定され、すべて加算されて、特定のピクセルの法線が作成されます。この基底関数は次のようになります。
2x^2 + 2y^2 + 4xy -3x -3y + 1
-4x^2 -4xy + 4x
2x^2 - x
-4y^2 -4xy + 4y
2y^2 - y
4xy