Pochodne pikseli na ekranie mają ogromny wpływ na wydajność, ale wpływają na wydajność, niezależnie od tego, czy ich używasz, czy nie, więc z pewnego punktu widzenia są one bezpłatne!
Każda karta graficzna w najnowszej historii zawiera cztery piksele razem i umieszcza je w tym samym warp / wavefront, co w zasadzie oznacza, że działają one obok siebie na GPU, więc dostęp do nich jest bardzo tani. Ponieważ osnowy / czoła fali są uruchamiane w trybie blokowania, pozostałe piksele również będą dokładnie w tym samym miejscu w module cieniującym, co Ty, więc wartość p
tych pikseli będzie po prostu czekała na ciebie w rejestrze. Te trzy pozostałe piksele będą zawsze wykonywane, nawet jeśli ich wyniki zostaną wyrzucone. Tak więc trójkąt obejmujący pojedynczy piksel zawsze będzie zacieniał cztery piksele i odrzucał wyniki trzech z nich, tak aby te funkcje pochodne działały!
Jest to uważane za akceptowalny koszt (dla obecnego sprzętu), ponieważ nie tylko takie funkcje fwidth
wykorzystują te pochodne: każda pojedyncza próbka tekstury również, aby wybrać, z której mipmapy tekstury należy odczytać. Zastanów się: jeśli jesteś bardzo blisko powierzchni, współrzędna UV, której używasz do próbkowania tekstury, będzie miała bardzo małą pochodną w przestrzeni ekranu, co oznacza, że musisz użyć większej mipmapy, a jeśli jesteś dalej, współrzędna UV będzie miała większa pochodna w przestrzeni ekranu, co oznacza, że musisz użyć mniejszej mipmapy.
O ile oznacza to mniej matematycznie: fwidth
jest równoważne abs(dFdx(p)) + abs(dFdy(p))
. dFdx(p)
jest po prostu różnicą między wartością p
w pikselu x + 1 a wartością p
w pikselu x i podobnie dla dFdy(p)
.
dFdx(p) = p(x1) - p(x)
, tox1
może być albo,(x+1)
albo(x-1)
, w zależności od pozycji pikselax
w kwadracie. Tak czy inaczej,x1
musi być w tej samej warp / wavefront cox
. Mam rację?dFdx
jest obliczana dla każdego z 2 sąsiednich pikseli w siatce 2x2. Ta wartość jest po prostu obliczana na podstawie różnicy między dwiema wartościami sąsiadów, jeśli tak jestp(x+1)-p(x)
lubp(x)-p(x-1)
zależy od twojego pojęcia, cox
dokładnie jest tutaj. Rezultat jest jednak taki sam. Więc tak, masz rację.Z technicznego punktu widzenia
fwidth(p)
jest zdefiniowany jakoI
dFdx(p)
/dFdy(p)
są częściowymi pochodnymi wartościp
w odniesieniu do wymiarówx
iy
ekranu. Oznacza to więc, jakp
zachowuje się wartość parametru, gdy przesuwa się jeden piksel w prawo (x
) lub jeden piksel w górę (y
).Jak można je praktycznie obliczyć? Cóż, jeśli znasz wartości sąsiednich pikseli
p
, możesz po prostu obliczyć te pochodne jako bezpośrednie różnice skończone jako przybliżenie ich rzeczywistych pochodnych matematycznych (które mogą nie mieć w ogóle dokładnego rozwiązania analitycznego):Ale oczywiście teraz możesz zapytać, skąd w ogóle znamy wartości
p
(które mogą być dowolną dowolnie obliczoną wartością w programie modułu cieniującego) dla sąsiednich pikseli? Jak obliczyć te wartości bez ponoszenia dużego obciążenia, wykonując obliczenia całego modułu cieniującego dwa (lub trzy) razy?Cóż, wiesz co, te sąsiednie wartości są i tak obliczane, ponieważ dla sąsiedniego piksela uruchamiasz również moduł cieniujący fragmenty. Tak więc wszystko, czego potrzebujesz, to dostęp do wywołania tego modułu cieniującego fragmenty przy uruchomieniu dla sąsiedniego piksela. Ale jest to jeszcze łatwiejsze, ponieważ te sąsiednie wartości są również obliczane dokładnie w tym samym czasie.
Nowoczesne rasteryzatory nazywają shadery fragmentów w większych kafelkach więcej niż jednego sąsiedniego piksela. W najmniejszym przypadku byłaby to siatka pikseli 2x2. I dla każdego takiego bloku pikseli wywoływany jest moduł cieniujący fragmenty dla każdego piksela, a te wywołania przebiegają w idealnie równoległym kroku blokowania, dzięki czemu wszystkie obliczenia są wykonywane w dokładnie tej samej kolejności i w tym samym czasie dla każdego z tych pikseli w bloku (dlatego też rozgałęzienie w module cieniującym fragmenty, chociaż nie jest zabójcze, należy unikać, jeśli to możliwe, ponieważ każde wywołanie bloku musiałoby zbadać każdą gałąź, która jest pobierana przez co najmniej jedno wywołanie, nawet jeśli po prostu wyrzuca) wyniki później, jak podano również w odpowiedziach na to powiązane pytanie). Tak więc w dowolnym momencie moduł cieniujący fragment teoretycznie ma dostęp do wartości modułu cieniującego fragmentu sąsiednich pikseli. A gdy nie mają bezpośredniego dostępu do tych wartości, masz dostęp do wartościami obliczonymi z nich, podobnie jak funkcje pochodnych
dFdx
,dFdy
,fwidth
, ...źródło