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 p
fü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 fwidth
solche 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: fwidth
entspricht abs(dFdx(p)) + abs(dFdy(p))
. dFdx(p)
ist einfach die Differenz zwischen dem Wert von p
bei Pixel x + 1 und dem Wert von p
bei Pixel x und ähnlich für dFdy(p)
.
dFdx(p) = p(x1) - p(x)
, dannx1
kann das entweder sein(x+1)
oder(x-1)
, je nach Position des Pixelsx
im Quad. In beiden Fällenx1
muss sich die Wellenfront in derselben Verzerrung wie befindenx
. Hab ich recht?dFdx
Fü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ängtp(x+1)-p(x)
oderp(x)-p(x-1)
nur davonx
. Das Ergebnis ist jedoch dasselbe. Also ja, du hast recht.Ganz technisch
fwidth(p)
ist definiert alsUnd
dFdx(p)
/dFdy(p)
sind die partiellen Ableitungen des Wertesp
in Bezug auf diex
undy
Bildschirmabmessungen. Sie geben an, wie sich der Wert vonp
verhä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):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
, ...quelle