Qu'est-ce que fwidth et comment ça marche?

18

La documentation OpenGL indique que fwidth returns the sum of the absolute value of derivatives in x and y.

Qu'est-ce que cela signifie en termes moins mathématiques et existe-t-il un moyen de le visualiser?

Basé sur ma compréhension de la fonction, fwidth(p)a accès à la valeur de ppixels voisins. Comment cela fonctionne-t-il sur le GPU sans impact drastique sur les performances et fonctionne-t-il de manière fiable et uniforme sur tous les pixels?

ApoorvaJ
la source

Réponses:

18

Dérivés espace écran Pixel font impact sur les performances de manière drastique, mais les performances influent sur votre ou les utiliser pas, donc d'un certain point de vue , ils sont gratuits!

Chaque GPU de l'histoire récente rassemble un quadruple de quatre pixels et les place dans le même warp / front d'onde, ce qui signifie essentiellement qu'ils fonctionnent les uns à côté des autres sur le GPU, donc accéder à leurs valeurs est très bon marché. Étant donné que les déformations / fronts d'onde sont exécutés au même rythme, les autres pixels seront également exactement au même endroit que vous dans le shader, de sorte que la valeur de ppour ces pixels se trouvera simplement dans un registre qui vous attend. Ces trois autres pixels seront toujours exécutés, même si leurs résultats seront jetés. Ainsi, un triangle qui couvre un seul pixel ombrera toujours quatre pixels et jettera les résultats de trois d'entre eux, juste pour que ces fonctionnalités dérivées fonctionnent!

Ceci est considéré comme un coût acceptable (pour le matériel actuel) car ce ne sont pas seulement des fonctions comme fwidthcelles-ci qui utilisent ces dérivés: chaque échantillon de texture le fait également, afin de choisir la mipmap de votre texture à lire. Considérez: si vous êtes très proche d'une surface, la coordonnée UV que vous utilisez pour échantillonner la texture aura une très petite dérivée dans l'espace d'écran, ce qui signifie que vous devez utiliser une mipmap plus grande, et si vous êtes plus loin, la coordonnée UV aura un dérivé plus grand dans l'espace d'écran, ce qui signifie que vous devez utiliser un mipmap plus petit.

Pour ce que cela signifie en termes moins mathématiques: fwidthest équivalent à abs(dFdx(p)) + abs(dFdy(p)). dFdx(p)est simplement la différence entre la valeur de pau pixel x + 1 et la valeur de pau pixel x, et de même pour dFdy(p).

John Calsbeek
la source
Donc, si dFdx(p) = p(x1) - p(x), alors x1peut être soit (x+1)ou (x-1), selon la position du pixel xdans le quad. De toute façon, x1doit être dans le même warp / front d'onde que x. Ai-je raison?
ApoorvaJ
3
@ApoorvaJ Essentiellement, la même valeur pour dFdxest calculée pour chacun des 2 pixels voisins dans la grille 2x2. Et cette valeur est simplement calculée en utilisant la différence entre les deux valeurs voisines, si c'est p(x+1)-p(x)ou p(x)-p(x-1)dépend simplement de votre notion de ce qui xest exactement ici. Le résultat est cependant le même. Alors oui, tu as raison.
Chris dit Réintégrer Monica
@ChristianRau Cela répond à ma question. Merci.
ApoorvaJ
11

En termes entièrement techniques, fwidth(p)est défini comme

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

Et dFdx(p)/ dFdy(p)sont les dérivées partielles de la valeur ppar rapport aux dimensions de l'écran xet y. Ils indiquent donc comment la valeur de pse comporte lorsque l'on va d'un pixel vers la droite ( x) ou d'un pixel vers le haut ( y).

Maintenant, comment peuvent-ils être calculés pratiquement? Eh bien, si vous connaissez les valeurs des pixels voisins pour p, vous pouvez simplement calculer ces dérivés sous forme de différences finies directes comme approximation de leurs dérivées mathématiques réelles (qui pourraient ne pas avoir de solution analytique exacte du tout):

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

Mais bien sûr, vous pouvez maintenant demander, comment pouvons-nous même connaître les valeurs de p(qui pourraient après tout être une valeur calculée arbitrairement à l'intérieur du programme de shader) pour les pixels voisins? Comment calculer ces valeurs sans encourir de frais généraux majeurs en effectuant le calcul du shader deux (ou trois) fois?

Eh bien, vous savez quoi, ces valeurs voisines sont quand même calculées, car pour le pixel voisin, vous exécutez également un shader de fragment. Donc, tout ce dont vous avez besoin est d'accéder à cette invocation de shader de fragment voisin lorsqu'elle est exécutée pour le pixel voisin. Mais c'est encore plus facile, car ces valeurs voisines sont également calculées en même temps.

Les rastérisateurs modernes appellent des shaders de fragments dans de plus grandes tuiles de plus d'un pixel voisin. Au minimum, ce serait une grille 2x2 de pixels. Et pour chacun de ces blocs de pixels, le shader de fragment est invoqué pour chaque pixel et ces invocations s'exécutent en étape de verrouillage parfaitement parallèle afin que tous les calculs soient effectués dans le même ordre exact et au même moment exact pour chacun de ces pixels du bloc. (c'est aussi pourquoi la ramification dans le fragment shader, bien que non mortelle, devrait être évitée si possible, car chaque invocation d'un bloc devrait explorer chaque branche prise par au moins une des invocations, même si elle est simplement jetée les résultats par la suite, comme également abordés dans les réponses à cette question connexe). Donc, à tout moment, un shader de fragment a théoriquement accès aux valeurs de shader de fragment de ses pixels voisins. Et pendant que vous n'avez pas accès direct à ces valeurs, vous avez accès à des valeurs calculées d'eux, comme les fonctions dérivées dFdx, dFdy, fwidth, ...

Chris dit de réintégrer Monica
la source