¿Cómo uso derivados del espacio de pantalla para antialias de una forma paramétrica en un sombreador de píxeles?

8

En el documento Valve's Alpha Tested Magnification , menciona el uso de "derivados de espacio de pantalla por píxel" para hacer suavizado. Mi opinión es que este es el ddxe ddyintrínsecas funciones en HLSL?

Estoy tratando de dibujar formas paramétricas (por ejemplo, un círculo: x² + y² <1 ) en un sombreador, y no sé cómo usar esta técnica para suavizar correctamente los píxeles del borde de mi forma. ¿Alguien puede dar un ejemplo?

Para completar, aquí hay un ejemplo del tipo de sombreador de píxeles que estoy haciendo:

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
fuente

Respuestas:

10

Tomando su ejemplo, tiene una función de paso de la distancia, que produce un borde perfectamente duro (alias). Una forma simple de antialias del círculo sería convertirlo en un umbral blando, como:

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

Aquí utilicé una rampa lineal sujeta para una función de umbral suave, pero también podría usar smoothstepo algo más. El + 0.5es centrar la rampa en la ubicación matemática del borde. De todos modos, el punto es que esta función cambia suavemente desde outsideColorque insideColormás de algún rango de distancias, por lo que si tienes que elegir thresholdWidthadecuadamente obtendrá una ventaja mirando antialiased.

¿Pero cómo debes elegir thresholdWidth? Si es demasiado pequeño, volverá a aparecer el alias, y si es demasiado grande, el borde estará demasiado borroso. Además, generalmente dependerá de la posición de la cámara: si distse mide en unidades de espacio mundial o espacio de textura, entonces una thresholdWidthque funcione para una posición de cámara será incorrecta para otra.

Aquí es donde entran las derivadas del espacio de pantalla (sí, son las funciones ddxy ddycomo lo adivinó). Al calcular la longitud del gradiente dist, puede hacerse una idea de qué tan rápido está cambiando en el espacio de la pantalla y usarlo para estimar thresholdWidth, por ejemplo:

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

Aún tiene un valor que puede ajustar para obtener el nivel deseado de suavidad, pero ahora debería obtener resultados consistentes independientemente de la posición de la cámara.

Nathan Reed
fuente
Buena respuesta: el código funciona bien. +1 y aceptado. ¿Pero podría molestarlo para que amplíe su respuesta y explique por qué esto funciona? Estoy un poco confundido acerca de qué derivXy derivYrealmente representar.
Andrew Russell
@ AndrewRussell ¿Está familiarizado con los derivados, como en el cálculo? derivXy derivYson solo (aproximaciones de) las derivadas parciales de cualquier expresión que ingrese ddxy ddy, con respecto al espacio de pantalla x e y. Si no has estudiado cálculo, ese es un tema más amplio que el que puedo explicar aquí. :)
Nathan Reed
Mi cálculo está un poco oxidado: tuve que ir y volver a aprender qué es una derivada parcial . Pero a partir de ahí creo que lo tengo resuelto. Gracias :)
Andrew Russell
OpenGL no es compatible con ddx / ddy, por lo que en esos casos es posible que necesite este http.developer.nvidia.com/GPUGems/gpugems_ch25.html que básicamente utiliza una textura precalculada para identificar el nivel de mipmap utilizado, para aproximarse a ddx / ddy.
Runonthespot
2
@Runonthespot OpenGL tiene derivados; simplemente se llaman algo más: dFdx/ endFdy lugar de ddx/ ddy.
Nathan Reed