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 p
par rapport aux dimensions de l'écran x
et y
. Ils indiquent donc comment la valeur de p
se 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
, ...
dFdx(p) = p(x1) - p(x)
, alorsx1
peut être soit(x+1)
ou(x-1)
, selon la position du pixelx
dans le quad. De toute façon,x1
doit être dans le même warp / front d'onde quex
. Ai-je raison?