Пиксельные производные экрана пространства делают существенно влияют на производительность, но они влияют на производительность , используете ли вы их или нет, так что с определенной точки зрения они свободны!
Каждый графический процессор в недавней истории упаковывает по четыре пикселя вместе и помещает их в один и тот же деформационный / волновой фронт, что по сути означает, что они работают рядом друг с другом на графическом процессоре, поэтому доступ к значениям из них очень дешев. Поскольку деформации / волновые фронты выполняются в режиме блокировки, остальные пиксели также будут в том же месте в шейдере, что и вы, поэтому значение 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
. Я прав?dFdx
вычисляется для каждого из 2 соседних пикселей в сетке 2x2. И это значение просто вычисляется с использованием разницы между двумя соседними значениями, если этоp(x+1)-p(x)
илиp(x)-p(x-1)
просто зависит от вашего представления о том, чтоx
именно здесь. Результат тот же, однако. Так что да, ты прав.С технической точки зрения
fwidth(p)
определяется какИ
dFdx(p)
/dFdy(p)
являются частными производными значенияp
относительно размеров экранаx
иy
. Таким образом, они обозначают, как значениеp
ведет себя при переходе на один пиксель вправо (x
) или на один пиксель вверх (y
).Теперь, как они могут быть практически вычислены? Что ж, если вы знаете значения соседних пикселей для
p
, вы можете просто вычислить эти производные как прямые конечные разности как приближение для их фактических математических производных (которые могут вообще не иметь точного аналитического решения):Но, конечно, теперь вы можете спросить, как мы можем узнать значения
p
(которые могут быть любым произвольно вычисленным значением в программе шейдера) для соседних пикселей? Как мы можем вычислить эти значения без больших накладных расходов, выполнив весь шейдерный расчет два (или три) раза?Ну, вы знаете, что эти соседние значения все равно вычисляются, поскольку для соседнего пикселя вы также запускаете фрагментный шейдер. Так что все, что вам нужно, это доступ к этому соседнему вызову фрагмента шейдера при запуске для соседнего пикселя. Но это даже проще, потому что эти соседние значения также вычисляются в одно и то же время.
Современные растеризаторы называют фрагментные шейдеры в больших тайлах размером более одного соседнего пикселя. Наименьшее это будет сетка пикселей 2х2. И для каждого такого блока пикселей фрагментный шейдер вызывается для каждого пикселя, и эти вызовы выполняются в совершенно параллельном режиме блокировки, так что все вычисления выполняются в точно таком же порядке и в одно и то же время для каждого из этих пикселей в блоке. (именно поэтому также следует избегать ветвления в фрагментном шейдере, хотя и не смертельно, если это возможно, поскольку каждый вызов блока должен был бы исследовать каждую ветвь, которая занята хотя бы одним из вызовов, даже если он просто выбрасывает результаты после, а также указано в ответах на этот связанный вопрос). Таким образом, в любой момент фрагментный шейдер теоретически имеет доступ к значениям фрагментного шейдера соседних пикселей. И пока вы не имеете прямой доступ к этим значениям, вы имеете доступ к значениям вычисленных из них, как и производных функций
dFdx
,dFdy
,fwidth
, ...источник