カスケードされたシャドウ マップ

カスケード シャドウ マップ (CSP) は、シャドウ処理で最も一般的なエラーの 1 つに対処するための最適な方法です。パースペクティブ エイリアシング。 この技術記事では、リーダーがシャドウ マッピングに精通していることを前提に、CSP のトピックに取り組みます。 具体的には次のとおりです。

  • は、CSP の複雑さを説明します。
  • は、CSM アルゴリズムの可能なバリエーションの詳細を提供します。
  • では、最も一般的な 2 つのフィルター処理手法 (割合の近いフィルター処理 (PCF) と分散シャドウ マップ (VSM) によるフィルター処理) について説明します。
  • は、CSP へのフィルター処理の追加に関連する一般的な落とし穴の一部を識別して対処します。そして
  • は、Direct3D 11 ハードウェアを介して DIRECT3D 10 に CSP をマップする方法を示しています。

この記事で使用されるコードは、CascadedShadowMaps11 サンプルと VarianceShadows11 サンプルの DirectX ソフトウェア開発キット (SDK) にあります。 この記事は、技術記事「 影深度マップを改善するための一般的な手法」で説明されている手法を実装した後に最も役立ちます。

カスケードシャドウマップとパースペクティブエイリアシング

シャドウ マップでのパースペクティブ エイリアシングは、克服するのが最も困難な問題の 1 つです。 技術記事「シャドウ 深度マップを改善するための一般的な手法」では、パースペクティブ エイリアシングについて説明し、問題を軽減するためのいくつかのアプローチを特定します。 実際には、CSM は最適なソリューションである傾向があり、一般的に最新のゲームで使用されます。

CSP の基本的な概念は理解しやすいです。 カメラの視錐台の領域が異なると、解像度が異なるシャドウ マップが必要になります。 目に最も近いオブジェクトは、遠くのオブジェクトよりも高い解像度を必要とします。 実際、目がジオメトリに非常に近い位置に移動すると、目に最も近いピクセルの解像度が非常に高く、4096 × 4096 シャドウ マップでさえ不十分です。

CSP の基本的な考え方は、視錐台を複数の frusta にパーティション分割することです。 シャドウ マップは、各サブフラスタムに対してレンダリングされます。ピクセル シェーダーは、必要な解像度に最も近いマップからサンプリングします (図 2)。

図 1. シャドウ マップカバレッジ

シャドウ マップカバレッジ

図 1 では、品質が最高から最低まで (左から右へ) 示されています。 ビュー視錐台 (赤の逆円錐) を持つシャドウ マップを表す一連のグリッドは、さまざまな解像度のシャドウ マップでピクセル カバレッジがどのように影響を受けるかを示しています。 シャドウ マップ内のテクセルにライト空間に 1 対 1 の比率マッピング ピクセルがある場合、シャドウは最高品質 (白ピクセル) です。 遠近法のエイリアシングは、同じシャドウ テクセルにマップされるピクセル数が多すぎる場合に、大きなブロック状のテクスチャ マップ (左の画像) の形式で発生します。 シャドウ マップが大きすぎると、サンプリングの対象になります。 この場合、テクセルはスキップされ、きらめくアーティファクトが導入され、パフォーマンスが影響を受けます。

図 2. CSM シャドウ品質

csm シャドウ品質

図 2 は、図 1 の各シャドウ マップの最高品質セクションからの切り抜きを示しています。 最も近いピクセル (頂点) を持つシャドウ マップは、最も近い目です。 技術的には、これらは同じサイズのマップであり、カスケードシャドウマップの成功を例示するために白と灰色が使用されます。 白は、優れたカバレッジ (視線空間ピクセルとシャドウ マップ テクセルの 1 対 1 の比率) を示すので理想的です。

CSP には、フレームごとに次の手順が必要です。

  1. 視錐台をサブフラウスタにパーティション分割します。

  2. 各サブフラスタムの正投影法を計算します。

  3. 各サブフラスタムのシャドウ マップをレンダリングします。

  4. シーンをレンダリングします。

    1. シャドウ マップをバインドしてレンダリングします。

    2. 頂点シェーダーは、次の処理を行います。

      • 各ライト サブフラスタムのテクスチャ座標を計算します (ピクセル シェーダーで必要なテクスチャ座標が計算されない限り)。
      • 頂点の変換やライトの設定などを行います。
    3. ピクセル シェーダーでは、次の処理が行われます。

      • 適切なシャドウ マップを決定します。
      • 必要に応じてテクスチャ座標を変換します。
      • カスケードをサンプリングします。
      • ピクセルを点灯します。

Frustum のパーティション分割

視錐台をパーティション分割することは、サブフラウスタを作成する行為です。 視錐台を分割する手法の 1 つは、Z 方向の 0 % から 100% までの間隔を計算する方法です。 各間隔は、Z 軸に対するパーセンテージとして近平面と遠方平面を表します。

図 3: 任意にパーティション分割された視錐台を表示する

任意にパーティション分割された視錐台を表示する

実際には、フレームごとに錐台の分割を再計算すると、影の端がきらめきます。 一般的に受け入れられる方法は、シナリオごとに静的なカスケード間隔のセットを使用することです。 このシナリオでは、Z 軸に沿った間隔を使用して、錐台をパーティション分割するときに発生するサブフルートを記述します。 特定のシーンの正しいサイズ間隔を決定する方法は、いくつかの要因によって異なります。

シーン ジオメトリの向き

シーン ジオメトリに関して、カメラの向きはカスケード間隔の選択に影響します。 たとえば、サッカー ゲームのグラウンド カメラなど、地面に非常に近いカメラは、空のカメラとは異なる静的なカスケード間隔のセットを持ちます。

図 4 は、いくつかの異なるカメラとそれぞれのパーティションを示しています。 シーンの Z 範囲が非常に大きい場合は、より多くの分割平面が必要です。 たとえば、目が地面に非常に近く、離れたオブジェクトがまだ表示されている場合は、複数のカスケードが必要になる場合があります。 視錐台を分割して、目の近くに分割する (遠近エイリアシングが最も速く変化している) ことも重要です。 ほとんどのジオメトリがビュー視錐台の小さなセクション (オーバーヘッド ビューやフライト シミュレーターなど) に集まる場合、必要なカスケードが少なくなります。

図 4: 異なる構成では、異なる錐台分割が必要です

異なる構成では、異なる錐台分割が必要です

(左)ジオメトリのダイナミック レンジが Z の場合、多くのカスケードが必要です。 (中央)ジオメトリのダイナミックレンジが Z で低い場合、複数の錐台の利点はほとんどありません。 (右)ダイナミック レンジが中程度の場合は、3 つのパーティションのみが必要です。

ライトとカメラの向き

各カスケードの投影行列は、対応するサブフラスタムの周囲にしっかりと収まります。 ビュー カメラと光の方向が直交する構成では、重なりがほとんどない状態でカスケードをしっかりと収めることができます。 ライトとビュー カメラが平行な配置に移動すると、重なりが大きくなります (図 5)。 ライトとビュー カメラがほぼ平行である場合、"デュエル フルスタ" と呼ばれ、ほとんどのシャドウ アルゴリズムにとって非常に困難なシナリオです。 このシナリオが発生しないように、ライトとカメラを制約することは珍しくありません。 ただし、このシナリオでは、他の多くのアルゴリズムよりもはるかに優れたパフォーマンスを発揮します。

図 5: 光の方向がカメラの方向と平行になると、重なり合うカスケードが大きくなります

光の方向がカメラの方向と平行になると、重なり合うカスケードが大きくなります

多くの CSM 実装では、固定サイズの frusta が使用されます。 円錐台が固定サイズ間隔で分割されている場合、ピクセル シェーダーは Z 深度を使用してカスケードの配列にインデックスを作成できます。

バインドされたView-Frustumの計算

視錐台間隔を選択すると、サブフラウスタは、シーンに合わせ、カスケードに合わせるという 2 つのいずれかを使用して作成されます。

シーンに合わせる

すべての frusta は、同じニア プレーンを使用して作成できます。 これにより、カスケードが強制的に重複します。 CascadedShadowMaps11 サンプルでは、この手法をシーンに合わせて呼び出します。

カスケードに合わせる

または、実際のパーティション間隔をニアプレーンとファープレーンとして使用して、frusta を作成することもできます。 これにより、よりタイトなフィットが発生しますが、デュエル フルスタの場合はシーンに合わせて縮退します。 CascadedShadowMaps11 サンプルは、この手法をカスケードに適合するように呼び出します。

これら 2 つの方法を図 6 に示します。 カスケードに適合すると、より少ない解像度で無駄になります。 カスケードへの適合に関する問題は、視錐台の向きに基づいて正投影が拡大および縮小することです。 シーンに合わせる手法は、ビュー カメラの移動時に表示されるアーティファクトを削除するビュー視錐台の最大サイズによって正投影を埋め込みます。 シャドウ 深度マップを改善するための一般的な手法 では、"テクセル サイズの増分でライトを移動する" セクションでライトが移動したときに表示されるアーティファクトに対処します。

図 6: シーンに合わせるのとカスケードに合わせる

シーンに合わせるのとカスケードに合わせる

シャドウ マップをレンダリングする

CascadedShadowMaps11 サンプルでは、シャドウ マップを 1 つの大きなバッファーにレンダリングします。 これは、テクスチャ配列の PCF が Direct3D 10.1 機能であるためです。 カスケードごとに、そのカスケードに対応する深度バッファーのセクションをカバーするビューポートが作成されます。 深度のみが必要であるため、null ピクセル シェーダーがバインドされます。 最後に、深度マップがメインシャドウ バッファーに一度に 1 つずつレンダリングされるため、各カスケードに対して正しいビューポートとシャドウ マトリックスが設定されます。

シーンをレンダリングする

シャドウを含むバッファーがピクセル シェーダーにバインドされるようになりました。 CascadedShadowMaps11 サンプルで実装されているカスケードを選択する方法は 2 つあります。 これら 2 つのメソッドについてシェーダー コードで説明します。

Interval-Basedカスケード選択

図 7: 間隔ベースのカスケード選択

間隔ベースのカスケード選択

間隔ベースの選択 (図 7) では、頂点シェーダーは頂点のワールド空間内の位置を計算します。

Output.vDepth = mul( Input.vPosition, m_mWorldView ).z;

ピクセル シェーダーは、補間された深度を受け取ります。

fCurrentPixelDepth = Input.vDepth;

間隔ベースのカスケード選択では、ベクトル比較とドット積を使用して正しいカケードが決定されます。 CASCADE_COUNT_FLAGはカスケードの数を指定します。 m_fCascadeFrustumsEyeSpaceDepths_dataは、ビューの視錐台パーティションを制限します。 比較後、fComparison には、現在のピクセルがバリアより大きい値が 1、現在のカスケードが小さい場合は 0 が含まれます。 ドット積は、これらの値を配列インデックスに合計します。

        float4 vCurrentPixelDepth = Input.vDepth;
        float4 fComparison = ( vCurrentPixelDepth > m_fCascadeFrustumsEyeSpaceDepths_data[0]);
        float fIndex = dot(
        float4( CASCADE_COUNT_FLAG > 0,
        CASCADE_COUNT_FLAG > 1,
        CASCADE_COUNT_FLAG > 2,
        CASCADE_COUNT_FLAG > 3)
        , fComparison );

        fIndex = min( fIndex, CASCADE_COUNT_FLAG );
        iCurrentCascadeIndex = (int)fIndex;

カスケードを選択したら、テクスチャ座標を正しいカスケードに変換する必要があります。

vShadowTexCoord = mul( InterpolatedPosition, m_mShadow[iCascadeIndex] );

次に、このテクスチャ座標を使用して、X 座標と Y 座標を使用してテクスチャをサンプリングします。 Z 座標は、最終的な深度比較を行うために使用されます。

Map-Basedカスケード選択

マップベースの選択 (図 8) は、カスケードの 4 つの側面に対してテストし、特定のピクセルをカバーする最も狭いマップを見つけます。 頂点シェーダーは、ワールド空間内の位置を計算する代わりに、すべてのカスケードのビュー空間の位置を計算します。 ピクセル シェーダーは、現在のカスケードのインデックスが作成されるようにテクスチャ座標をスケーリングおよびシフトするために、カスケードを反復処理します。 テクスチャ座標は、テクスチャ境界に対してテストされます。 テクスチャ座標の X 値と Y 値がカスケード内にある場合は、テクスチャのサンプリングに使用されます。 Z 座標は、最終的な深度比較を行うために使用されます。

図 8: マップベースのカスケード選択

マップベースのカスケード選択

Interval-Based選択とMap-Based選択

間隔ベースの選択は、カスケード選択を直接行うことができるため、マップベースの選択よりも若干高速です。 マップベースの選択は、テクスチャ座標とカスケード境界と交差する必要があります。

マップベースの選択では、シャドウ マップが完全に揃っていない場合に、カスケードをより効率的に使用します (図 8 を参照)。

カスケード間のブレンド

VSM (この記事で後述) と PCF などのフィルター処理手法を低解像度の CSM と共に使用して、ソフト シャドウを生成できます。 残念ながら、解像度が一致しないため、カスケード レイヤー間に目に見える継ぎ目が生じます (図 9)。 この解決策は、両方のカスケードに対してシャドウ テストが実行されるシャドウ マップ間にバンドを作成することです。 次に、シェーダーは、ブレンド バンド内のピクセルの位置に基づいて、2 つの値の間を線形補間します。 CascadedShadowMaps11 と VarianceShadows11 のサンプルには、このぼかしバンドを増減するために使用できる GUI スライダーが用意されています。 シェーダーは動的分岐を実行して、ピクセルの大部分が現在のカスケードからのみ読み取るようにします。

図 9: カスケード継ぎ目

カスケード継ぎ目

(左)カスケードが重なる場所には、目に見える縫い目が見えます。 (右)カスケードがブレンドされると、継ぎ目は発生しません。

シャドウ マップのフィルター処理

PCF

通常のシャドウ マップをフィルター処理しても、ぼかした柔らかい影は生成されません。 フィルターハードウェアは深度値をぼかし、ぼかした値をライトスペーステクセルと比較します。 合格/不合格テストの結果のハード エッジはまだ存在します。 ぼかしシャドウ マップは、ハード エッジを誤って移動する場合にのみ機能します。 PCF を使用すると、シャドウ マップでのフィルター処理が可能になります。 PCF の一般的な考え方は、サブサンプルの合計数に対する深度テストに合格するサブサンプルの数に基づいて、影のピクセルの割合を計算することです。

Direct3D 10 および Direct3D 11 ハードウェアは PCF を実行できます。 PCF サンプラーへの入力は、テクスチャ座標と比較深度値で構成されます。 わかりやすくするために、PCF は 4 タップ フィルターで説明されています。 テクスチャ サンプラーは、標準フィルターと同様にテクスチャを 4 回読み取ります。 ただし、返される結果は、深度テストに合格したピクセルの割合です。 図 10 は、4 つの深度テストのいずれかに合格するピクセルの影が 25% であることを示しています。 返される実際の値は、滑らかなグラデーションを生成するためのテクスチャ読み取りのサブテキセル座標に基づく線形補間です。 この線形補間がないと、4 タップ PCF は 5 つの値 ({ 0.0、0.25、0.5、0.75、1.0 }) のみを返すことができるようになります。

図 10: 選択したピクセルの 25% がカバーされた PCF フィルター処理された画像

pcf でフィルター処理された画像(選択したピクセルの 25% がカバーされています)

ハードウェアをサポートせずに PCF を実行したり、より大きなカーネルに PCF を拡張したりすることもできます。 一部の手法では、重み付けカーネルを使用したサンプルも含まれています。 これを行うには、N × N グリッドのカーネル (ガウスなど) を作成します。 重みは 1 まで加算する必要があります。 テクスチャは N2 回サンプリングされます。 各サンプルは、カーネル内の対応する重みによってスケーリングされます。 CascadedShadowMaps11 サンプルでは、このアプローチを使用します。

深度バイアス

大きな PCF カーネルを使用すると、深度バイアスがさらに重要になります。 ピクセルのライトスペース深度を深度マップ内でマップするピクセルと比較する場合にのみ有効です。 深度マップ テクセルの隣接するは、別の位置を参照します。 この深さは似ている可能性がありますが、シーンによって大きく異なる場合があります。 図 11 では、発生する成果物が強調表示されています。 1 つの深さは、シャドウ マップ内の 3 つの隣接するテクセルと比較されます。 深度テストの 1 つが誤って失敗するのは、その深さが現在のジオメトリの計算された光空間の深さと相関しないためです。 この問題に対して推奨される解決策は、より大きなオフセットを使用することです。 ただし、オフセットが大きすぎると、ピーター パンが発生する可能性があります。 狭いニア プレーンと遠方平面を計算すると、オフセットを使用した場合の影響を軽減できます。

図 11: 誤った自己シャドウ

誤った自己シャドウ

誤ったセルフシャドウの結果、ライトスペース深度のピクセルと、関連付けのないシャドウ マップ内のテクセルが比較されます。 ライト空間の深度は、深度マップのシャドウ テクセル 2 と相関します。 テクセル 1 は光空間の深さを超え、2 は等しく、3 は小さくなります。 テクセル 2 と 3 は深度テストに合格し、テクセル 1 は失敗します。

大きなPCFに対するDDXとDDYによるPer-Texel深度バイアスの計算

大きな PCF に 対して ddxddy を使用してテクセルごとの深度バイアスを計算することは、隣接するシャドウ マップ テクセルのサーフェスが平面であると仮定して、正しい深度バイアスを計算する手法です。

この手法は、導関数情報を使用して比較深度を平面に適合します。 この手法は計算が複雑であるため、GPU に予備のコンピューティング サイクルがある場合にのみ使用する必要があります。 非常に大きなカーネルを使用する場合は、Peter Panning を発生させずに自己シャドウアーティファクトを削除する唯一の手法である可能性があります。

図 12 では、問題が強調表示されています。 ライト空間の深さは、比較される 1 つのテクセルで知られています。 深度マップ内の隣接するテクセルに対応するライトスペース深度は不明です。

図 12. シーンと深度マップ

シーンと深度マップ

レンダリングされたシーンが左に表示され、サンプル テクセル ブロックを含む深度マップが右に表示されます。 視線空間テクセルは、ブロックの中央にある D というラベルが付いたピクセルにマップされます。 この比較は正確です。 隣接する D が不明なピクセルに関連付けられた目の空間の正しい深さ。 隣接するテクセルを視線空間にマッピングできるのは、ピクセルが D と同じ三角形に関連していると仮定した場合のみです。

深度は、ライトスペースの位置と相関するテクセルで知られています。 深度マップ内の隣接するテクセルの深さは不明です。

大まかに言えば、この手法では ddx および ddy HLSL 演算を使用して、ライトスペース位置の導関数を見つけます。 これは、導関数演算によって画面空間に対するライトスペースの深さのグラデーションが返されるため、非トリビアルです。 これを光空間に対する光空間深度のグラデーションに変換するには、変換行列を計算する必要があります。

シェーダー コードを使用した説明

アルゴリズムの残りの部分の詳細は、この操作を実行するシェーダー コードの説明として提供されます。 このコードは、CascadedShadowMaps11 サンプルにあります。 図 13 は、ライト空間テクスチャ座標を深度マップにマップする方法と、X と Y の導関数を使用して変換行列を作成する方法を示しています。

図 13. 画面空間からライトスペース マトリックス

screen-space から light-space マトリックス

X と Y の光空間位置の導関数を使用して、この行列を作成します。

最初のステップは、ライトビュー空間位置の導関数を計算することです。

          float3 vShadowTexDDX = ddx (vShadowMapTextureCoordViewSpace);
          float3 vShadowTexDDY = ddy (vShadowMapTextureCoordViewSpace);

Direct3D 11 クラス GPU では、2 × 2 クワッドのピクセルを並列で実行し、 ddx の場合は X の近隣から、 ddy の場合は Y の近隣からテクスチャ座標を減算することで、これらの派生関数を計算します。 これら2つの導関数は、2×2行列の行を構成する。 現在の形式では、このマトリックスを使用して、画面空間の隣接するピクセルをライトスペースの斜面に変換できます。 ただし、この行列の逆関数が必要です。 ライトスペース隣接ピクセルを画面空間の傾斜に変換するマトリックスが必要です。

          float2x2 matScreentoShadow = float2x2( vShadowTexDDX.xy, vShadowTexDDY.xy );
          float fInvDeterminant = 1.0f / fDeterminant;

          float2x2 matShadowToScreen = float2x2 (
          matScreentoShadow._22 * fInvDeterminant,
          matScreentoShadow._12 * -fInvDeterminant,
          matScreentoShadow._21 * -fInvDeterminant,
          matScreentoShadow._11 * fInvDeterminant );

図 14: 画面領域への明るいスペース

light-space から screen-space へ

次に、このマトリックスを使用して、現在のテクセルの上と右側の 2 つのテクセルを変換します。 これらの隣接ノードは、現在のテクセルからのオフセットとして表されます。

          float2 vRightShadowTexelLocation = float2( m_fTexelSize, 0.0f );
          float2 vUpShadowTexelLocation = float2( 0.0f, m_fTexelSize );
          float2 vRightTexelDepthRatio = mul( vRightShadowTexelLocation,
          matShadowToScreen );
          float2 vUpTexelDepthRatio = mul( vUpShadowTexelLocation,
          matShadowToScreen );

行列によって作成される比率は、最終的に深度の導関数で乗算され、隣接するピクセルの深度オフセットが計算されます。

            float fUpTexelDepthDelta =
            vUpTexelDepthRatio.x * vShadowTexDDX.z
            + vUpTexelDepthRatio.y * vShadowTexDDY.z;
            float fRightTexelDepthDelta =
            vRightTexelDepthRatio.x * vShadowTexDDX.z
            + vRightTexelDepthRatio.y * vShadowTexDDY.z;

PCF ループでこれらの重みを使用して、位置にオフセットを追加できるようになりました。

    for( int x = m_iPCFBlurForLoopStart; x < m_iPCFBlurForLoopEnd; ++x ) 
    {
        for( int y = m_iPCFBlurForLoopStart; y < m_iPCFBlurForLoopEnd; ++y )
            {
            if ( USE_DERIVATIVES_FOR_DEPTH_OFFSET_FLAG )
            {
            depthcompare += fRightTexelDepthDelta * ( (float) x ) +
            fUpTexelDepthDelta * ( (float) y );
            }
            // Compare the transformed pixel depth to the depth read
            // from the map.
            fPercentLit += g_txShadow.SampleCmpLevelZero( g_samShadow,
            float2(
            vShadowTexCoord.x + ( ( (float) x ) * m_fNativeTexelSizeInX ) ,
            vShadowTexCoord.y + ( ( (float) y ) * m_fTexelSize )
            ),
            depthcompare
            );
            }
     }

PCF と CSM

PCF は Direct3D 10 のテクスチャ配列では機能しません。 PCF を使用するために、すべてのカスケードが 1 つの大きなテクスチャ アトラスに格納されます。

Derivative-Based オフセット

CSP の派生ベースのオフセットを追加すると、いくつかの課題が発生します。 これは、分岐フロー制御内の派生計算が原因です。 この問題は、GPU が動作する基本的な方法が原因で発生します。 Direct3D11 GPU は、2 × 2 クワッドのピクセルで動作します。 派生関数を実行するために、GPU は通常、隣接するピクセルの同じ変数のコピーから現在のピクセルの変数のコピーを減算します。 これがどのように行われるかは、GPU によって異なります。 テクスチャ座標は、マップベースまたは間隔ベースのカスケード選択によって決定されます。 ピクセル クワッド内の一部のピクセルは、残りのピクセルとは異なるカスケードを選択します。 これにより、派生ベースのオフセットが完全に間違っているため、シャドウ マップ間の継ぎ目が表示されます。 解決策は、ライトビュー空間テクスチャ座標に対して派生を実行することです。 これらの座標は、すべてのカスケードで同じです。

PCF カーネルのパディング

シャドウ バッファーが埋め込まれていない場合、PCF カーネルはカスケード パーティションの外部にインデックスを付けます。 解決策は、カスケードの外側の縁を PCF カーネルの 2 分の 1 のサイズで埋め込むことです。 これは、カスケードを選択するシェーダーと、境界を保持するのに十分な大きさのカスケードをレンダリングする必要があるプロジェクション マトリックスに実装する必要があります。

分散シャドウ マップ

VSM (詳細については、「Donnelly と Lauritzen による 分散シャドウ マップ 」を参照) では、直接シャドウ マップのフィルター処理を有効にします。 VSM を使用する場合は、テクスチャ フィルタリング ハードウェアのすべての機能を使用できます。 三線および異方性(図15)フィルタリングが使用できる。 さらに、畳み込みを使用して VM を直接ぼかすことができます。 VSM にはいくつかの欠点があります。深度データの 2 つのチャネルを格納する必要があります (深さと深さの 2 乗)。 影が重なると、光の出血が一般的です。 ただし、解像度が低く、CSM と組み合わせることもできます。

図 15: 異方性フィルタリング

異方性フィルタリング

アルゴリズムの詳細

VSM は、深度と深度を 2 チャネルのシャドウ マップにレンダリングすることで機能します。 この 2 チャンネルのシャドウ マップは、通常のテクスチャと同様にぼやけてフィルター処理できます。 次に、アルゴリズムは、ピクセル シェーダーで Chebychev の不等値を使用して、深度テストに合格するピクセル領域の割合を推定します。

ピクセル シェーダーは、深度と深度の 2 乗値をフェッチします。

        float  fAvgZ  = mapDepth.x; // Filtered z
        float  fAvgZ2 = mapDepth.y; // Filtered z-squared

深度の比較が実行されます。

        if ( fDepth <= fAvgZ )
        {
        fPercentLit = 1;
        }

深度の比較が失敗した場合、点灯しているピクセルの割合が推定されます。 分散は、2 乗平均から平均 2 乗を引いた値として計算されます。

        float variance = ( fAvgZ2 ) − ( fAvgZ * fAvgZ );
        variance = min( 1.0f, max( 0.0f, variance + 0.00001f ) );

fPercentLit 値は、Chebychev の不等値で推定されます。

        float mean           = fAvgZ;
        float d              = fDepth - mean;
        float fPercentLit    = variance / ( variance + d*d );

ライトブリード

VSM の最大の欠点は、光ブリードです (図 16)。 ライトブリードは、複数のシャドウキャスターがエッジに沿って互いに隠れ合うときに発生します。 VSM は、深度の不一部に基づいて影の端を網掛けします。 影が重なり合うと、影を付ける領域の中心に深さの不一貫性が存在します。 これは、VSM アルゴリズムの使用に関する問題です。

図 16: VSM 光ブリード

vsm 光の出血

この問題の部分的な解決策は、fPercentLit を累乗することです。 これは、ぼかしを減衰させる効果があり、深度の不一貫性が小さいアーティファクトを引き起こす可能性があります。 場合によっては、問題を軽減する魔法の値が存在します。

fPercentLit = pow( p_max, MAGIC_NUMBER );

電力に照らされた割合を上げる代わりに、影が重なる構成を回避することもできます。 高度に調整されたシャドウ構成でも、ライト、カメラ、ジオメトリに対していくつかの制約があります。 また、解像度の高いテクスチャを使用することで、光のブリードも軽減されます。

階層分散シャドウ マップ (LVSM) は、視錐台をライトに垂直なレイヤーに分割する代わりに問題を解決します。 VM も使用されている場合、必要なマップの数は非常に多くなります。

さらに、VSM に関する論文の共同執筆者であり、LVSM に関する論文の著者である Andrew Lauritzen は、 Beyond3D フォーラムで光のブレンドを打ち消すために、指数シャドウ マップ (ESM) と VSM の組み合わせについて説明しました。

VM を含む VSM

サンプル VarianceShadow11 は、VSM と CSM を組み合わせたものになります。 この組み合わせは非常に簡単です。 このサンプルは、CascadedShadowMaps11 サンプルと同じ手順に従います。 PCF は使用されないため、2 パスの分離可能な畳み込みでは影がぼやけています。 PCF を使用しない場合、サンプルではテクスチャ アトラスの代わりにテクスチャ配列を使用することもできます。 テクスチャ配列上の PCF は、Direct3D 10.1 機能です。

CSM を使用したグラデーション

CSM でグラデーションを使用すると、図 17 に示すように、2 つのカスケード間の境界線に沿って継ぎ目を生成できます。 このサンプル命令では、ピクセル間の派生物を使用して、フィルターに必要なミップマップ レベルなどの情報を計算します。 これにより、特にミップマップの選択や異方性フィルタリングに問題が発生します。 四角形のピクセルがシェーダーで異なる分岐を取る場合、GPU ハードウェアによって計算される派生物は無効です。 これにより、シャドウ マップに沿って継ぎ目がジャグされます。

図 17: 異方流制御による異方性フィルタリングによるカスケード境界線上の継ぎ目

異なるフロー制御による異方性フィルタリングによるカスケード境界線上の継ぎ目

この問題は、ライトビュー空間内の位置の導関数を計算することによって解決されます。ライト ビュー空間座標は、選択したカスケードに固有ではありません。 計算された派生物は、プロジェクション テクスチャ マトリックスのスケール部分によって正しいミップマップ レベルにスケーリングできます。

        float3 vShadowTexCoordDDX = ddx( vShadowMapTextureCoordViewSpace );
        vShadowTexCoordDDX *= m_vCascadeScale[iCascade].xyz;
        float3 vShadowTexCoordDDY = ddy( vShadowMapTextureCoordViewSpace );
        vShadowTexCoordDDY *= m_vCascadeScale[iCascade].xyz;

        mapDepth += g_txShadow.SampleGrad( g_samShadow, vShadowTexCoord.xyz,
        vShadowTexCoordDDX, vShadowTexCoordDDY );

PCF を使用した標準シャドウと比較した VSM

VSM と PCF はどちらも、深度テストに合格するピクセル領域の割合を概算しようとします。 VSM はフィルター処理ハードウェアで動作し、分離可能なカーネルでぼかしることができます。 分離可能な畳み込みカーネルは、完全なカーネルよりも実装する方がかなり安価です。 さらに、VSM は、ライトスペース深度マップ内の 1 つの値と 1 つのライトスペース深度を比較します。 これは、VSM に PCF と同じオフセット問題がないことを意味します。 技術的には、VSM は、より大きな領域に対して深度をサンプリングし、統計分析を実行します。 これは PCF よりも正確ではありません。 実際には、VSM はブレンドの非常に良い仕事をするため、必要なオフセットが少なくなります。 前述のように、VSM に対する第 1 の欠点は、光出血です。

VSM と PCF は、GPU コンピューティング能力と GPU テクスチャ帯域幅のトレードオフを表します。 VSM では、分散を計算するために、より多くの数学を実行する必要があります。 PCF には、より多くのテクスチャ メモリ帯域幅が必要です。 大規模な PCF カーネルは、テクスチャ帯域幅によってすぐにボトルネックになる可能性があります。 GPU の計算能力が GPU 帯域幅よりも急速に増加する中、VSM は 2 つのアルゴリズムのより実用的なものになっています。 また、ブレンドとフィルター処理により、VSM の方が解像度の低いシャドウ マップの方が見た目が良くなります。

まとめ

CSP は、パースペクティブ エイリアシングの問題に対するソリューションを提供します。 タイトルに必要な視覚的な忠実性を取得するには、いくつかの構成が考えられます。 PCF と VSM は広く使用されており、エイリアスを減らすために、VM と組み合わせる必要があります。

リファレンス

Donnelly、W.、Lauritzen、A. Variance シャドウ マップ。 SI3D '06: インタラクティブな3Dグラフィックスとゲームに関する2006シンポジウムの議事録。 2006. pp. 161-165. ニューヨーク、ニューヨーク、アメリカ:ACMプレス。

Lauritzen、Andrew、McCool、Michael。 レイヤー分散シャドウ マップ。 グラフィックスインターフェイスの議事録 2008, 5月 28- 30, 2008, ウィンザー, オンタリオ州, カナダ.

Engel,Woflgang F. Section 4. カスケード シャドウ マップ。 ShaderX5、Advanced Rendering Techniques、Wolfgang F. Engel、Ed。 チャールズリバーメディア、ボストン、マサチューセッツ州。 2006. pp. 197-206.