GLSL: Zentrum oder Schwerpunkt? Oder wenn die Shader angreifen

Als ich den Shader für das kommende Spiel modifizierte, stieß ich auf ein unangenehmes Artefakt, das sich nur beim Einschalten der Hardware-MSAA manifestiert. Im Screenshot der Landschaft sehen Sie einige zu helle Pixel. Die Farbwerte in einigen von ihnen waren so groß, dass sie sich nach dem Blühen in mehrfarbige „Geister“ verwandelten.

Bild

Ich mache Sie auf eine Übersetzung des Artikels aufmerksam, in der der Grund für dieses Phänomen und die Art und Weise, wie damit umgegangen wird, ausführlich erläutert werden.

Bild

Abbildung 1 - Richtige (links) und falsche (rechts) Bilder. Achten Sie auf den gelben Balken am linken Rand des „falschen“ Bildes. Obwohl die Variable myMixer von 0 bis 1 variiert, geht sie im „falschen“ Bild irgendwie über diesen Bereich hinaus.

Stellen Sie sich einen einfachen Fragment-Shader mit einer einfachen nichtlinearen Transformation vor:

smooth in float myMixer; //      . //  sqrt    . void main( void ) { const vec3 blue = vec3( 0.0, 0.0, 1.0 ); const vec3 yellow = vec3( 1.0, 1.0, 0.0 ); float a = sqrt( myMixer ); //    myMixer < 0.0 vec3 color = mix( blue, yellow, a ); //   gl_FragColor = vec4( color, 1.0 ); } 

Woher kommt der gelbe Streifen links im falschen Bild? Um besser zu verstehen, was schief gelaufen ist, schauen wir uns zunächst einen Fall an, in dem (fast) immer alles richtig funktioniert.

Bild

Dies ist eine klassische Rasterung mit einer Probe. Die grauen Quadrate sind Pixel, und die gelben Punkte sind die Zentren der Pixel, die sich bei halbkoordinierten Fensterkoordinaten befinden (standardmäßig sind die Koordinaten des unteren linken Pixels in gl_FragCoord (0,5, 0,5) - trans. ).

Bild

Im obigen Bild trennt die Sekantenlinie den Halbraum des Grundelements. Oberhalb und links von dieser Zeile ist die Variable myMixer positiv und unterhalb und rechts ist negativ.

Die klassische Rasterung mit einer Stichprobe klassifiziert Pixel nach ihrer primitiven Zugehörigkeit und erstellt Fragmente nur für Pixel, deren Zentren innerhalb des Grundelements liegen. In diesem Beispiel werden sechs Fragmente erzeugt, die oben links gezeigt werden. In gedämpfter Farbe markierte Pixel fallen nicht in das Grundelement. Fragmente werden für sie nicht generiert.

Bild

Grün zeigt die Punkte an, an denen der Fragment-Shader berechnet wird. Der Wert von myMixer wird für die Mitte jedes Pixels berechnet. Beachten Sie, dass sich die grünen Punkte über und links von der Linie befinden, sodass die darin enthaltenen myMixer-Werte positiv sind. Alle Eingabedaten, die mit Scheitelpunkten verknüpft sind (variierende oder In / Out-Variablen), werden ebenfalls an diesen Punkten interpoliert.

Unser einfacher Shader verwendet keine Ableitungen (explizit oder implizit, z. B. beim Abtasten aus einer Textur mit Mip-Levels), aber die Ableitungen dFdx (horizontal) und dFdy (vertikal) sind mit Pfeilen markiert. Innerhalb des Primitivs sind sie ziemlich gut definiert und regelmäßig.

Zusammenfassend: In einer einzelnen Auswahl werden Fragmente nur dann erzeugt , wenn die Mitte des Pixels "innerhalb" des Grundelements liegt, die Fragmentdaten für die Mitte des Pixels berechnet werden, die Vertexdateninterpolation und die Shader-Berechnung nur innerhalb des Grundelements durchgeführt werden. Alles ist gut und "richtig". (Fast immer. Lassen Sie vorerst die Ungenauigkeiten einiger Ableitungen auf Pixeln entlang der Grenze des Grundelements weg.)

Bei der Rasterung mit einer Auswahl ist also alles (fast) hervorragend. Aber was kann schief gehen, wenn Multisampling aktiviert ist?

Bild

Dies ist eine klassische Multisampling-Rasterung. Graue Quadrate zeigen Pixel an. Gelbe Punkte sind Pixelzentren in Halbzahlkoordinaten. An blauen Punkten erfolgt die Probenahme. Dieses Beispiel zeigt ein einfaches Diagramm von zwei gedrehten Proben. Alle Argumente können für eine beliebige Anzahl von Stichproben verallgemeinert werden.

Bild

Die Linie trennt immer noch den Halbraum des Grundelements. Oben und links davon ist der Wert von myMixer positiv. Unten und rechts - negativ.

Beim Rasteren mit Multisampling generiert der Pixelklassifizierer ein Fragment, wenn mindestens ein Pixelabtastwert in das Grundelement fällt.

In diesem Beispiel werden 10 Fragmente generiert, die in der oberen linken Halbebene angezeigt werden. Beachten Sie die hinzugefügten vier Fragmente entlang der Fläche, bei denen eine Probe in das Grundelement fällt, obwohl sich das Zentrum außerhalb befindet. Pixel außerhalb des Grundelements sind immer noch dunkel markiert.

Bild

Was passiert bei der Berechnung in der Mitte des Pixels?

Der Shader wird für jedes der Fragmente in grünen und roten Punkten berechnet. Die mit myMixer verknüpften Daten werden in der Mitte jedes Pixels berechnet. In grünen Punkten sind diese Werte positiv, da sie über und links vom Rand liegen. Rote Punkte befinden sich außerhalb des Grundelements, da die Werte von myMixer in ihnen negativ sind. Bei roten Punkten werden zugehörige Daten anstelle der Interpolation extrapoliert.

In unserem Shader werden sqrt-Werte (myMixer) nicht mit einem negativen myMixer definiert. Selbst wenn die vom Vertex-Shader aufgezeichneten myMixer-Werte im Intervall von Null bis Eins liegen, kann myMixer im Fragment-Shader aufgrund der Extrapolation über dieses Intervall hinausgehen. Daher ist bei einem negativen myMixer das Ergebnis des Fragment-Shaders nicht definiert.

Bild

Wir denken immer noch darüber nach, den Shader in der Mitte der Pixel zu berechnen. Die Pfeile in der Abbildung zeigen dFdx und dFdy. Auf den internen Fragmenten des Polygons sind sie ziemlich gut definiert, da alle Berechnungen in der Mitte von Pixeln durchgeführt werden, die sich in gleichen Intervallen befinden.

Bild

Was passiert bei der Berechnung an anderen Punkten als den Pixelmitten?

Die grünen Punkte sind die Punkte, an denen der Shader berechnet wird. Der zugehörige Wert von myMixer wird im Schwerpunkt jedes Pixels berechnet.

Der Schwerpunkt eines Pixels ist der Schwerpunkt des Schnittpunkts des Quadrats des Pixels und des Inneren des Grundelements. Bei einem vollständig abgedeckten Pixel ist der Schwerpunkt das Zentrum. Bei einem teilweise abgedeckten Pixel unterscheidet sich der Schwerpunkt normalerweise von der Mitte.

Der OpenGL-Standard ermöglicht es einer Implementierung, einen beliebigen Punkt am Schnittpunkt eines Grundelements und eines Pixels anstelle eines idealen Schwerpunkts auszuwählen. Zum Beispiel könnte es ein Abtastpunkt sein.

Wenn in diesem Beispiel das Zentrum innerhalb des Grundelements liegt, werden die Scheitelpunktdaten für das Zentrum berechnet. Andernfalls werden sie an einem der im Grundelement liegenden Stichprobenpunkte berechnet. Dies geschieht für vier Pixel entlang des Randes. Alle grünen Punkte liegen über und links vom Rand, sodass die darin enthaltenen Werte immer interpoliert und niemals extrapoliert werden.

Warum nicht immer den Schwerpunkt-Shader berechnen? Im Allgemeinen ist es teurer als das Rechnen im Zentrum. Dies ist jedoch nicht der Hauptfaktor.

Es geht nur darum, Derivate zu berechnen. Achten Sie auf die Pfeile zwischen den grünen Punkten. Der Abstand zwischen ihnen ist für verschiedene Punktpaare nicht gleich. Außerdem ist y für dFdx nicht konstant und x ist für dFdy nicht konstant. Derivate sind weniger genau, wenn sie in Zentroiden berechnet werden .

Dies ist ein Kompromiss, und daher bietet OpenGL ab GLSL 1.20 dem Shader-Entwickler die Wahl zwischen Center und Centroid mithilfe des Centroid-Qualifikators:

 centroid in float myMixer; //  centroid  smooth //      . //  sqrt    . void main( void ) { const vec3 blue = vec3( 0.0, 0.0, 1.0 ); const vec3 yellow = vec3( 1.0, 1.0, 0.0 ); float a = sqrt( myMixer ); //    myMixer < 0.0 vec3 color = mix( blue, yellow, a ); //   gl_FragColor = vec4( color, 1.0 ); } 

Wann sollten Sie den Schwerpunkt verwenden?

  1. Wenn ein extrapolierter Wert zu vagen Ergebnissen führen kann. Achten Sie besonders auf die integrierten Funktionen, deren Beschreibung lautet "Das Ergebnis ist nicht definiert, wenn ...".
  2. Wenn ein extrapolierter Wert mit einer sehr nichtlinearen oder diskontinuierlichen Funktion verwendet wird. Dazu gehört die Schrittfunktion oder die Flare-Berechnung, insbesondere wenn der Exponent groß genug ist.

Wann sollten Sie keinen Schwerpunkt verwenden?

  1. Wenn Sie genaue Ableitungen benötigen. Derivate können entweder explizit (dFdx-Aufruf) oder implizit sein, z. B. Samples aus Texturen mit Mip-Levels oder mit anisotroper Filterung. In der GLSL-Spezifikation werden Derivate in Zentroiden als so unbrauchbar angesehen, dass sie als undefiniert deklariert werden. Versuchen Sie in solchen Fällen zu schreiben:

     centroid in float myMixer; //  ! smooth in float myCenterMixer; //     . 

  2. Wenn ein Raster gerendert wird, in dem die meisten Grenzen der Grundelemente intern und immer gut definiert sind. Das einfachste Beispiel ist ein Streifen mit 100 Dreiecken (TRIANGLE_STRIP), in dem nur das erste und das letzte Dreieck extrapoliert werden. Das Schwerpunktqualifikationsmerkmal führt zu einer Interpolation dieser beiden Dreiecke auf Kosten des Verlusts an Genauigkeit und Kontinuität bei den verbleibenden 98 Dreiecken.
  3. Wenn Sie wissen, dass Artefakte aus einer unbestimmten, nichtlinearen oder diskontinuierlichen Funktion hervorgehen können, erweisen sich diese Artefakte in der Praxis jedoch als nahezu unsichtbar. Wenn der Shader nicht angreift, beheben Sie ihn nicht!

Source: https://habr.com/ru/post/de434046/


All Articles