Comment utiliser les dérivés de l'espace d'écran pour anticréneler une forme paramétrique dans un pixel shader?

8

Dans le document Alpha Tested Magnification de Valve , il mentionne l'utilisation de «dérivés d'espace d'écran par pixel» pour effectuer l'anticrénelage. Ma compréhension est que ce sont les fonctions intrinsèques ddxet ddydans HLSL?

J'essaie de dessiner des formes paramétriques (par exemple un cercle: x² + y² <1 ) dans un shader, et je ne sais pas comment utiliser cette technique pour correctement anti-aliaser les pixels de bord de ma forme. Quelqu'un peut-il donner un exemple?

Pour être complet, voici un exemple du type de pixel shader que je fais:

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
la source

Réponses:

10

En prenant votre exemple, vous avez une fonction de pas de la distance, ce qui produit un bord parfaitement dur (aliasé). Un moyen simple d'antialiaser le cercle serait de transformer cela en un seuil souple, comme:

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

Ici, j'ai utilisé une rampe linéaire serrée pour une fonction de seuil doux, mais vous pouvez également utiliser smoothstepou autre chose. Il + 0.5s'agit de centrer la rampe sur l'emplacement mathématique du bord. Quoi qu'il en soit, le point est que cette fonction change en douceur de outsideColorla insideColorsur une plage de distances, donc si vous prenez thresholdWidthcorrectement , vous aurez un avantage crénelées prospectifs.

Mais comment choisir thresholdWidth? S'il est trop petit, vous obtiendrez à nouveau un alias, et s'il est trop grand, le bord sera trop flou. De plus, cela dépendra généralement de la position de la caméra: si elle distest mesurée en unités de l'espace-monde ou de l'espace-texture, alors une thresholdWidthqui fonctionne pour une position de caméra sera fausse pour une autre.

Voici où les dérivés de l'espace d'écran entrent en jeu (oui, ce sont les fonctions ddxet ddycomme vous l'avez deviné). En calculant la longueur du gradient de distvous pouvez vous faire une idée de la vitesse à laquelle il change dans l'espace de l'écran et l'utiliser pour estimer thresholdWidth, comme:

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

Vous avez toujours une valeur que vous pouvez régler pour obtenir le niveau de douceur souhaité, mais maintenant vous devriez obtenir des résultats cohérents quelle que soit la position de la caméra.

Nathan Reed
la source
Bonne réponse - le code fonctionne bien. +1 et accepté. Mais pourrais-je vous déranger de développer votre réponse et d'expliquer pourquoi cela fonctionne? Je suis un peu confus sur ce que derivXet derivYreprésentent en réalité.
Andrew Russell
@AndrewRussell Connaissez-vous les dérivés, comme dans le calcul? derivXet ne derivYsont que (approximations de) les dérivées partielles de l'expression dans laquelle vous passez ddxet ddy, par rapport à l'espace d'écran x et y. Si vous n'avez pas étudié le calcul, c'est un sujet plus vaste que je ne peux l'expliquer ici. :)
Nathan Reed
Mon calcul est un peu rouillé - j'ai dû aller réapprendre ce qu'est une dérivée partielle . Mais à partir de là, je pense que j'ai compris. Merci :)
Andrew Russell
OpenGL ne prend pas en charge ddx / ddy, donc dans ces cas, vous pourriez avoir besoin de ce http.developer.nvidia.com/GPUGems/gpugems_ch25.html qui utilise essentiellement une texture précalculée pour identifier le niveau de mipmap utilisé, pour approximer ddx / ddy.
Runonthespot
2
@Runonthespot OpenGL a des dérivés; ils sont simplement appelés quelque chose d'autre: dFdx/dFdy au lieu de ddx/ ddy.
Nathan Reed