In Valve's Alpha Tested Magnification paper, it mentions using "per-pixel screen-space derivatives" for doing anti-aliasing. My understanding is that this is the ddx and ddy intrinsic functions in HLSL?

I'm trying to draw parametric shapes (for example a circle: x² + y² < 1) in a shader, and I don't know how to use this technique to correctly anti-alias the edge pixels of my shape. Can someone provide an example?

For completeness, here is an example of the kind of pixel shader I'm making:

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);
        return float4(1, 1, 1, 1);

1 Answer 1


Taking your example, you have a step function of the distance, which produces a perfectly hard (aliased) edge. A simple way to antialias the circle would be to turn that into a soft threshold, like:

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

Here I used a clamped linear ramp for a soft threshold function, but you could also use smoothstep or something else. The + 0.5 is to center the ramp on the mathematical location of the edge. Anyway, the point is that this function smoothly changes from outsideColor to insideColor over some range of distances, so if you pick thresholdWidth appropriately you'll get an antialiased-looking edge.

But how should you choose thresholdWidth? If it's too small, you'll get aliasing again, and if it's too large the edge will be overly blurry. Moreover, it'll generally depend on camera position: if dist is measured in world-space or texture-space units, then a thresholdWidth that works for one camera position will be wrong for another.

Here's where the screen-space derivatives come in (yes, they're the ddx and ddy functions as you guessed). By calculating the length of the gradient of dist you can get an idea how rapidly it's changing in screen space and use that to estimate the thresholdWidth, like:

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

You still have a value you can tune to get the desired level of softness, but now you should get consistent results regardless of camera position.

  • \$\begingroup\$ Good answer - the code works nicely. +1 and accepted. But could I trouble you to expand on your answer and explain why this works? I'm a bit confused about what derivX and derivY actually represent. \$\endgroup\$ Commented Aug 20, 2012 at 7:55
  • \$\begingroup\$ @AndrewRussell Are you familiar with derivatives, as in calculus? derivX and derivY are just (approximations of) the partial derivatives of whatever expression you pass into ddx and ddy, with respect to screen-space x and y. If you haven't studied calculus, that is a bigger topic than I can explain here. :) \$\endgroup\$ Commented Aug 20, 2012 at 16:28
  • \$\begingroup\$ My calculus is a bit rusty - I had to go and re-learn what a partial derivative is. But from there I think I've got it figured out. Thanks :) \$\endgroup\$ Commented Aug 21, 2012 at 6:59
  • \$\begingroup\$ OpenGL doesn't support ddx/ddy so in those cases you might need this http.developer.nvidia.com/GPUGems/gpugems_ch25.html which basically uses a precomputed texture to identify the mipmap level used, to approximate ddx/ddy. \$\endgroup\$ Commented Sep 9, 2013 at 14:39
  • 2
    \$\begingroup\$ @Runonthespot OpenGL has derivatives; they're just called something else: dFdx/dFdy instead of ddx/ddy. \$\endgroup\$ Commented Sep 9, 2013 at 16:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .