Wie verwende ich Bildschirmraumableitungen, um eine parametrische Form in einem Pixel-Shader zu antialiasieren?

8

In Valves Alpha Tested Magnification- Artikel wird die Verwendung von "Bildschirmableitungen pro Pixel" für das Anti-Aliasing erwähnt. Mein Verständnis ist, dass dies die ddxund ddyintrinsische Funktionen in HLSL sind?

Ich versuche, parametrische Formen (zum Beispiel einen Kreis: x² + y² <1 ) in einem Shader zu zeichnen , und ich weiß nicht, wie ich diese Technik verwenden kann, um die Kantenpixel meiner Form korrekt gegen Alias ​​zu machen. Kann jemand ein Beispiel geben?

Der Vollständigkeit halber hier ein Beispiel für die Art von Pixel-Shader, die ich mache:

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);
}
Andrew Russell
quelle

Antworten:

10

In Ihrem Beispiel haben Sie eine Schrittfunktion der Entfernung, die eine perfekt harte (Alias-) Kante erzeugt. Eine einfache Möglichkeit, den Kreis zu antialiasieren, besteht darin, ihn in eine weiche Schwelle umzuwandeln, wie z.

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);

Hier habe ich eine geklemmte lineare Rampe für eine Soft-Threshold-Funktion verwendet, aber Sie könnten auch smoothstepoder etwas anderes verwenden. Das + 0.5ist, um die Rampe auf der mathematischen Position der Kante zu zentrieren. Der Punkt ist jedenfalls, dass sich diese Funktion reibungslos von outsideColorzu insideColorüber einen bestimmten Bereich von Entfernungen ändert. Wenn Sie also die thresholdWidthrichtige Auswahl treffen, erhalten Sie eine antialias-aussehende Kante.

Aber wie solltest du wählen thresholdWidth? Wenn es zu klein ist, erhalten Sie erneut ein Aliasing, und wenn es zu groß ist, ist die Kante übermäßig verschwommen. Darüber hinaus hängt es im Allgemeinen von der Kameraposition ab: Wenn distes in Weltraum- oder Texturraumeinheiten gemessen wird, ist eine Funktion thresholdWidth, die für eine Kameraposition funktioniert, für eine andere falsch.

Hier kommen die Screen-Space-Ableitungen ins Spiel (ja, sie sind die ddxund ddyfunktionieren, wie Sie vermutet haben). Indem Sie die Länge des Gradienten berechnen dist, können Sie eine Vorstellung davon bekommen, wie schnell sich der Bildschirmbereich ändert, und diese verwenden, um Folgendes zu schätzen 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

Sie haben immer noch einen Wert, den Sie einstellen können, um den gewünschten Weichheitsgrad zu erzielen. Jetzt sollten Sie jedoch unabhängig von der Kameraposition konsistente Ergebnisse erzielen.

Nathan Reed
quelle
Gute Antwort - der Code funktioniert gut. +1 und akzeptiert. Aber könnte ich Sie beunruhigen, Ihre Antwort zu erweitern und zu erklären, warum dies funktioniert? Ich bin ein bisschen verwirrt darüber, was derivXund derivYtatsächlich repräsentieren.
Andrew Russell
@AndrewRussell Kennen Sie sich mit Derivaten wie im Kalkül? derivXund derivYsind nur (Annäherungen an) die partiellen Ableitungen von jedem Ausdruck, in den Sie übergehen, ddxund ddyin Bezug auf den Bildschirmraum x und y. Wenn Sie nicht Kalkül studiert haben, ist das ein größeres Thema, als ich hier erklären kann. :)
Nathan Reed
Mein Kalkül ist etwas rostig - ich musste noch einmal lernen, was eine partielle Ableitung ist. Aber von dort aus habe ich es wohl herausgefunden. Danke :)
Andrew Russell
OpenGL unterstützt ddx / ddy nicht. In diesen Fällen benötigen Sie möglicherweise diese http.developer.nvidia.com/GPUGems/gpugems_ch25.html, die im Grunde eine vorberechnete Textur verwendet, um die verwendete Mipmap-Ebene zu identifizieren und ddx / ddy zu approximieren.
Runonthespot
2
@ Runonthespot OpenGL hat Derivate; Sie heißen einfach etwas anderes: dFdx/dFdy statt ddx/ ddy.
Nathan Reed