Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Kaskadierte Schattenkarten (Cascaded Shadow Maps, CSMs) sind die beste Möglichkeit, einen der häufigsten Fehler mit Schatten zu bekämpfen: perspektivische Aliasing. In diesem technischen Artikel, der davon ausgeht, dass der Leser mit der Schattenzuordnung vertraut ist, befasst sich mit dem Thema csMs. Insbesondere:
- erläutert die Komplexität von CSMs;
- gibt Details zu den möglichen Variationen der CSM-Algorithmen;
- beschreibt die beiden am häufigsten verwendeten Filtertechniken : Prozent näherer Filterung (PCF) und Filtern mit Varianzschattenkarten (VSMs);
- identifiziert und behebt einige der allgemeinen Fallstricke, die mit dem Hinzufügen von Filtern zu CSMs verbunden sind; und
- zeigt, wie CSMs direct3D 10 bis Direct3D 11-Hardware zugeordnet werden.
Der in diesem Artikel verwendete Code finden Sie im DirectX Software Development Kit (SDK) in den Beispielen CascadedShadowMaps11 und VarianceShadows11. Dieser Artikel wird sich nach der Implementierung der im technischen Artikel behandelten Techniken Allgemeinen Techniken zur Verbesserung von Schattentiefekartenam nützlichsten erweisen.
Kaskadierte Schattenkarten und perspektivische Aliasierung
Perspektivische Aliase in einer Schattenkarte sind eines der schwierigsten Probleme, die zu überwinden sind. Im technischen Artikel werden allgemeine Techniken zur Verbesserung der Tiefenkarten für Schatten, perspektivische Aliasing beschrieben, und einige Ansätze zur Behebung des Problems werden identifiziert. In der Praxis sind CSMs die beste Lösung und werden häufig in modernen Spielen eingesetzt.
Das grundlegende Konzept von CSMs ist leicht verständlich. Verschiedene Bereiche des Kamera-Frustums erfordern Schattenkarten mit unterschiedlichen Auflösungen. Objekte, die dem Auge am nächsten sind, erfordern eine höhere Auflösung als entfernte Objekte. Wenn sich das Auge sehr nahe an der Geometrie bewegt, kann die Pixel, die dem Auge am nächsten sind, so viel Auflösung erfordern, dass sogar eine Schattenkarte von 4096 × 4096 unzureichend ist.
Die Grundidee von CSMs besteht darin, das Frustum in mehrere Frusta zu unterteilen. Für jedes Unterfrustum wird eine Schattenkarte gerendert; der Pixelshader dann Beispiele aus der Karte, die am ehesten mit der erforderlichen Auflösung übereinstimmt (Abbildung 2).
Abbildung 1. Schattenkartenabdeckung
In Abbildung 1 wird die Qualität (von links nach rechts) von der höchsten bis zur niedrigsten angezeigt. Die Reihe von Rastern, die Schattenkarten mit einem Ansichts-Frustum darstellen (umgekehrter Kegel in Rot), zeigt, wie die Pixelabdeckung mit unterschiedlichen Auflösungsschattenkarten beeinflusst wird. Schatten weisen die höchste Qualität (weiße Pixel) auf, wenn in der Schattenkarte ein Verhältnis von Pixeln mit einem Verhältnis von 1:1 besteht. Perspektivische Aliase treten in Form großer blockischer Texturzuordnungen (linkes Bild) auf, wenn zu viele Pixel dem gleichen Schatten texel zugeordnet werden. Wenn die Schattenkarte zu groß ist, wird sie im Beispiel verwendet. In diesem Fall werden Texel übersprungen, schimmernde Artefakte eingeführt, und die Leistung ist betroffen.
Abbildung 2. CSM-Schattenqualität
Abbildung 2 zeigt Ausschnitte aus dem abschnitt mit höchster Qualität in jeder Schattenkarte in Abbildung 1. Die Schattenkarte mit den am ehesten platzierten Pixeln (am Scheitelpunkt) ist das Auge am nächsten. Technisch gesehen sind dies Karten der gleichen Größe, mit weißer und grauer Verwendung, um den Erfolg der kaskadierten Schattenkarte zu veranzeugen. Weiß ist ideal, da es eine gute Abdeckung zeigt – ein Verhältnis von 1:1 für Augenraumpixel und Schattenkarten-Texel.
CSMs erfordern die folgenden Schritte pro Frame.
Partitionieren Sie das Frustum in subfrusta.
Berechnen einer orthografischen Projektion für jedes Subfrustum.
Rendern einer Schattenkarte für jedes Unterfrustum.
Rendern der Szene.
Binden Sie die Schattenkarten und rendern Sie sie.
Der Vertex-Shader führt folgende Aktionen aus:
- Berechnet Texturkoordinaten für jedes helle Subfrustum (es sei denn, die erforderliche Texturkoordinate wird im Pixelshader berechnet).
- Transformiert und leuchtet den Scheitelpunkt usw.
Der Pixelshader führt folgende Aktionen aus:
- Bestimmt die richtige Schattenkarte.
- Transformiert die Texturkoordinaten bei Bedarf.
- Gibt die Kaskade an.
- Leuchtet das Pixel.
Partitionieren des Frustums
Die Partitionierung des Frustums ist der Akt der Erstellung von Subfrusta. Eine Technik zum Aufteilen des Frustums besteht darin, Intervalle von Null prozent bis zu 10 Prozent in Z-Richtung zu berechnen. Jedes Intervall stellt dann eine nahe Ebene und eine Weitebene als Prozentsatz der Z-Achse dar.
Abbildung 3. Anzeigen von Frustums, die beliebig partitioniert sind
In der Praxis bewirkt die Neuberechnung des Frustumsteilungen pro Frame, dass Schattenränder schimmern. Die allgemein akzeptierte Methode besteht darin, einen statischen Satz von Kaskadenintervallen pro Szenario zu verwenden. In diesem Szenario wird das Intervall entlang der Z-Achse verwendet, um ein Unterfrustum zu beschreiben, das beim Partitionieren des Frustums auftritt. Die Bestimmung der richtigen Größenintervalle für eine bestimmte Szene hängt von mehreren Faktoren ab.
Ausrichtung der Szenengeometrie
Im Hinblick auf die Szenengeometrie wirkt sich die Kameraausrichtung auf die Intervallauswahl aus. Beispielsweise verfügt eine Kamera sehr nahe am Boden, z. B. eine Bodenkamera in einem Fußballspiel, über einen anderen statischen Satz von Kaskadenintervallen als eine Kamera im Himmel.
Abbildung 4 zeigt einige verschiedene Kameras und ihre jeweiligen Partitionen. Wenn der Z-Bereich der Szene sehr groß ist, sind mehr geteilte Ebenen erforderlich. Wenn sich das Auge z. B. in der Nähe der Bodenebene befindet, entfernte Objekte aber immer noch sichtbar sind, können mehrere Kaskaden erforderlich sein. Das Dividieren des Frustums, sodass sich mehr Teilungen in der Nähe des Auges befinden (wo sich perspektivische Aliasing am schnellsten ändert) ebenfalls wertvoll ist. Wenn die meisten Geometrien in einen kleinen Abschnitt (z. B. eine Overheadansicht oder einen Flugsimulator) des Ansichts-Frustums unterteilt sind, sind weniger Kaskaden erforderlich.
Abbildung 4. Unterschiedliche Konfigurationen erfordern unterschiedliche Frustumsteilungen
(Links) Wenn Geometrie einen hohen dynamischen Bereich in Z aufweist, sind viele Kaskaden erforderlich. (Mitte) Wenn die Geometrie einen niedrigen Dynamischen Bereich in Z aufweist, gibt es wenig Vorteile von mehreren Frustums. (Rechts) Es sind nur drei Partitionen erforderlich, wenn der dynamische Bereich mittel ist.
Ausrichtung des Lichts und der Kamera
Die Projektionsmatrix jeder Kaskade ist eng um das entsprechende Subfrustum angepasst. In Konfigurationen, bei denen die Ansichtskamera und die Lichtrichtungen orthogonal sind, können die Kaskaden eng mit wenig Überlappung passen. Die Überlappung wird größer, wenn das Licht und die Ansichtskamera parallel ausgerichtet wird (Abbildung 5). Wenn das Licht und die Ansichtskamera nahezu parallel sind, wird sie als "Dueling Frusta" bezeichnet und ist ein sehr hartes Szenario für die meisten Schattenalgorithmen. Es ist nicht ungewöhnlich, das Licht und die Kamera einzuschränken, damit dieses Szenario nicht auftritt. CSMs führen jedoch in diesem Szenario viel besser aus als viele andere Algorithmen.
Abbildung 5. Die Überlappung nimmt zu, wenn die Lichtrichtung parallel zur Kamerarichtung wird
Viele CSM-Implementierungen verwenden frusta mit fester Größe. Der Pixelshader kann die Z-Tiefe zum Indizieren in das Array von Kaskaden verwenden, wenn das Frustum in Intervallen mit fester Größe geteilt wird.
Berechnen eines gebundenen View-Frustum
Sobald die Frustumintervalle ausgewählt wurden, werden die Subfrusta mit einer von zwei erstellt: Passen Sie an szene und passen Sie an die Kaskade.
An Szene anpassen
Alle Frusta können mit der gleichen nahe Ebene erstellt werden. Dadurch werden die Kaskaden überlappend erzwungen. Im Beispiel "CascadedShadowMaps11" wird diese Technik für die Szene aufgerufen.
An Kaskaden anpassen
Alternativ kann Frusta mit dem tatsächlichen Partitionsintervall erstellt werden, das als Nahe- und Weitebenen verwendet wird. Dies führt zu einer engeren Passform, aber entgeneigt sich, im Falle von Dueling Frusta in Szene zu passen. Die CascadedShadowMaps11-Beispiele rufen diese Technik zur Kaskade auf.
Diese beiden Methoden sind in Abbildung 6 dargestellt. Passen Sie zu Kaskadenabfällen weniger Auflösung. Das Problem bei der Kaskade besteht darin, dass die orthografische Projektion auf der Grundlage der Ausrichtung des Ansichts-Frustums wächst und verkleinern wird. Die Anpassung an szenentechnik pads the orthographic projection by the max size of the view frustum removing the artifacts that appear when the view-camera moves. gängigen Techniken zur Verbesserung der Tiefenkarten für Schatten adressiert die Artefakte, die angezeigt werden, wenn das Licht im Abschnitt "Verschieben des Lichts in Texelgröße in Schritten" bewegt wird.
Abbildung 6. An Szene anpassen und an Kaskade anpassen
Rendern der Schattenkarte
Im Beispiel "CascadedShadowMaps11" werden die Schattenzuordnungen in einem großen Puffer gerendert. Dies liegt daran, dass PCF auf Texturarrays ein Direct3D 10.1-Feature ist. Für jede Kaskade wird ein Viewport erstellt, der den Abschnitt des Tiefenpuffers abdeckt, der dieser Kaskade entspricht. Ein NULL-Pixelshader ist gebunden, da nur die Tiefe benötigt wird. Schließlich werden die richtigen Viewport- und Schattenmatrix für jede Kaskade festgelegt, während die Tiefenkarten jeweils einzeln in den Hauptschattenpuffer gerendert werden.
Rendern der Szene
Der Puffer, der die Schatten enthält, ist jetzt an den Pixelshader gebunden. Es gibt zwei Methoden zum Auswählen der im CascadedShadowMaps11-Beispiel implementierten Kaskade. Diese beiden Methoden werden mit Shadercode erläutert.
Interval-Based Kaskadenauswahl
Abbildung 7. Intervallbasierte Kaskadeauswahl
In der intervallbasierten Auswahl (Abbildung 7) berechnet der Vertex-Shader die Position im Weltbereich des Scheitelpunkts.
Output.vDepth = mul( Input.vPosition, m_mWorldView ).z;
Der Pixelshader empfängt die interpolierte Tiefe.
fCurrentPixelDepth = Input.vDepth;
Die Intervallbasierte Kaskadenauswahl verwendet einen Vektorvergleich und ein Punktprodukt, um die richtige Kaskade zu bestimmen. Die CASCADE_COUNT_FLAG gibt die Anzahl der Kaskaden an. Die m_fCascadeFrustumsEyeSpaceDepths_data schränkt die Ansichts-Frustumpartitionen ein. Nach dem Vergleich enthält das fComparison einen Wert von 1, wobei das aktuelle Pixel größer als die Barriere ist, und einen Wert von 0, wenn die aktuelle Kaskade kleiner ist. Ein Punktprodukt summiert diese Werte in einen Arrayindex.
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;
Sobald die Kaskade ausgewählt ist, muss die Texturkoordinate in die richtige Kaskade transformiert werden.
vShadowTexCoord = mul( InterpolatedPosition, m_mShadow[iCascadeIndex] );
Diese Texturkoordinate wird dann zum Beispiel der Textur mit der X-Koordinate und der Y-Koordinate verwendet. Die Z-Koordinate wird verwendet, um den endgültigen Tiefenvergleich zu erledigen.
Map-Based Kaskadenauswahl
Kartenbasierte Auswahl (Abbildung 8) testet anhand der vier Seiten der Kaskaden, um die strengste Karte zu finden, die das bestimmte Pixel abdeckt. Anstatt die Position im Weltbereich zu berechnen, berechnet der Vertex-Shader die Ansichtsraumposition für jede Kaskade. Der Pixelshader durchläuft die Kaskaden, um die Texturkoordinaten zu skalieren und zu verschieben, sodass sie die aktuelle Kaskade indiziert. Die Texturkoordinate wird dann anhand der Texturgrenzen getestet. Wenn die X- und Y-Werte der Texturkoordinate in eine Kaskade fallen, werden sie zum Beispiel der Textur verwendet. Die Z-Koordinate wird verwendet, um den endgültigen Tiefenvergleich zu erledigen.
Abbildung 8. Kartenbasierte Kaskadenauswahl
Interval-Based Auswahl im Vergleich Map-Based Auswahl
Die intervallbasierte Auswahl ist etwas schneller als die kartenbasierte Auswahl, da die Kaskadenauswahl direkt erfolgen kann. Die kartenbasierte Auswahl muss die Texturkoordinate mit den Begrenzungen überlappen.
Die kartenbasierte Auswahl verwendet die Kaskade effizienter, wenn Schattenkarten nicht perfekt ausgerichtet sind (siehe Abbildung 8).
Vermischung zwischen Kaskaden
VSMs (weiter unten in diesem Artikel erläutert) und Filtertechniken wie PCF können mit csMs mit geringer Auflösung verwendet werden, um weiche Schatten zu erzeugen. Leider führt dies zu einem sichtbaren Naht (Abbildung 9) zwischen Kaskadenschichten, da die Auflösung nicht übereinstimmt. Die Lösung besteht darin, eine Band zwischen Schattenkarten zu erstellen, bei denen der Schattentest für beide Kaskaden durchgeführt wird. Der Shader interpoliert dann linear zwischen den beiden Werten basierend auf der Position des Pixels im Blendband. Die Beispiele CascadedShadowMaps11 und VarianceShadows11 stellen einen GUI-Schieberegler bereit, der verwendet werden kann, um dieses Weichzeichnerband zu vergrößern und zu verringern. Der Shader führt eine dynamische Verzweigung aus, sodass die überwiegende Mehrheit der Pixel nur aus der aktuellen Kaskade gelesen wird.
Abbildung 9. Kaskadensee
(Links) Ein sichtbarer Naht ist zu sehen, wo sich Kaskaden überlappen. (Rechts) Wenn die Kaskaden zwischeneinander vermischungen werden, tritt kein Naht auf.
Filtern von Schattenzuordnungen
PCF
Das Filtern gewöhnlicher Schattenkarten erzeugt keine weichen, verschwommenen Schatten. Die Filterhardware weicht die Tiefenwerte und vergleicht dann diese verschwommenen Werte mit dem Lichtraum texel. Der harte Rand, der sich aus dem Bestanden/Fail-Test ergibt, ist noch vorhanden. Verschwommene Schattenkarten dienen nur dazu, den harten Rand fälschlicherweise zu verschieben. PCF ermöglicht das Filtern auf Schattenkarten. Die allgemeine Idee von PCF besteht darin, einen Prozentsatz des Pixels im Schatten basierend auf der Anzahl der Untersamples zu berechnen, die den Tiefentest über die Gesamtanzahl der Teilsamples bestehen.
Direct3D 10- und Direct3D 11-Hardware kann PCF ausführen. Die Eingabe an einen PCF-Sampler besteht aus der Texturkoordinate und einem Vergleichstiefewert. Der Einfachheit halber wird PCF mit einem Vier-Tipp-Filter erläutert. Der Textur-Sampler liest die Textur viermal, ähnlich einem Standardfilter. Das zurückgegebene Ergebnis ist jedoch ein Prozentsatz der Pixel, die den Tiefentest bestanden haben. Abbildung 10 zeigt, wie ein Pixel, das einen der vier Tiefentests bestanden, 25 Prozent im Schatten beträgt. Der zurückgegebene tatsächliche Wert ist eine lineare Interpolation basierend auf den Untertexelkoordinaten der Textur, die gelesen wird, um einen glatten Farbverlauf zu erzeugen. Ohne diese lineare Interpolation konnte der vierkippige PCF nur fünf Werte zurückgeben: { 0,0, 0,25, 0,5, 0,75, 1,0 }.
Abbildung 10. PCF gefiltertes Bild mit 25 Prozent des ausgewählten Pixels
Es ist auch möglich, PCF ohne Hardwareunterstützung durchzuführen oder PCF auf größere Kernel zu erweitern. Einige Techniken werden sogar mit einem gewichteten Kernel beispielweise verwendet. Erstellen Sie dazu einen Kernel (z. B. einen Gaussian) für ein N-× N-Raster. Die Gewichte müssen bis zu 1 addiert werden. Die Textur wird dann N2-mal pro Beispiel angezeigt. Jedes Beispiel wird durch die entsprechenden Gewichtungen im Kernel skaliert. Im Beispiel "CascadedShadowMaps11" wird dieser Ansatz verwendet.
Tiefenverzerrung
Tiefenverzerrungen werden noch wichtiger, wenn große PCF-Kernel verwendet werden. Es ist nur gültig, um die Lichtraumtiefe eines Pixels mit dem Pixel zu vergleichen, dem es in der Tiefenkarte zugeordnet ist. Die Nachbarn des Tiefenkarten-Texels beziehen sich auf eine andere Position. Diese Tiefe ist wahrscheinlich ähnlich, kann aber je nach Szene sehr unterschiedlich sein. In Abbildung 11 werden die auftretenden Artefakte hervorgehoben. Eine einzelne Tiefe wird mit drei benachbarten Texeln in der Schattenkarte verglichen. Einer der Tiefentests schlägt fehlerhaft fehl, da seine Tiefe nicht mit der berechneten Lichtraumtiefe der aktuellen Geometrie korreliert. Die empfohlene Lösung für dieses Problem besteht darin, einen größeren Offset zu verwenden. Zu groß ein Offset kann jedoch zu Peter Panning führen. Das Berechnen einer engen Nahebene und einer weit entfernten Ebene trägt dazu bei, die Auswirkungen der Verwendung eines Offsets zu reduzieren.
Abbildung 11. Fehlerhafte Selbstschattierung
Die fehlerhafte Selbstschattierung ergibt sich aus dem Vergleich von Pixeln in der Lichtraumtiefe mit den Texeln in der Schattenkarte, die nicht korrelieren. Die Tiefe im Lichtraum korreliert mit Schatten texel 2 in der Tiefenkarte. Texel 1 ist größer als die Lichtraumtiefe, während 2 gleich und 3 kleiner ist. Texel 2 und 3 bestehen den Tiefentest, während Texel 1 fehlschlägt.
Berechnen eines Per-Texel Tiefenverzerrung mit DDX und DDY für große PCFs
Die Berechnung einer Abweichung pro Texeltiefe mit ddx- und ddy- für große PCFs ist eine Technik, mit der die richtige Tiefenverzerrung berechnet wird , vorausgesetzt, die Oberfläche ist planar – für das benachbarte Schattenkarten-Texel.
Diese Technik passt zur Vergleichstiefe zu einer Ebene mit den abgeleiteten Informationen. Da diese Technik rechenintensiv ist, sollte sie nur verwendet werden, wenn eine GPU Rechenzyklen benötigt, um sie zu sparen. Wenn sehr große Kernel verwendet werden, kann dies die einzige Technik sein, die funktioniert, um Selbstschattierungsartefakte zu entfernen, ohne Peter Panning zu verursachen.
Abbildung 12 hebt das Problem hervor. Die Tiefe im Lichtraum ist für das ein Texel bekannt, das verglichen wird. Die Lichtraumtiefen, die den benachbarten Texeln in der Tiefenkarte entsprechen, sind unbekannt.
Abbildung 12. Szenen- und Tiefenkarte
Die gerenderte Szene wird links angezeigt, und die Tiefenkarte mit einem Beispiel-Texelblock wird rechts angezeigt. Das Eye-Space-Texel entspricht dem Pixel mit der Beschriftung D in der Mitte des Blocks. Dieser Vergleich ist genau. Die richtige Tiefe im Blickbereich, die mit den Pixeln korreliert, die der Nachbar D unbekannt ist. Die Zuordnung der benachbarten Texel zum Augenbereich ist nur möglich, wenn wir davon ausgehen, dass sich das Pixel auf das gleiche Dreieck wie D bezieht.
Die Tiefe ist für das Texel bekannt, das mit der Lichtraumposition korreliert. Die Tiefe ist für die benachbarten Texel in der Tiefenkarte unbekannt.
Auf hoher Ebene verwendet diese Technik die ddx- und Ddy- HLSL-Vorgänge, um die Ableitung der Lichtraumposition zu finden. Dies ist nichttrivial, da die abgeleiteten Vorgänge den Farbverlauf der Lichtraumtiefe im Hinblick auf den Bildschirmraum zurückgeben. Um dies in einen Farbverlauf der Lichtraumtiefe im Hinblick auf den Lichtraum umzuwandeln, muss eine Konvertierungsmatrix berechnet werden.
Erläuterung mit Shadercode
Die Details des restlichen Algorithmus werden als Erläuterung des Shadercodes angegeben, der diesen Vorgang ausführt. Dieser Code befindet sich im CascadedShadowMaps11-Beispiel. Abbildung 13 zeigt, wie die Texturkoordinaten für den Lichtraum der Tiefenkarte zugeordnet sind und wie die Ableitungen in X und Y verwendet werden können, um eine Transformationsmatrix zu erstellen.
Abbildung 13. Bildschirmbereich zur Lichtraummatrix
Die Ableitungen der Lichtraumposition in X und Y werden verwendet, um diese Matrix zu erstellen.
Der erste Schritt besteht darin, die Ableitung der Position des Lichtsichtraums zu berechnen.
float3 vShadowTexDDX = ddx (vShadowMapTextureCoordViewSpace);
float3 vShadowTexDDY = ddy (vShadowMapTextureCoordViewSpace);
Direct3D 11-Klassen-GPUs berechnen diese Ableitungen, indem sie 2 × 2 Quad-of-Pixel parallel ausführen und die Texturkoordinaten vom Nachbarn in X für ddx- und vom Nachbarn in Y für ddysubtrahieren. Diese beiden Ableitungen bilden die Zeilen einer 2 × 2-Matrix. In seiner aktuellen Form könnte diese Matrix verwendet werden, um benachbarte Bildschirmraumpixel in Lichtraumhänge zu konvertieren. Die Umkehrung dieser Matrix ist jedoch erforderlich. Eine Matrix, die benachbarte Pixel in Bildschirmbereichshänge transformiert, ist erforderlich.
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 );
Abbildung 14. Lichtraum zum Bildschirmbereich
Diese Matrix wird dann verwendet, um die beiden Texel über und rechts vom aktuellen Texel zu transformieren. Diese Nachbarn werden als Offset vom aktuellen Texel dargestellt.
float2 vRightShadowTexelLocation = float2( m_fTexelSize, 0.0f );
float2 vUpShadowTexelLocation = float2( 0.0f, m_fTexelSize );
float2 vRightTexelDepthRatio = mul( vRightShadowTexelLocation,
matShadowToScreen );
float2 vUpTexelDepthRatio = mul( vUpShadowTexelLocation,
matShadowToScreen );
Das von der Matrix erstellte Verhältnis wird schließlich mit den Tiefenderivaten multipliziert, um die Tiefenversatze für die benachbarten Pixel zu berechnen.
float fUpTexelDepthDelta =
vUpTexelDepthRatio.x * vShadowTexDDX.z
+ vUpTexelDepthRatio.y * vShadowTexDDY.z;
float fRightTexelDepthDelta =
vRightTexelDepthRatio.x * vShadowTexDDX.z
+ vRightTexelDepthRatio.y * vShadowTexDDY.z;
Diese Gewichtungen können jetzt in einer PCF-Schleife verwendet werden, um der Position einen Offset hinzuzufügen.
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 und CSMs
PCF funktioniert nicht für Texturarrays in Direct3D 10. Zur Verwendung von PCF werden alle Kaskaden in einem großen Texturatlas gespeichert.
Derivative-Based Offset
Das Hinzufügen der abgeleiteten basierten Offsets für CSMs stellt einige Herausforderungen dar. Dies liegt an einer abgeleiteten Berechnung innerhalb der divergierenden Flusssteuerung. Das Problem tritt aufgrund einer grundlegenden Funktionsweise von GPUs auf. Direct3D11-GPUs arbeiten mit 2 × 2 Quads von Pixeln. Um eine Ableitung durchzuführen, subtrahieren GPUs im Allgemeinen die Kopie einer Variablen des aktuellen Pixels aus der Kopie des benachbarten Pixels derselben Variablen. Dies variiert von GPU zu GPU. Die Texturkoordinaten werden durch kartenbasierte oder intervallbasierte Kaskadenauswahl bestimmt. Einige Pixel in einem Pixel quad wählen eine andere Kaskade als die restlichen Pixel aus. Dies führt zu sichtbaren Seams zwischen Schattenkarten, da die abgeleiteten Offsets jetzt völlig falsch sind. Die Lösung besteht darin, die abgeleiteten Texturkoordinaten für den Lichtansichtraum durchzuführen. Diese Koordinaten sind für jede Kaskade identisch.
Abstand für PCF-Kernel
PCF Kernels index outside of a cascade partition if the shadow buffer is not padded. Die Lösung besteht darin, den äußeren Rand der Kaskade um eine Hälfte der Größe des PCF-Kernels zu polstern. Dies muss im Shader implementiert werden, der die Kaskade und die Projektionsmatrix auswählt, die die Kaskade groß genug rendern muss, damit der Rahmen erhalten bleibt.
Varianzschattenkarten
VSMs (siehe Abweichung Schattenkarten von Donnelly und Lauritzen für weitere Informationen) aktivieren die direkte Schattenkartenfilterung. Bei Verwendung von VSMs kann die gesamte Leistungsfähigkeit der Texturfilterhardware verwendet werden. Trilinear- und anisotropische Filterung (Abbildung 15) können verwendet werden. Darüber hinaus können VSMs direkt durch Konvolution verschwommen werden. VSMs haben einige Nachteile; Zwei Kanäle mit Tiefendaten müssen gespeichert werden (Tiefe und Tiefe quadratisch). Wenn Schatten sich überschneiden, sind Lichtblutungen üblich. Sie funktionieren jedoch gut mit niedrigeren Auflösungen und können mit CSMs kombiniert werden.
Abbildung 15. Anisotropische Filterung
Algorithmusdetails
VSMs funktionieren, indem die Tiefe und die Tiefe quadratisch in eine Schattenkarte mit zwei Kanälen gerendert werden. Diese Schattenkarte mit zwei Kanälen kann dann wie eine normale Textur verschwommen und gefiltert werden. Der Algorithmus verwendet dann die Ungleichheit von Chebychev im Pixelshader, um den Bruchteil des Pixelbereichs zu schätzen, der den Tiefentest bestanden würde.
Der Pixelshader ruft die Tiefen- und Tiefenquadratwerte ab.
float fAvgZ = mapDepth.x; // Filtered z
float fAvgZ2 = mapDepth.y; // Filtered z-squared
Der Tiefenvergleich wird durchgeführt.
if ( fDepth <= fAvgZ )
{
fPercentLit = 1;
}
Wenn der Tiefenvergleich fehlschlägt, wird der Prozentsatz des Pixels geschätzt, das beleuchtet wird. Die Varianz wird als Mittelwert von Quadraten minus quadratisch berechnet.
float variance = ( fAvgZ2 ) − ( fAvgZ * fAvgZ );
variance = min( 1.0f, max( 0.0f, variance + 0.00001f ) );
Der Wert "fPercentLit" wird mit Chebychevs Ungleichheit geschätzt.
float mean = fAvgZ;
float d = fDepth - mean;
float fPercentLit = variance / ( variance + d*d );
Lichtblutungen
Der größte Nachteil von VSMs ist leichte Blutungen (Abbildung 16). Lichtblutungen treten auf, wenn sich mehrere Schatten casters entlang der Kanten verdecken. VSMs schattiert die Ränder von Schatten auf der Grundlage von Tiefenunterschieden. Wenn Schatten einander überlappen, existiert eine Tiefenunterschiede in der Mitte eines Bereichs, der abgeschattet werden soll. Dies ist ein Problem bei der Verwendung des VSM-Algorithmus.
Abbildung 16. VSM-Lichtblutungen
Eine Teillösung für das Problem besteht darin, den fPercentLit auf eine Leistung zu heben. Dies hat die Auswirkung, den Weichzeichner zu dämpfen, was Artefakte verursachen kann, bei denen die Tiefenunterschiede klein sind. Manchmal gibt es einen magischen Wert, der das Problem mildert.
fPercentLit = pow( p_max, MAGIC_NUMBER );
Eine Alternative zum Erhöhen des Prozentlichts auf eine Leistung besteht darin, Konfigurationen zu vermeiden, bei denen Schatten sich überlappen. Selbst stark abgestimmte Schattenkonfigurationen haben mehrere Einschränkungen für Licht, Kamera und Geometrie. Lichtblutungen werden auch durch die Verwendung von Texturen mit höherer Auflösung verringert.
Layered variance shadow maps (LVSMs) lösen das Problem auf Kosten des Zerbrechens des Frustums in Schichten, die senkrecht zum Licht sind. Die anzahl der erforderlichen Karten wäre ziemlich groß, wenn auch CSMs verwendet werden.
Darüber hinaus diskutierte Andrew Lauritzen, Co-Autor des Papiers auf VSMs und Autor eines Papiers über LVSMs, die Kombination exponentieller Schattenkarten (ESMs) mit VSMs, um der Lichtmischung in einem Beyond3D Forumentgegenwirken.
VSMs mit CSMs
Die BeispielvarianzShadow11 kombiniert VSMs und CSMs. Die Kombination ist ziemlich einfach. Das Beispiel folgt den gleichen Schritten wie das CascadedShadowMaps11-Beispiel. Da PCF nicht verwendet wird, werden die Schatten in einer zweidurchlässigen trennbaren Konvolution verschwommen. Die Verwendung von PCF ermöglicht es dem Beispiel auch, Texturarrays anstelle eines Texturatlas zu verwenden. PCF auf Texturarrays ist ein Direct3D 10.1-Feature.
Farbverläufe mit CSMs
Die Verwendung von Farbverläufen mit CSMs kann eine Naht entlang des Rahmens zwischen zwei Kaskaden erzeugen, wie in Abbildung 17 dargestellt. Die Beispielanweisung verwendet Ableitungen zwischen Pixeln, um Informationen zu berechnen, z. B. die mipmap-Ebene, die vom Filter benötigt werden. Dies verursacht insbesondere bei der Mipmap-Auswahl oder der anisotropen Filterung ein Problem. Wenn Pixel in einem Quad verschiedene Verzweigungen im Shader belegen, sind die von der GPU-Hardware berechneten Ableitungen ungültig. Dies führt zu einem gezackten Naht entlang der Schattenkarte.
Abbildung 17. Naht an Kaskadengrenzen durch anisotrope Filterung mit divergierender Flusssteuerung
Dieses Problem wird gelöst, indem die Ableitungen an der Position im Lichtraum geklärt werden; die Lichtansichtsraumkoordinate ist nicht spezifisch für die ausgewählte Kaskade. Die berechneten Ableitungen können durch den Skalierungsteil der Projektionstexturmatrix auf die richtige Mipmap-Ebene skaliert werden.
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 );
VSMs im Vergleich zu Standardschatten mit PCF
Sowohl VSMs als auch PCF versuchen, den Bruchteil des Pixelbereichs anzunähern, der den Tiefentest bestanden würde. VSMs arbeiten mit Filterhardware und können mit separierbaren Kerneln verschwommen werden. Separierbare Konvolutionskerne sind wesentlich günstiger zu implementieren als ein vollständiger Kernel. Darüber hinaus vergleichen VSMs eine Lichtraumtiefe mit einem Wert in der Tiefenkarte des Lichtraums. Dies bedeutet, dass VSMs nicht dieselben Offsetprobleme haben wie PCF. Technisch gesehen sind VSMs sampling depth over a greater area, sowie performieren eine statistische Analyse. Dies ist weniger präzise als PCF. In der Praxis führen VSMs eine sehr gute Vermischung durch, was dazu führt, dass weniger Offset erforderlich ist. Wie oben beschrieben, ist der Nachteil von VSMs leichte Blutungen.
VSMs und PCF stellen einen Kompromiss zwischen GPU-Computeleistung und GPU-Texturbandbreite dar. VSMs erfordern mehr Mathematik, um die Varianz zu berechnen. PCF erfordert mehr Texturspeicherbandbreite. Große PCF-Kernel können schnell durch Texturbandbreite eng werden. Da die GPU-Berechnungsleistung schneller wächst als die GPU-Bandbreite, werden VSMs immer praktischer für die beiden Algorithmen. VSMs sehen aufgrund der Vermischung und Filterung auch besser mit Schattenkarten mit geringerer Auflösung aus.
Zusammenfassung
CSMs bieten eine Lösung für das Perspektivische Aliasing-Problem. Es gibt mehrere mögliche Konfigurationen, um die erforderliche Genauigkeit für einen Titel zu erhalten. PCF und VSMs werden häufig verwendet und sollten mit CSMs kombiniert werden, um aliasing zu reduzieren.
Referenzen
Donnelly, W. und Lauritzen, A. Abweichung Schattenkarten. In SI3D '06: Proceedings of the 2006 symposium on Interactive 3D graphics and games. 2006. S. 161–165. New York, NY, USA: ACM Press.
Lauritzen, Andrew und McCool, Michael. Ebenenabweichungsschattenzuordnungen. Proceedings of graphics interface 2008, May 28–30, 2008, Windsor, Ontario, Canada.
Engel, Woflgang F. Abschnitt 4. Kaskadierte Schattenkarten. ShaderX5 , Advanced Rendering Techniques, Wolfgang F. Engel, Ed. Charles River Media, Boston, Massachusetts. 2006. S. 197–206.