Что такое fwidth и как он работает?

18

В документации OpenGL говорится, что fwidth returns the sum of the absolute value of derivatives in x and y.

Что это означает в менее математических терминах, и есть ли способ визуализировать это?

Основываясь на моем понимании функции, fwidth(p)имеет доступ к значению pв соседних пикселях. Как это работает на графическом процессоре без существенного влияния на производительность и работает ли оно надежно и равномерно по всем пикселям?

ApoorvaJ
источник

Ответы:

18

Пиксельные производные экрана пространства делают существенно влияют на производительность, но они влияют на производительность , используете ли вы их или нет, так что с определенной точки зрения они свободны!

Каждый графический процессор в недавней истории упаковывает по четыре пикселя вместе и помещает их в один и тот же деформационный / волновой фронт, что по сути означает, что они работают рядом друг с другом на графическом процессоре, поэтому доступ к значениям из них очень дешев. Поскольку деформации / волновые фронты выполняются в режиме блокировки, остальные пиксели также будут в том же месте в шейдере, что и вы, поэтому значение pэтих пикселей будет просто сидеть в регистре, ожидая вас. Эти остальные три пикселя всегда будут выполняться, даже если их результаты будут отброшены. Таким образом, треугольник, который покрывает один пиксель, всегда затеняет четыре пикселя и отбрасывает результаты трех из них, просто чтобы эти производные функции работали!

Это считается приемлемой стоимостью (для текущего аппаратного обеспечения), потому fwidthчто эти производные используют не только функции, подобные этим: каждый отдельный образец текстуры делает то же самое, чтобы выбрать, из какого mip-карты вашей текстуры читать. Учтите: если вы очень близко к поверхности, у UV-координаты, которую вы используете для выборки текстуры, будет очень малая производная в пространстве экрана, а это означает, что вам нужно использовать большую карту, а если вы находитесь дальше, у UV-координаты будет большая производная в пространстве экрана, что означает, что вам нужно использовать меньший mipmap.

Насколько это означает в менее математических терминах: fwidthэквивалентно abs(dFdx(p)) + abs(dFdy(p)). dFdx(p)просто разница между значением pв пикселе x + 1 и значением pв пикселе x, и аналогично для dFdy(p).

Джон Калсбек
источник
Так что, если dFdx(p) = p(x1) - p(x), то x1может быть либо, (x+1)либо (x-1), в зависимости от положения пикселя xв квадре. В любом случае, x1должен быть в том же варп / волновом фронте, что и x. Я прав?
ApoorvaJ
3
@ApoorvaJ По сути, то же значение для dFdxвычисляется для каждого из 2 соседних пикселей в сетке 2x2. И это значение просто вычисляется с использованием разницы между двумя соседними значениями, если это p(x+1)-p(x)или p(x)-p(x-1)просто зависит от вашего представления о том, что xименно здесь. Результат тот же, однако. Так что да, ты прав.
Крис говорит восстановить Монику
@ChristianRau Это отвечает на мой вопрос. Благодарю.
ApoorvaJ
11

С технической точки зрения fwidth(p)определяется как

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

И dFdx(p)/ dFdy(p)являются частными производными значения pотносительно размеров экрана xи y. Таким образом, они обозначают, как значение pведет себя при переходе на один пиксель вправо ( x) или на один пиксель вверх ( y).

Теперь, как они могут быть практически вычислены? Что ж, если вы знаете значения соседних пикселей для p, вы можете просто вычислить эти производные как прямые конечные разности как приближение для их фактических математических производных (которые могут вообще не иметь точного аналитического решения):

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

Но, конечно, теперь вы можете спросить, как мы можем узнать значения p(которые могут быть любым произвольно вычисленным значением в программе шейдера) для соседних пикселей? Как мы можем вычислить эти значения без больших накладных расходов, выполнив весь шейдерный расчет два (или три) раза?

Ну, вы знаете, что эти соседние значения все равно вычисляются, поскольку для соседнего пикселя вы также запускаете фрагментный шейдер. Так что все, что вам нужно, это доступ к этому соседнему вызову фрагмента шейдера при запуске для соседнего пикселя. Но это даже проще, потому что эти соседние значения также вычисляются в одно и то же время.

Современные растеризаторы называют фрагментные шейдеры в больших тайлах размером более одного соседнего пикселя. Наименьшее это будет сетка пикселей 2х2. И для каждого такого блока пикселей фрагментный шейдер вызывается для каждого пикселя, и эти вызовы выполняются в совершенно параллельном режиме блокировки, так что все вычисления выполняются в точно таком же порядке и в одно и то же время для каждого из этих пикселей в блоке. (именно поэтому также следует избегать ветвления в фрагментном шейдере, хотя и не смертельно, если это возможно, поскольку каждый вызов блока должен был бы исследовать каждую ветвь, которая занята хотя бы одним из вызовов, даже если он просто выбрасывает результаты после, а также указано в ответах на этот связанный вопрос). Таким образом, в любой момент фрагментный шейдер теоретически имеет доступ к значениям фрагментного шейдера соседних пикселей. И пока вы не имеете прямой доступ к этим значениям, вы имеете доступ к значениям вычисленных из них, как и производных функций dFdx, dFdy, fwidth, ...

Крис говорит восстановить Монику
источник