Как использовать производные пространства экрана для сглаживания параметрической фигуры в пиксельном шейдере?

8

В документе Valve Alpha Tested Magnification упоминается использование «производных пространства экрана на пиксель» для сглаживания. Я понимаю, что это ddxи есть ddyвстроенные функции в HLSL?

Я пытаюсь нарисовать параметрические фигуры (например, круг: x² + y² <1 ) в шейдере, и я не знаю, как использовать эту технику для правильного сглаживания краевых пикселей моей фигуры. Может кто-нибудь привести пример?

Для полноты вот пример того, какой пиксельный шейдер я делаю:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    float dist = input.TexCoord.x * input.TexCoord.x
               + input.TexCoord.y * input.TexCoord.y;
    if(dist < 1)
        return float4(0, 0, 0, 1);
    else
        return float4(1, 1, 1, 1);
}
Эндрю Рассел
источник

Ответы:

10

Если взять ваш пример, у вас есть ступенчатая функция расстояния, которая дает совершенно твердый (псевдоним) край. Простой способ сгладить круг - это превратить его в мягкий порог, например:

float distFromEdge = 1.0 - dist;  // positive when inside the circle
float thresholdWidth = 0.01;  // a constant you'd tune to get the right level of softness
float antialiasedCircle = saturate((distFromEdge / thresholdWidth) + 0.5);
return lerp(outsideColor, insideColor, antialiasedCircle);

Здесь я использовал фиксированный линейный уклон для функции мягкого порога, но вы также можете использовать smoothstepили что-то еще. + 0.5Является центр по трапу на математическом месте края. Во всяком случае, дело в том , что эта функция плавно изменяется от outsideColorдо insideColorв некотором диапазоне расстояний, так что если вы выбираете thresholdWidthсоответственно вы получите сглаженные красивый край.

Но как выбрать thresholdWidth? Если он слишком мал, вы снова получите псевдонимы, а если он будет слишком большим, то края будут чрезмерно размытыми. Более того, обычно это зависит от положения камеры: если distизмеряется в единицах измерения мирового пространства или пространства текстуры, то a, thresholdWidthкоторый работает для одной позиции камеры, будет неправильным для другой.

Вот где появляются производные экранного пространства (да, они ddxи ddyфункции, как вы уже догадались). Рассчитав длину градиента, distвы можете получить представление о том, как быстро он изменяется в пространстве экрана, и использовать его для оценки thresholdWidth, например:

float derivX = ddx(distFromEdge);
float derivY = ddy(distFromEdge);
float gradientLength = length(float2(derivX, derivY));
float thresholdWidth = 2.0 * gradientLength;  // the 2.0 is a constant you can tune

У вас все еще есть значение, которое вы можете настроить, чтобы получить желаемый уровень мягкости, но теперь вы должны получать стабильные результаты независимо от положения камеры.

Натан Рид
источник
Хороший ответ - код работает хорошо. +1 и принято. Но могу ли я побеспокоить вас, чтобы вы расширили свой ответ и объяснили, почему это работает? Я немного запутался в том, что derivXи на derivYсамом деле представляю.
Эндрю Рассел
@AndrewRussell Вы знакомы с производными, как в исчислении? derivXи derivYявляются просто (приблизительными) частными производными любого выражения, в которое вы переходите, ddxи ddy, относительно экранного пространства x и y. Если вы не изучали исчисление, это большая тема, чем я могу объяснить здесь. :)
Натан Рид
Мое исчисление немного ржаво - мне пришлось пойти и заново изучить, что такое частная производная. Но оттуда я думаю, что понял это. Спасибо :)
Эндрю Рассел
OpenGL не поддерживает ddx / ddy, поэтому в этих случаях вам может понадобиться этот http.developer.nvidia.com/GPUGems/gpugems_ch25.html, который в основном использует предварительно вычисленную текстуру для определения используемого уровня mipmap, чтобы приблизить ddx / ddy.
Runonthespot
2
@Runonthespot OpenGL имеет производные; они просто называются как-то иначе: dFdx/dFdy вместо ddx/ ddy.
Натан Рид