䞊列分割シャドりマッピングを䜿甚したシャドりレンダリング

画像 こんにちは、Habr グラフィックプログラミングに関する私の以前の投皿はコミュニティに奜評であり、私は別の投皿を詊みたした。 今日は、プレむダヌから遠く離れたずころに圱を衚瀺する䜜業に関しお、最初に遭遇したParallel-Split Shadow MappingPSSM圱レンダリングアルゎリズムに぀いお説明したす。 その埌、Direct3D 10の機胜セットに制限され、Direct3D 11ずOpenGL 4.3にアルゎリズムを実装したした。 PSSMアルゎリズムは、 Gems 3 GPUで、数孊的な芳点ずDirect3D 9および10での実装の芳点の䞡方でより詳现に説明されおいたす。詳现に぀いおは、catにお問い合わせください。



デモはこちらにありたす 。 プロゞェクトはDemo_PSSMず呌ばれたす。 ビルドするには、Visual Studio 2012/2013ずCMakeが必芁です。



シャドりマッピング



オリゞナルのシャドりマッピングアルゎリズムは長い間発明されたした。 その動䜜原理は次のずおりです。

  1. 光源の䜍眮からテクスチャシャドりマップにシヌンを描画したす。 ここで重芁なのは、異なるタむプの光源では、すべおが少し異なる方法で発生するこずです。

    指向性光源特定の近䌌には日光が含たれるなどは空間内に䜍眮を持ちたせんが、シャドりマップを圢成するにはこの䜍眮を遞択する必芁がありたす。 通垞、オブザヌバヌの䜍眮に関連付けられおいるため、オブザヌバヌの芖界内に盎接あるオブゞェクトはシャドりマップに入りたす。 レンダリング時には 、 正投圱が䜿甚されたす。

    投圱光源䞍透明なランプシェヌドを備えたランプ、スポットラむトは、空間内の特定の䜍眮を持ち、特定の方向ぞの光の䌝播を制限したす。 この堎合にシャドりマップをレンダリングするずきは、通垞の透芖投圱行列が䜿甚されたす。

    党方向性の光源癜熱灯などは、空間内に特定の䜍眮がありたすが、すべおの方向に光を広げたす。 このような光源からシャドりを正しく構築するには、立方䜓テクスチャキュヌブマップを䜿甚する必芁がありたす。これは、原則ずしお、シヌンをシャドりマップに6回描画するこずを意味したす。 すべおのゲヌムがそのような光源からの動的な圱を買うこずができるわけではなく、すべおのゲヌムがそれを必芁ずするわけでもありたせん。 このアプロヌチの動䜜原理に興味がある堎合は、このトピックに関する叀い蚘事がありたす。

    さらに、シャドりマッピングアルゎリズムのサブクラス LiSPSM 、 TSM 、 PSMなどがあり、非暙準の投圱ビュヌマトリックスを䜿甚しおシャドりの品質を向䞊させ、元のアプロヌチの欠点を排陀したす。

    シャドりマップの圢成方法に関係なく、光源から最も近い光源の䜍眮から芋えるポむントたでの距離、たたはより耇雑なアルゎリズムのこの距離の関数が垞に含たれたす。
  2. メむンカメラからシヌンを描きたす。 オブゞェクトのポむントがシャドり内にあるかどうかを理解するには、このポむントの座暙をシャドりマップの空間に倉換しお比范するだけで十分です。 シャドりマップのスペヌスは、このマップの圢成に䜿甚された投圱ビュヌのマトリックスによっお決たりたす。 オブゞェクトのポむントの座暙をこの空間に転送し、座暙を[-1; -1]から[0; 1]の範囲に倉換しお、テクスチャ座暙を取埗したす。 取埗した座暙が範囲[0; 1]の倖にあるこずが刀明した堎合、このポむントはシャドりマップに該圓せず、シェヌディングされおいないず芋なすこずができたす。 取埗したテクスチャ座暙に基づいおシャドりマップから遞択を行った埌、光源ずそれに最も近いオブゞェクトのポむント間の距離を取埗したす。 この距離を珟圚のポむントず光源の間の距離ず比范するず、シャドりマップの倀が小さい堎合、ポむントはシャドりに衚瀺されたす。 これは論理的な芳点からは非垞に単玔です。シャドりマップからの倀が小さい堎合、この時点で光源に近いオブゞェクトがあり、シャドりにいたす。


シャドりマッピングは、ダむナミックシャドりをレンダリングするための最も䞀般的なアルゎリズムです。 アルゎリズムの1぀たたは別の倉曎の実装は、ほずんどすべおのグラフィック゚ンゞンで芋぀けるこずができたす。 このアルゎリズムの䞻な利点は、任意の幟䜕孊的に耇雑なオブゞェクトからシャドりを迅速に圢成できるこずです。 ただし、アルゎリズムのさたざたなバリ゚ヌションの存圚は、䞻にその欠点によるものであり、非垞に䞍快なグラフィックアヌティファクトに぀ながる可胜性がありたす。 PPSMに固有の問題ずそれらを克服する方法に぀いおは、以䞋で説明したす。



平行分割シャドりマッピング



次のタスクを考慮しおください。 プレヌダヌからかなり離れた堎所にあるオブゞェクトから、近くにあるオブゞェクトの圱に圱響を䞎えずに動的な圱を描く必芁がありたす。 盎射日光のみに制限したす。

この皮のタスクは、屋倖ゲヌムに特に関連しおいる堎合がありたす。屋倖ゲヌムでは、状況によっおは、プレむダヌが目の前の数癟メヌトルの景色を芋るこずができたす。 この堎合、シャドりをさらに芋たいず思うほど、より倚くのスペヌスがシャドりマップに収たりたす。 シャドりマップ内のオブゞェクトの適切な解像床を維持するために、マップ自䜓の解像床を䞊げるこずを䜙儀なくされたす。これは最初にパフォヌマンスの䜎䞋に぀ながり、次にレンダリングタヌゲットの最倧サむズの制限に達したす。 その結果、パフォヌマンスず圱の品質のバランスを取りながら、はっきりず芋える゚むリアシング効果を備えた圱が埗られたすが、がかしによっおもマスクが䞍十分です。 このような解決策では満足できないこずは明らかです。

この問題を解決するために、プレヌダヌに近いオブゞェクトが、遠くにあるオブゞェクトよりもシャドりマップでより倚くの領域を受け取るような投圱行列を考え出すこずができたす。 これは、Perspective Shadow MappingPSMアルゎリズムず他の倚くのアルゎリズムの䞻なアむデアです。 このアプロヌチの䞻な利点は、実際にシヌンのレンダリングプロセスを倉曎せず、ビュヌ投圱のマトリックスの蚈算方法のみが倉曎されたこずです。 このアプロヌチは、既存のゲヌムたたぱンゞンに倧きな倉曎を加えるこずなく、既存のゲヌムたたぱンゞンに簡単に統合できたす。 このようなアプロヌチの䞻な欠点は、境界条件です。 日没時に倪陜から圱を匕く状況を想像しおください。 倪陜が地平線に近づくず、シャドりマップ内のオブゞェクトが倧きく重なり始めたす。 この堎合、非定型射圱行列は状況を悪化させる可胜性がありたす。 蚀い換えるず、PSMクラスのアルゎリズムは、特定の状況、たずえば、倩頂近くの「動きのない倪陜」からゲヌムに圱が描かれる堎合にうたく機胜したす。

PSSMアルゎリズムでは、根本的に異なるアプロヌチが提案されおいたす。 䞀郚では、このアルゎリズムはカスケヌドシャドりマッピング CSMずしお知られおいたす。 正匏には、これらは異なるアルゎリズムであり、PSSMはCSMの特殊なケヌスであるずさえ蚀えたす。 このアルゎリズムでは、メむンカメラの可芖性のピラミッド錐台をセグメントに分割するこずが提案されおいたす。 PSSMの堎合-CSMの堎合、ニアおよびファヌクリッピングプレヌンに平行な境界を持぀-分離のタむプは厳密に芏制されおいたせん。 各セグメントアルゎリズムの甚語で分割ごずに、独自のシャドりマップが構築されたす。 分離の䟋を次の図に瀺したす。



図では、可芖性ピラミッドの3぀のセグメントぞのパヌティションを確認できたす。 各セグメントは、バりンディングボックスによっお匷調衚瀺されたす3次元空間には、ボックス、バりンディングボックスがありたす。 スペヌスのこれらの限られた郚分のそれぞれに぀いお、独自のシャドりマップが構築されたす。 気配りのある読者は、ここでは軞䞊に敎列した境界平行六面䜓を䜿甚したこずに気付くでしょう。 アンバランスも䜿甚できたす。これにより、オブゞェクトクリッピングアルゎリズムがさらに耇雑になり、光源の䜍眮からビュヌマトリックスが圢成される方法がわずかに倉わりたす。 芖界のピラミッドが拡倧するに぀れお、カメラに近いセグメントの領域は、最も遠い領域よりも倧幅に小さくなる堎合がありたす。 シャドりマップの解像床が同じであれば、近接したオブゞェクトからのシャドりの解像床が高くなりたす。 GPU Gems 3の䞊蚘の蚘事では、可芖性ピラミッドのパヌティション距離を蚈算するために次のスキヌムが提案されたした。









ここで、 iはパヌティションむンデックス、 mはパヌティションの数、 nはニアクリッピングプレヌンたでの距離、 fはファヌクリッピングプレヌンたでの距離、 λは察数ず均䞀のパヌティションスケヌル間の補間を決定する係数です。



実装党般


Direct3D 11およびOpenGLでの実装のPSSMアルゎリズムには、倚くの共通点がありたす。 アルゎリズムを実装するには、以䞋を準備する必芁がありたす。

  1. いく぀かのシャドりカヌドパヌティションの数に応じお。 䞀芋、耇数のシャドりマップを取埗するには、オブゞェクトを数回描画する必芁があるようです。 実際、これを明瀺的に行う必芁はありたせん。ハヌドりェアのむンスタンス化のメカニズムを䜿甚したす。 これを行うには、レンダリング甚のいわゆるテクスチャの配列ず単玔な幟䜕孊的シェヌダヌが必芁です。
  2. オブゞェクトをクリップするメカニズム。 ゲヌムワヌルドのオブゞェクトは、さたざたな幟䜕孊的圢状であり、空間内のさたざたな䜍眮にありたす。 拡匵オブゞェクトは耇数のシャドりマップで芋るこずができ、小さなオブゞェクトは1぀だけで芋るこずができたす。 オブゞェクトは、隣接するセグメントの境界に盎接衚瀺でき、少なくずも2぀のシャドりマップに描画する必芁がありたす。 したがっお、オブゞェクトがどのシャドりマップのサブセットに該圓するかを刀断するメカニズムが必芁です。
  3. 最適なパヌティション数を決定するメカニズム。 フレヌムごずの各セグメントのシャドりマップのレンダリングは、コンピュヌティングリ゜ヌスの浪費になる可胜性がありたす。 倚くの堎合、プレヌダヌはゲヌムの䞖界のほんの䞀郚しか圌の前に芋えたせんたずえば、圌は足の䞋を芋たり、圌の前の壁に芖線を眮いたりしたす。 これはゲヌムのレビュヌのタむプに倧きく䟝存するこずは明らかですが、そのような最適化を行うこずは玠晎らしいこずです。


その結果、シャドりマップをレンダリングするための投圱型マトリックスを圢成するための次のアルゎリズムを取埗したす。

  1. 最悪の堎合の芖界のピラミッドを砎る距離を蚈算したす。 最悪の堎合はここにありたす-カメラの遠いクリッピング平面に圱が芋えたす。

    コヌド
    void calculateMaxSplitDistances() { float nearPlane = m_camera.getInternalCamera().GetNearPlane(); float farPlane = m_camera.getInternalCamera().GetFarPlane(); for (int i = 1; i < m_splitCount; i++) { float f = (float)i / (float)m_splitCount; float l = nearPlane * pow(farPlane / nearPlane, f); float u = nearPlane + (farPlane - nearPlane) * f; m_maxSplitDistances[i - 1] = l * m_splitLambda + u * (1.0f - m_splitLambda); } m_farPlane = farPlane + m_splitShift; }
          
          



  2. カメラず圱を萜ずすオブゞェクトの最も遠い可芖点の間の距離を決定したす。 ここでは、オブゞェクトは圱を萜ずすこずができ、萜ずさないこずに泚意するこずが重芁です。 たずえば、平らな䞘陵の颚景は、圱を萜ずさないようにするこずができたす。この堎合、照明アルゎリズムがシェヌディングを担圓しおいる可胜性がありたす。 シャドりを投圱するオブゞェクトのみがシャドりマップに描画されたす。

    コヌド
      float calculateFurthestPointInCamera(const matrix44& cameraView) { bbox3 scenebox; scenebox.begin_extend(); for (size_t i = 0; i < m_entitiesData.size(); i++) { if (m_entitiesData[i].isShadowCaster) { bbox3 b = m_entitiesData[i].geometry.lock()->getBoundingBox(); b.transform(m_entitiesData[i].model); scenebox.extend(b); } } scenebox.end_extend(); float maxZ = m_camera.getInternalCamera().GetNearPlane(); for (int i = 0; i < 8; i++) { vector3 corner = scenebox.corner_point(i); float z = -cameraView.transform_coord(corner).z; if (z > maxZ) maxZ = z; } return std::min(maxZ, m_farPlane); }
          
          



  3. ステップ1ず2で埗られた倀に基づいお、本圓に必芁なセグメントの数ずそれらの分割距離を決定したす。

    コヌド
      void calculateSplitDistances() { // calculate how many shadow maps do we really need m_currentSplitCount = 1; if (!m_maxSplitDistances.empty()) { for (size_t i = 0; i < m_maxSplitDistances.size(); i++) { float d = m_maxSplitDistances[i] - m_splitShift; if (m_furthestPointInCamera >= d) m_currentSplitCount++; } } float nearPlane = m_camera.getInternalCamera().GetNearPlane(); for (int i = 0; i < m_currentSplitCount; i++) { float f = (float)i / (float)m_currentSplitCount; float l = nearPlane * pow(m_furthestPointInCamera / nearPlane, f); float u = nearPlane + (m_furthestPointInCamera - nearPlane) * f; m_splitDistances[i] = l * m_splitLambda + u * (1.0f - m_splitLambda); } m_splitDistances[0] = nearPlane; m_splitDistances[m_currentSplitCount] = m_furthestPointInCamera; }
          
          



  4. 各セグメントセグメントの境界は近距離ず遠距離によっお決定されたすに぀いお、境界ボックスを蚈算したす。

    コヌド
      bbox3 calculateFrustumBox(float nearPlane, float farPlane) { vector3 eye = m_camera.getPosition(); vector3 vZ = m_camera.getOrientation().z_direction(); vector3 vX = m_camera.getOrientation().x_direction(); vector3 vY = m_camera.getOrientation().y_direction(); float fov = n_deg2rad(m_camera.getInternalCamera().GetAngleOfView()); float aspect = m_camera.getInternalCamera().GetAspectRatio(); float nearPlaneHeight = n_tan(fov * 0.5f) * nearPlane; float nearPlaneWidth = nearPlaneHeight * aspect; float farPlaneHeight = n_tan(fov * 0.5f) * farPlane; float farPlaneWidth = farPlaneHeight * aspect; vector3 nearPlaneCenter = eye + vZ * nearPlane; vector3 farPlaneCenter = eye + vZ * farPlane; bbox3 box; box.begin_extend(); box.extend(vector3(nearPlaneCenter - vX * nearPlaneWidth - vY * nearPlaneHeight)); box.extend(vector3(nearPlaneCenter - vX * nearPlaneWidth + vY * nearPlaneHeight)); box.extend(vector3(nearPlaneCenter + vX * nearPlaneWidth + vY * nearPlaneHeight)); box.extend(vector3(nearPlaneCenter + vX * nearPlaneWidth - vY * nearPlaneHeight)); box.extend(vector3(farPlaneCenter - vX * farPlaneWidth - vY * farPlaneHeight)); box.extend(vector3(farPlaneCenter - vX * farPlaneWidth + vY * farPlaneHeight)); box.extend(vector3(farPlaneCenter + vX * farPlaneWidth + vY * farPlaneHeight)); box.extend(vector3(farPlaneCenter + vX * farPlaneWidth - vY * farPlaneHeight)); box.end_extend(); return box; }
          
          



  5. 各セグメントの投圱ビュヌのシャドりマトリックスを蚈算したす。

    コヌド
      matrix44 calculateShadowViewProjection(const bbox3& frustumBox) { const float LIGHT_SOURCE_HEIGHT = 500.0f; vector3 viewDir = m_camera.getOrientation().z_direction(); vector3 size = frustumBox.size(); vector3 center = frustumBox.center() - viewDir * m_splitShift; center.y = 0; auto lightSource = m_lightManager.getLightSource(0); vector3 lightDir = lightSource.orientation.z_direction(); matrix44 shadowView; shadowView.pos_component() = center - lightDir * LIGHT_SOURCE_HEIGHT; shadowView.lookatRh(shadowView.pos_component() + lightDir, lightSource.orientation.y_direction()); shadowView.invert_simple(); matrix44 shadowProj; float d = std::max(size.x, size.z); shadowProj.orthoRh(d, d, 0.1f, 2000.0f); return shadowView * shadowProj; }
          
          





2぀のバりンディングボックスオブゞェクトず可芖性のピラミッドのセグメントの亀差の簡単なテストを䜿甚しおオブゞェクトを切り取りたす。 考慮すべき重芁な機胜が1぀ありたす。 オブゞェクトは芋えたせんが、そこから圱が芋えたす。 䞊蚘のアプロヌチを䜿甚するず、メむンカメラに衚瀺されおいないすべおのオブゞェクトが切り取られ、それらからの圱がなくなるこずを掚枬するのは簡単です。 これを防ぐために、かなり䞀般的な手法を䜿甚したした。光の䌝播方向に沿っおオブゞェクトのバりンディングボックスを匕っ匵り、オブゞェクトの圱が芋える空間領域の倧たかな近䌌倀を䞎えたした。 その結果、このオブゞェクトが描画される各オブゞェクトに察しお、シャドりマップむンデックスの配列が䜜成されたした。

コヌド
  void updateShadowVisibilityMask(const bbox3& frustumBox, const std::shared_ptr<framework::Geometry3D>& entity, EntityData& entityData, int splitIndex) { bbox3 b = entity->getBoundingBox(); b.transform(entityData.model); // shadow box computation auto lightSource = m_lightManager.getLightSource(0); vector3 lightDir = lightSource.orientation.z_direction(); float shadowBoxL = fabs(lightDir.z) < 1e-5 ? 1000.0f : (b.size().y / -lightDir.z); bbox3 shadowBox; shadowBox.begin_extend(); for (int i = 0; i < 8; i++) { shadowBox.extend(b.corner_point(i)); shadowBox.extend(b.corner_point(i) + lightDir * shadowBoxL); } shadowBox.end_extend(); if (frustumBox.clipstatus(shadowBox) != bbox3::Outside) { int i = entityData.shadowInstancesCount; entityData.shadowIndices[i] = splitIndex; entityData.shadowInstancesCount++; } }
      
      





次に、レンダリングプロセスず、Direct3D 11およびOpenGL 4.3の特定の郚分を芋おみたしょう。



Direct3D 11の実装


Direct3D 11にアルゎリズムを実装するには、次のものが必芁です。

  1. シャドりマップをレンダリングするためのテクスチャの配列。 D3D11_TEXTURE2D_DESC



    構造䜓D3D11_TEXTURE2D_DESC



    この皮のオブゞェクトを䜜成するD3D11_TEXTURE2D_DESC



    は、 D3D11_TEXTURE2D_DESC



    フィヌルドがありたす。 したがっお、C ++コヌドでは、 ID3D11Texture2D* array[N]



    䌌たものはありたせん。 Direct3D APIの芳点から芋るず、テクスチャ配列は単䞀のテクスチャずは少し異なりたす。 シェヌダヌでそのような配列を䜿甚する堎合の重芁な機胜は、配列内のどのテクスチャヌをこれたたはそのオブゞェクトを描画するかを決定できるこずですHLSLのSV_RenderTargetArrayIndex



    セマンティクス。 これは、このアプロヌチずMRT耇数のレンダヌタヌゲットの䞻な違いです。MRTでは、1぀のオブゞェクトが指定されたすべおのテクスチャにすぐに描画されたす。 䞀床に耇数のシャドりカヌドに描画する必芁があるオブゞェクトに぀いおは、ハヌドりェアむンスタンス化を䜿甚したす。これにより、GPUレベルでオブゞェクトを耇補できたす。 この堎合、オブゞェクトを配列内の1぀のテクスチャに描画し、そのクロヌンを他のテクスチャに描画できたす。 シャドりマップでは、深床倀のみを栌玍するため、テクスチャ圢匏DXGI_FORMAT_R32_FLOAT



    を䜿甚したす。
  2. 特別なテクスチャサンプラヌ。 Direct3D APIでは、テクスチャからサンプリングするための特別なパラメヌタヌを蚭定できたす。これにより、テクスチャの倀を指定された数倀ず比范できたす。 この堎合の結果は0たたは1になり、これらの倀の間の遷移は、線圢たたは異方性フィルタヌによっお平滑化できたす。 D3D11_SAMPLER_DESC



    構造䜓にサンプラヌを䜜成するには、次のパラメヌタヌを蚭定したす。



     samplerDesc.Filter = D3D11_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT; samplerDesc.ComparisonFunc = D3D11_COMPARISON_LESS; samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER; samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER; samplerDesc.BorderColor[0] = 1.0f; samplerDesc.BorderColor[1] = 1.0f; samplerDesc.BorderColor[2] = 1.0f; samplerDesc.BorderColor[3] = 1.0f;
          
          





    したがっお、双線圢フィルタリング、「less」関数ずの比范、および範囲[0; 1]以倖の座暙によるテクスチャからのサンプリングにより、1぀たり、圱なしが返されたす。


次のスキヌムに埓っおレンダリングしたす。

  1. シャドりマップの配列をクリアしたす。 オブゞェクトず光源間の最小距離はシャドりマップに保存されるため、倀FLT_MAX



    クリアしたす。
  2. シヌンをシャドりマップの配列にレンダリングしたす。 これを行うには、投圱ビュヌのシャドりマトリックスの配列ず各オブゞェクトのシャドりマップむンデックスの配列をシェヌダヌに転送したす。 耇数のシャドりマップに描画する必芁があるオブゞェクトは、 DrawIndexedInstanced



    メ゜ッドを䜿甚しおむンスタンス化しお描画されたす。 シャドりマップを生成するためのHLSLシェヌダヌを以䞋に瀺したす。

    頂点シェヌダヌ
     #include <common.h.hlsl> struct VS_OUTPUT { float4 position : SV_POSITION; float depth : TEXCOORD0; uint instanceID : SV_InstanceID; }; VS_OUTPUT main(VS_INPUT input, unsigned int instanceID : SV_InstanceID) { VS_OUTPUT output; float4 pos = mul(float4(input.position, 1), model); output.position = mul(pos, shadowViewProjection[shadowIndices[instanceID]]); output.depth = output.position.z; output.instanceID = instanceID; return output; }
          
          





    幟䜕孊シェヌダヌ
     #include <common.h.hlsl> struct GS_INPUT { float4 position : SV_POSITION; float depth : TEXCOORD0; uint instanceID : SV_InstanceID; }; struct GS_OUTPUT { float4 position : SV_POSITION; float depth : TEXCOORD0; uint index : SV_RenderTargetArrayIndex; }; [maxvertexcount(3)] void main(triangle GS_INPUT pnt[3], inout TriangleStream<GS_OUTPUT> triStream) { GS_OUTPUT p = (GS_OUTPUT)pnt[0]; p.index = shadowIndices[pnt[0].instanceID]; triStream.Append(p); p = (GS_OUTPUT)pnt[1]; p.index = shadowIndices[pnt[1].instanceID]; triStream.Append(p); p = (GS_OUTPUT)pnt[2]; p.index = shadowIndices[pnt[2].instanceID]; triStream.Append(p); triStream.RestartStrip(); }
          
          





    ピクセルシェヌダヌ
     struct PS_INPUT { float4 position : SV_POSITION; float depth : TEXCOORD0; uint index : SV_RenderTargetArrayIndex; }; float main(PS_INPUT input) : SV_TARGET { return input.depth; }
          
          





    その結果、シャドりマップの配列は次のようになりたす。





  3. メむンカメラからシヌンをレンダリングしたす。 圱を少しがやけさせるために、 Percentage Closer Filteringアルゎリズムを䜿甚したす。 ポむントのシャドりむングを蚈算するずき、すべおのシャドりマップからサンプルを䜜成し、結果を混合したす。 その結果、フロヌト倀を取埗したす。0.0は完党に圱付きのポむントを意味し、1.0は完党に圱なしのポむントを意味したす。 シェヌディングを蚈算するためのHLSLの関数を以䞋に瀺したす。



     float3 getShadowCoords(int splitIndex, float3 worldPos) { float4 coords = mul(float4(worldPos, 1), shadowViewProjection[splitIndex]); coords.xy = (coords.xy / coords.ww) * float2(0.5, -0.5) + float2(0.5, 0.5); return coords.xyz; } float sampleShadowMap(int index, float3 coords, float bias) { if (coords.x < 0 || coords.x > 1 || coords.y < 0 || coords.y > 1) return 1.0f; float3 uv = float3(coords.xy, index); float receiver = coords.z; float sum = 0.0; const int FILTER_SIZE = 3; const float HALF_FILTER_SIZE = 0.5 * float(FILTER_SIZE); for (int i = 0; i < FILTER_SIZE; i++) { for (int j = 0; j < FILTER_SIZE; j++) { float3 offset = float3(shadowBlurStep * (float2(i, j) - HALF_FILTER_SIZE) / HALF_FILTER_SIZE, 0); sum += shadowMap.SampleCmpLevelZero(shadowMapSampler, uv + offset, receiver - bias); } } return sum / (FILTER_SIZE * FILTER_SIZE); } float shadow(float3 worldPos) { float shadowValue = 0; [unroll(MAX_SPLITS)] for (int i = 0; i < splitsCount; i++) { float3 coords = getShadowCoords(i, worldPos); shadowValue += (1.0 - sampleShadowMap(i, coords, SHADOW_BIASES[i])); } return 1.0 - saturate(shadowValue); }
          
          





    最埌に、圱に加えお、ラむティングアルゎリズムからのシェヌディングもあるずいう事実を考慮する必芁がありたす私はBlinn-Fongラむティングモデルを䜿甚したした。 二重シェヌディングを防ぐために、ピクセルシェヌダヌに次のコヌドを远加したした。



     float shadowValue = shadow(input.worldPos); shadowValue = lerp(1, shadowValue, ndol);
          
          





    ここでは、照明モデルに利点がありたす。 Blinn-Fongモデルによるず暗い堎合、圱は远加されたせん。 解決策は理想的であるず䞻匵しおいたせんが、ある皋床は問題を排陀したす。

    その結果、次の図が埗られたす。









OpenGL 4.3の実装


OpenGL 4.3にアルゎリズムを実装するには、Direct3D 11ず同じものがすべお必芁ですが、埮劙な点がありたす。 OpenGLでは、深床倀を含むテクスチャたずえば、圢匏GL_DEPTH_COMPONENT32F



のみの比范ず組み合わせお遞択を行うこずができたす。 したがっお、深床バッファのみをレンダリングし、カラヌ゚ントリを削陀したすより正確には、深床バッファを栌玍するためにフレヌムバッファにテクスチャの配列のみをアタッチしたす。 これは、䞀方で、ビデオメモリを少し節玄し、グラフィックパむプラむンを容易にしたす。他方では、正芏化された深床倀を䜿甚するこずを匷制したす。

OpenGLの遞択オプションは、テクスチャに盎接マッピングできたす。 これらは、以前Direct3D 11で怜蚎されおいたものず同じです。



 const float BORDER_COLOR[] = { 1.0f, 1.0f, 1.0f, 1.0f }; glBindTexture(m_shadowMap->getTargetType(), m_shadowMap->getDepthBuffer()); glTexParameteri(m_shadowMap->getTargetType(), GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(m_shadowMap->getTargetType(), GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(m_shadowMap->getTargetType(), GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); glTexParameteri(m_shadowMap->getTargetType(), GL_TEXTURE_COMPARE_FUNC, GL_LESS); glTexParameteri(m_shadowMap->getTargetType(), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(m_shadowMap->getTargetType(), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glTexParameterfv(m_shadowMap->getTargetType(), GL_TEXTURE_BORDER_COLOR, BORDER_COLOR); glBindTexture(m_shadowMap->getTargetType(), 0);
      
      





興味深いプロセスは、OpenGLの内郚で3次元のテクスチャで衚されるテクスチャの配列の䜜成です。 glTexStorage3D



はそれを䜜成するための特別な関数を䜜成せず、䞡方ずもglTexStorage3D



を䜿甚しお䜜成されたす。 GLSLのSV_RenderTargetArrayIndex



の類䌌物は、組み蟌み倉数gl_Layer



です。

レンダリングスキヌムも同じたたです。

  1. シャドりカヌドの配列をクリアしたす。 フレヌムバッファにはカラヌチャンネルがないため、深床バッファのみをクリアしたす。 正芏化された深床バッファの堎合、最倧倀は1.0になりたす。
  2. シャドりマップをレンダリングしたす。 深床バッファヌのみが圢成されるため、フラグメントシェヌダヌは必芁ありたせん。

    頂点シェヌダヌ
     #version 430 core const int MAX_SPLITS = 4; layout(location = 0) in vec3 position; layout(location = 1) in vec3 normal; layout(location = 2) in vec2 uv0; layout(location = 3) in vec3 tangent; layout(location = 4) in vec3 binormal; out VS_OUTPUT { float depth; flat int instanceID; } vsoutput; uniform mat4 modelMatrix; uniform mat4 shadowViewProjection[MAX_SPLITS]; uniform int shadowIndices[MAX_SPLITS]; void main() { vec4 wpos = modelMatrix * vec4(position, 1); vec4 pos = shadowViewProjection[shadowIndices[gl_InstanceID]] * vec4(wpos.xyz, 1); gl_Position = pos; vsoutput.depth = pos.z; vsoutput.instanceID = gl_InstanceID; }
          
          





    幟䜕孊シェヌダヌ
     #version 430 core const int MAX_SPLITS = 4; layout(triangles) in; layout(triangle_strip, max_vertices = 3) out; in VS_OUTPUT { float depth; flat int instanceID; } gsinput[]; uniform int shadowIndices[MAX_SPLITS]; void main() { for (int i = 0; i < gl_in.length(); i++) { gl_Position = gl_in[i].gl_Position; gl_Layer = shadowIndices[gsinput[i].instanceID]; EmitVertex(); } EndPrimitive(); }
          
          





  3. メむンカメラからシヌンをレンダリングしたす。 シェヌディングを蚈算するずき、䞻なこずは、珟圚のポむントず光源コヌドのcoords.z



    間の距離を範囲[0; 1]に倉換するこずを忘れないこずです。



     vec3 getShadowCoords(int splitIndex, vec3 worldPos) { vec4 coords = shadowViewProjection[splitIndex] * vec4(worldPos, 1); coords.xyz = (coords.xyz / coords.w) * vec3(0.5) + vec3(0.5); return coords.xyz; }
          
          





    その結果、Direct3D 11ずはわずかに異なる画像が埗られたす興味深いため、別の角床から衚瀺したす。







問題



シャドりマッピングアルゎリズムずその修正には倚くの問題がありたす。 倚くの堎合、アルゎリズムは特定のゲヌムたたは特定のシヌンに合わせお慎重に調敎する必芁がありたす。 最も䞀般的な問題ず解決策のリストは、 ここにありたす 。 PSSMを実装するずきに、次のこずに気付きたした。

  1. オブザヌバヌの背埌にあるオブゞェクトからの圱の消倱。 シャドりカヌドは、そのカバレッゞがメむンカメラからの可芖性のピラミッドに可胜な限り䞀臎するように配眮したす。 したがっお、オブザヌバヌの背埌にあるオブゞェクトは、単玔にそれらに萜ちたせん。 投圱ビュヌのシャドりマトリックスを蚈算するアルゎリズムで、ビゞョンベクトルずは反察の方向にシフトを導入するこずにより、このアヌティファクトを平滑化したした。 もちろん、これは日没時の長い圱や高いオブゞェクトからの圱の堎合の問題を解決したせんが。
  2. オブゞェクトの圱をトリミングしたす。 オブゞェクトが十分に倧きい堎合、最初のセグメントのシャドりマップをレンダリングするずきに、オブゞェクトを郚分的に切り取るこずができたす。 これを解決するには、圱がレンダリングされるカメラの䜍眮を調敎し、前の段萜からシフトしたす。 蚭定が倱敗するず、そのようなアヌティファクトを確認できたす。





  3. 停の自己シャドヌむング。 おそらく、シャドりマップの䜿甚から生じる最も有名なグラフィックアヌティファクト。 この問題は、シャドりマップの倀ず蚈算倀を比范するずきに゚ラヌを導入するこずにより、非垞にうたく解決されたす。 実際には、各シャドりマップに個別の゚ラヌ倀を䜿甚する必芁がありたした。 䞋の巊偎の図は、右偎の䞍幞な゚ラヌの遞択を瀺しおいたす-成功した゚ラヌです。





  4. 残念ながら、深さを比范するずきに゚ラヌを導入するこずで誀ったセルフシャドヌむングを排陀するず、Peter Panningず呌ばれる別のアヌティファクトに぀ながりたす「Peter Pan゚フェクト」ずしお倧たかにロシア語に倉換できたす。 本を芚えおいない人のために、ピヌタヌパンの圱は圌の人生を生き、しばしば所有者から逃げたした。 このように芋えたす家の隅の圱が少しずれおいたす。





    このアヌティファクトは、特に耇雑な圢状のオブゞェクトでは特に気づきにくい堎合がありたすが、ほずんど垞にそこにありたす。


性胜



パフォヌマンス枬定は、Windows 8.1を実行するAMD Phenom II X4 970 3.79GHz、16Gb RAM、AMD Radeon HD 7700シリヌズの構成のコンピュヌタヌで実行されたした。



平均フレヌム時間。 Direct3D 11 / 1920x1080 / MSAA 8x /フルスクリヌン/小さなシヌン1フレヌムあたり最倧12kポリゎン、最倧20個のオブゞェクト

分割数/シャドりマップサむズN x Nピクセル 1024 2048 4096
2 4.5546ms 5.07555ms 7.1661ミリ秒
3 5.50837ms 6.18023ms 9.75103ms
4 6.00958ミリ秒 7.23269ms 12.1952ms


平均フレヌム時間。 OpenGL 4.3 / 1920x1080 / MSAA 8x /フルスクリヌン/小さなシヌン1フレヌムあたり〜12kポリゎン、〜20オブゞェクト

分割数/シャドりマップサむズN x Nピクセル 1024 2048 4096
2 3.2095ms 4.05457ms 6.06558ms
3 3.9968ms 4.87389ms 8.65781ms
4 4.68831ms 5.93709ms 10.4345ms


平均フレヌム時間。 4パヌティション/ 1920x1080 / MSAA 8x /フルスクリヌン/倧シヌン1フレヌムあたり〜1000kポリゎン、〜1000オブゞェクト、〜500オブゞェクトむンスタンス

API /シャドりマップサむズN x Nピクセル 1024 2048 4096
Direct3D 11 29.2031ms 33.3434ms 40.5429ms
Opengl 4.3 21.0032ms 26.4095ms 41.8098ms


結果は、倧小のシヌンで、OpenGL 4.3の実装が䞀般に高速に動䜜するこずを瀺したした。 グラフィックパむプラむンの負荷の増加オブゞェクトずそのむンスタンスの数の増加、シャドりマップのサむズの増加により、実装間の䜜業速床の差は小さくなりたす。 OpenGL実装の利点を、Direct3D 11ずは異なるシャドりマップの圢成方法ず関連付けおいたす色に曞き蟌むこずなく深床バッファヌのみを䜿甚したした。 Direct3D 11で同じこずを行うこずを劚げるものは䜕もありたせん。これは、正芏化された深床倀の䜿甚ず調和しおいたす。 ただし、このアプロヌチは、どの远加デヌタたたは深床倀ではなく深床倀の関数をシャドりマップに栌玍するたでは機胜したせん。 たた、アルゎリズムのいく぀かの改善 Variance Shadow Mappingなど は実装が困難です。



結論



PSSMアルゎリズムは、倧きなオヌプンスペヌスに圱を䜜成する最も成功した方法の1぀です。 シンプルで理解しやすい分割原理に基づいおおり、シャドりの品質を増枛するこずで簡単にスケヌリングできたす。 このアルゎリズムを他のシャドりマッピングアルゎリズムず組み合わせお、より矎しい゜フトシャドりたたは物理的に正確な゜フトシャドりを取埗できたす。 同時に、シャドりマッピングクラスのアルゎリズムは、䞍快なグラフィックアヌティファクトの出珟に぀ながるこずが倚く、特定のゲヌム甚にアルゎリズムを埮調敎するこずで、それを陀去する必芁がありたす。



All Articles