Was ist fwidth und wie funktioniert es?

18

Die OpenGL-Dokumentation gibt diese Breite an returns the sum of the absolute value of derivatives in x and y.

Was bedeutet das in weniger mathematischen Begriffen und gibt es eine Möglichkeit, es zu visualisieren?

Basierend auf meinem Verständnis der Funktion, fwidth(p)hat der Zugriff auf den Wert pin benachbarten Pixeln. Wie funktioniert dies auf der GPU, ohne die Leistung drastisch zu beeinträchtigen, und funktioniert es zuverlässig und gleichmäßig über alle Pixel hinweg?

ApoorvaJ
quelle

Antworten:

18

Pixel-Screen-Space-Derivate wirken sich drastisch auf die Leistung aus, wirken sich jedoch auf die Leistung aus, unabhängig davon, ob Sie sie verwenden oder nicht. Unter bestimmten Gesichtspunkten sind sie also kostenlos!

Jede GPU in der jüngeren Vergangenheit packt ein Quad von vier Pixeln zusammen und ordnet sie derselben Verzerrung / Wellenfront zu. Dies bedeutet im Wesentlichen, dass sie auf der GPU direkt nebeneinander ausgeführt werden, sodass der Zugriff auf Werte von ihnen sehr kostengünstig ist. Da Warps / Wellenfronten im Gleichschritt ausgeführt werden, befinden sich auch die anderen Pixel genau an der gleichen Stelle im Shader wie Sie, sodass der Wert pfür diese Pixel nur in einem Register auf Sie wartet. Diese anderen drei Pixel werden immer ausgeführt, auch wenn ihre Ergebnisse weggeworfen werden. Ein Dreieck, das ein einzelnes Pixel abdeckt, schattiert also immer vier Pixel und wirft die Ergebnisse von drei davon weg, nur damit diese abgeleiteten Funktionen funktionieren!

Dies wird als akzeptabler Preis angesehen (für aktuelle Hardware), da nicht nur fwidthsolche Funktionen diese Derivate verwenden: Jedes einzelne Textur-Sample tut dies auch, um die Mipmap Ihrer Textur auszuwählen, aus der es gelesen werden soll. Bedenken Sie: Wenn Sie sich sehr nahe an einer Oberfläche befinden, weist die UV-Koordinate, die Sie zum Abtasten der Textur verwenden, eine sehr kleine Ableitung im Bildschirmbereich auf, was bedeutet, dass Sie eine größere Mipmap verwenden müssen eine größere Ableitung im Bildschirmbereich, was bedeutet, dass Sie eine kleinere Mipmap verwenden müssen.

Soweit es weniger mathematisch bedeutet: fwidthentspricht abs(dFdx(p)) + abs(dFdy(p)). dFdx(p)ist einfach die Differenz zwischen dem Wert von pbei Pixel x + 1 und dem Wert von pbei Pixel x und ähnlich für dFdy(p).

John Calsbeek
quelle
Also, wenn ja dFdx(p) = p(x1) - p(x), dann x1kann das entweder sein (x+1)oder (x-1), je nach Position des Pixels xim Quad. In beiden Fällen x1muss sich die Wellenfront in derselben Verzerrung wie befinden x. Hab ich recht?
ApoorvaJ
3
@ApoorvaJ dFdxFür jedes der 2 benachbarten Pixel im 2x2-Raster wird im Wesentlichen der gleiche Wert für berechnet. Und dieser Wert wird nur anhand der Differenz zwischen den beiden Nachbarwerten berechnet, ob dies von Ihrer Vorstellung, was genau hier ist, abhängt p(x+1)-p(x)oder p(x)-p(x-1)nur davon x. Das Ergebnis ist jedoch dasselbe. Also ja, du hast recht.
Chris sagt Reinstate Monica
@ ChristianRau Das beantwortet meine Frage. Vielen Dank.
ApoorvaJ
11

Ganz technisch fwidth(p)ist definiert als

fwidth(p) := abs(dFdx(p)) + abs(dFdy(p))

Und dFdx(p)/ dFdy(p)sind die partiellen Ableitungen des Wertes pin Bezug auf die xund yBildschirmabmessungen. Sie geben an, wie sich der Wert von pverhält, wenn ein Pixel nach rechts ( x) oder ein Pixel nach oben ( y) verschoben wird .

Wie können sie nun praktisch berechnet werden? Nun, wenn Sie die Werte der Nachbarpixel kennen p, können Sie diese Ableitungen einfach als direkte endliche Differenzen berechnen, als Näherung für ihre tatsächlichen mathematischen Ableitungen (die möglicherweise überhaupt keine exakte analytische Lösung haben):

dFdx(p) := p(x+1) - p(x)

Aber jetzt fragen Sie sich vielleicht, woher wir überhaupt die Werte von p(die im Shader-Programm auch willkürlich berechnet werden können) für die benachbarten Pixel kennen. Wie berechnen wir diese Werte, ohne dass ein erheblicher Overhead entsteht, indem wir die gesamte Shader-Berechnung zwei (oder drei) Mal durchführen?

Weißt du was? Diese Nachbarwerte werden sowieso berechnet, da für das Nachbarpixel auch ein Fragment-Shader ausgeführt wird. Alles, was Sie benötigen, ist der Zugriff auf diesen benachbarten Fragment-Shader-Aufruf, wenn er für das benachbarte Pixel ausgeführt wird. Aber es ist noch einfacher, weil diese Nachbarwerte genau zur gleichen Zeit berechnet werden.

Moderne Rasterizer bezeichnen Fragment-Shader in größeren Kacheln mit mehr als einem benachbarten Pixel. Im kleinsten Fall wäre dies ein 2 × 2-Pixelraster. Und für jeden solchen Pixelblock wird der Fragment-Shader für jedes Pixel aufgerufen, und diese Aufrufe werden in perfekt parallelen Verriegelungsschritten ausgeführt, so dass alle Berechnungen für jedes dieser Pixel im Block in genau derselben Reihenfolge und genau zur gleichen Zeit ausgeführt werden (Das ist auch der Grund, warum Verzweigungen im Fragment-Shader vermieden werden sollten, auch wenn sie nicht tödlich sind, da jeder Aufruf eines Blocks jeden Zweig untersuchen müsste, den mindestens einer der Aufrufe einnimmt, selbst wenn er nur wegwirft die Ergebnisse danach, wie auch in den Antworten auf diese verwandte Frage angesprochen). Ein Fragment-Shader kann also theoretisch jederzeit auf die Fragment-Shader-Werte seiner Nachbarpixel zugreifen. Und während Sie keinen direkten Zugriff auf diese Werte verfügen, haben Sie Zugriff auf Werte von ihnen berechnet, wie die Ableitungsfunktionen dFdx, dFdy, fwidth, ...

Chris sagt Reinstate Monica
quelle