Optimize and better document the exact antialiasing calculation

This commit is contained in:
Patrick Walton 2017-11-14 17:10:48 -08:00
parent ca0387d355
commit 8c1b3e9cb5
1 changed files with 42 additions and 53 deletions

View File

@ -205,103 +205,92 @@ bool computeECAAQuadPosition(out vec2 outPosition,
} }
bool slopeIsNegative(vec2 dp) { bool slopeIsNegative(vec2 dp) {
return dp.y < -0.001; return dp.y < 0.0;
}
bool slopeIsZero(vec2 dp) {
return abs(dp.y) < 0.001;
} }
vec2 clipToPixelBounds(vec2 p0, vec2 clipToPixelBounds(vec2 p0,
vec2 dp, vec2 dP,
vec2 center, vec2 center,
out vec4 outQ, out float outPixelTop,
out vec4 outPixelExtents, out vec2 outLeftIntercept,
out vec2 outSpanP0, out float outHorizontalSpan) {
out vec2 outSpanP1) {
// Determine the bounds of this pixel. // Determine the bounds of this pixel.
vec4 pixelExtents = center.xxyy + vec4(-0.5, 0.5, -0.5, 0.5); vec4 pixelSides = center.xxyy + vec4(-0.5, 0.5, -0.5, 0.5);
// Set up Liang-Barsky clipping. // Set up Liang-Barsky clipping.
vec4 q = pixelExtents - p0.xxyy; vec4 q = pixelSides - p0.xxyy;
// Use Liang-Barsky to clip to the left and right sides of this pixel. // Use Liang-Barsky to clip to the sides of the pixel.
vec2 t = clamp(q.xy / dp.xx, 0.0, 1.0); //
vec2 spanP0 = p0 + dp * t.x, spanP1 = p0 + dp * t.y; // These can be yield -Infinity or -Infinity in the case of horizontal lines, but the math all
// works out in the end.
vec2 tX = clamp(q.xy / dP.xx, 0.0, 1.0);
vec2 tY = (slopeIsNegative(dP) ? q.wz : q.zw) / dP.yy;
vec2 t = vec2(max(tX.x, tY.x), min(tX.y, tY.y));
// Likewise, clip to the to the bottom and top. outPixelTop = pixelSides.w;
if (!slopeIsZero(dp)) { outLeftIntercept = p0 + dP * tX.x;
vec2 tVertical = (slopeIsNegative(dp) ? q.wz : q.zw) / dp.yy; outHorizontalSpan = dP.x * (tX.y - tX.x);
t = vec2(max(t.x, tVertical.x), min(t.y, tVertical.y));
}
outQ = q;
outPixelExtents = pixelExtents;
outSpanP0 = spanP0;
outSpanP1 = spanP1;
return t; return t;
} }
bool lineDoesNotPassThroughPixel(vec2 p0, vec2 dp, vec2 t, vec4 pixelExtents) { bool lineDoesNotPassThroughPixel(vec2 t) {
return t.x >= t.y || (slopeIsZero(dp) && (p0.y < pixelExtents.z || p0.y > pixelExtents.w)); return t.x >= t.y;
} }
// Computes the area of the polygon covering the pixel with the given boundaries. // Computes the area of the polygon covering the pixel with the given boundaries.
// //
// * `p0` is the start point of the line. // * `p0` is the start point of the line.
// * `dp` is the vector from the start point to the endpoint of the line. // * `dP` is the vector from the start point to the endpoint of the line.
// * `center` is the center of the pixel in window coordinates (i.e. `gl_FragCoord.xy`). // * `center` is the center of the pixel in window coordinates (i.e. `gl_FragCoord.xy`).
// * `winding` is the winding number (1 or -1). // * `winding` is the winding number (1 or -1).
float computeCoverage(vec2 p0, vec2 dp, vec2 center, float winding) { float computeCoverage(vec2 p0, vec2 dP, vec2 center, float winding) {
// Clip to the pixel bounds. // Clip to the pixel bounds.
vec4 q, pixelExtents; vec2 leftIntercept;
vec2 spanP0, spanP1; float horizontalSpan, pixelTop;
vec2 t = clipToPixelBounds(p0, dp, center, q, pixelExtents, spanP0, spanP1); vec2 t = clipToPixelBounds(p0, dP, center, pixelTop, leftIntercept, horizontalSpan);
// If the line doesn't pass through this pixel, detect that and bail. // If the line doesn't pass through this pixel, detect that and bail.
// //
// This should be worth a branch because it's very common for fragment blocks to all hit this // This should be worth a branch because it's very common for fragment blocks to all hit this
// path. // path.
if (lineDoesNotPassThroughPixel(p0, dp, t, pixelExtents)) if (lineDoesNotPassThroughPixel(t))
return spanP0.y < pixelExtents.z ? winding * (spanP1.x - spanP0.x) : 0.0; return leftIntercept.y < pixelTop ? winding * horizontalSpan : 0.0;
// Calculate point A2, and swap the two clipped endpoints around if necessary. // Calculate point A2, and swap the two clipped endpoints around if necessary.
float a2x; float a2x = leftIntercept.x;
if (slopeIsNegative(dp)) { if (slopeIsNegative(dP))
a2x = spanP1.x; a2x += horizontalSpan;
} else { else
a2x = spanP0.x;
t.xy = t.yx; t.xy = t.yx;
}
// Calculate points A0-A3. // Calculate clipped points.
vec2 a0 = p0 + dp * t.x, a1 = p0 + dp * t.y; vec2 a0 = p0 + dP * t.x, a1 = p0 + dP * t.y;
float a3y = pixelExtents.w;
// Calculate area with the shoelace formula. // Calculate area with the shoelace formula.
// This is conceptually the sum of 5 determinants for points A0-A5, where A2-A5 are: // This is conceptually the sum of 5 determinants for points A0-A5, where A2-A5 are:
// //
// A2 = (a2.x, a1.y) // A2 = (a2.x, a1.y)
// A3 = (a2.x, a3.y) // A3 = (a2.x, top)
// A4 = (a0.x, a3.y) // A4 = (a0.x, top)
// //
// The formula is optimized. See: http://geomalgorithms.com/a01-_area.html // The formula is optimized. See: http://geomalgorithms.com/a01-_area.html
float area = a0.x * (a0.y + a1.y - 2.0 * a3y) + float area = a0.x * (a0.y + a1.y - 2.0 * pixelTop) +
a1.x * (a1.y - a0.y) + a1.x * (a1.y - a0.y) +
2.0 * a2x * (a3y - a1.y); 2.0 * a2x * (pixelTop - a1.y);
return abs(area) * winding * 0.5; return abs(area) * winding * 0.5;
} }
// * `p0` is the start point of the line. // * `p0` is the start point of the line.
// * `dp` is the vector from the start point to the endpoint of the line. // * `dP` is the vector from the start point to the endpoint of the line.
// * `center` is the center of the pixel in window coordinates (i.e. `gl_FragCoord.xy`). // * `center` is the center of the pixel in window coordinates (i.e. `gl_FragCoord.xy`).
// * `winding` is the winding number (1 or -1). // * `winding` is the winding number (1 or -1).
bool isPartiallyCovered(vec2 p0, vec2 dp, vec2 center, float winding) { bool isPartiallyCovered(vec2 p0, vec2 dP, vec2 center, float winding) {
// Clip to the pixel bounds. // Clip to the pixel bounds.
vec4 q, pixelExtents; vec2 leftIntercept;
vec2 spanP0, spanP1; float horizontalSpan, top;
vec2 t = clipToPixelBounds(p0, dp, center, q, pixelExtents, spanP0, spanP1); vec2 t = clipToPixelBounds(p0, dP, center, top, leftIntercept, horizontalSpan);
return !lineDoesNotPassThroughPixel(p0, dp, t, pixelExtents); return !lineDoesNotPassThroughPixel(t);
} }
// Solve the equation: // Solve the equation: