Fast Exact Area Sampling Antialiased Polygon

advertisement
Fast Exact Area Sampling Antialiased Polygon Filling Algorithm
Abstract
This paper presents a fast antialiased polygon filling algorithm, which is exactly area-sampled. It
can render multiple overlapping transparent complex polygons, including self-intersecting
polygons and holey polygons. Large amount of testing shows that this method can remove the
artifacts of supersampling methods completely, and has distinct advantage of performance over
supersampling methods.
CR Categories and Subject Descriptors: I.3.3 [Computer Graphics]: Picture/Image
generation-Digitizing and scanning
Additional Key Words and Phrases: polygon filling, anti-alising, exact area sampling.
1 Introduction
At present, there are two classes of antialiaing technologies: pre-filtering and post-filtering. The
most frequently used post-filtering method is supersampling, which first samples the ideal image
at a resolution higher than that of the display divice, for example 4x4 samples per pixel, then
filters the sampled image to remove infomation of hige frequency beyond the capability of
display divice, and then resamples the bandwidth limited image at the divice’s resolution.
Supersampling is simple to implement, but it can not remove aliase completely, unpleasant
“jaggies” artifacts will appear when graphs with near-horizontal or near-vertical edge are
rasterized with supersampling technology.
To remove the artifacts of regular supersampling, Cook [2] developed the stochastic sampling
technique. With stochastic sampling method, the sampling points are not regularly distributed, but
are disturbed stochastically. This causes remained artifacts to appear as high frequency noise
which is more tolerable to human visual system.
Supersampling methods are in broad use, because of their simplicity of implimentation. But
sampling the ideal image at a far higher resolution cause big computation cost. And supersamping
and it’s variants can not remove aliase completely.
Except post-filtering methods, numerous pre-filtering methods were developed.
Catmull [1] gave a method to render multiple overlapping polygon in hige quality, it
employed Weiler-Atherton’s [8] recursive clip algorithm to dicing the polygons inside of the
boundary of a pixel into small polygons, which are either completely visible or comletely invisible.
The method can generate images of hige quality, but it is very expensive because complex clip
algorithm was employed.
Feibush ets [5] described an anti-aliased polygon rendering method, which compute the
convolution of polygon with circularly symetric filter kernel. The method decompose polygons
into triangles with one vertex at the center of filter kernel. The convolution of triangles with the
filter kernel were pre-calculated and stored in a lookup table. With the help of the lookup table,
the converluton of polygon with filter kernel can be approximatly computed quickly. But for the
algorithm to work, polygon subdivision hidden surface algorithm developed by Weiler[8] is
employed to compute the visible portions of all the polygon.
Duff [4]’s convolution-scan-conversion algorithm computes the convolution of a polygon with an
arbitrary analytically integrable sampling kernal exactly, but for the algorithm to work, the
polygons must be simple ones whose boundary does not cross itself.
1
Schilling[7] presented a method of computing the area of polygon inside a pixel according to the
angle of the edges of the polygon and the distance between the edges and pixel center. The
method store the area and the geometry infomation in subpixel masks, which make the combining
accurately contribution of multiple polygons to be possible. Because the method look polygons as
intersection of halfplanes defined by polygons’ edges, it can only deal with convex polygons.
Doan [3] introduced an area sampling antialiasing technique capable of rendering complex,
self-intersecting polygons according to any fill rule. It works by decomposing the coverage area of
a polygon within each pixel into separate constituent simple regions, whose areas can be easily
computed. To speed up the computation of pixel value, some approximation techniques are used.
In this paper, a fast exact area sampling polygon filling method is present, which can deal with
overlapping transparent complex polygons, including self-intersecting polygons, concave
polygons, holey polygons, according to any filling role. It does not empoly any hidden surface
method to remove hidden polygon, and not pre-clip the polygons into simple ones. It is a scan line
algorithm, which processes the polygons simultaneously. It does not employ any approximation
technique and any lookup table to speed up. It compute the area of polygon inside of pixel 抯
boundary exactly and fast. Large amount of testing shows that this method is faster than
supersampling method, especially for complex graphs. and it can eliminate the artifacts of
supersampling methods completely.
Following text will show the method in details. Section 2 will introduce the algorithm of
render a single complex polygon, section 3 will explain how to render multiple overlapping
transparent polygons simultaneously, section 4 give the testing result.
2 Filling single polygon with exact area sampling
2.1 Overall algorithm
Given a polygon P, we want to compute the area of the polygon inside each pixel. To do the job,
we first cut P with boundary of each pixel into segments. Each such segment is monotone in y
coordinate and lies inside a pixel’s boundary, as shown in Figure.1.
With the segments computed, area of polygon in each pixel can be calculated in a scan-line
fashion. The following codes demonstrate the overall workflow. Note that the codes in this paper
are all demonstrative, they may not conform with the C++ syntax exactly.
//Polygon and Segment are all vector of points
class Polygon;
typedef vector<Point> Segment;
FillPolygon(Polygon *polygon, Color background,
Image pict)
{
for(int y=0; y<pict.Height(); y++)
{
Scan_A_Line(y polygon, background, pict);
}
2
}
typedef struct
{
float y;
int winding;
}Winding;
typedef vector<Winding> WINDING;
Scan_A_Line(int y, Polygon *polygon,
Color background, image pict)
{
vector<Segment> segments;
//segments is a vector storing the x-sorted
//segments of line y
polygon->Get_Segments_For_Y(y, segments);
vector<Winding> w = {<y,0>,<y+1,0>};
//w is a vector storing the winding
//infomation of left boundary of a pixel
for(int x=0; x<pict.Width(); x++)
{
Segment *first, *last;
//first points to the first segment of pixel
//(x,y), and last points to next to the last;
Get_First_Last_Seg_X(x,segments,first,last);
pict[x,y] = ComputeColor(w, first, last,
polygon->color,
polygon->pacity,background);
UpdateWinding(w, first, last);
//Update w to be the winding of left
//boundary of pixel (x+1, y);
}
}
Color ComputeColor(WINDING w,Segment* first,
Segment* last, Color color,
float opacity, Color background)
{
float area = GetArea(w, first, last);
Color c = color*area*opacity
3
+background*(1-area*opacity);
return c;
}
In above algorithm, a special data structure WINDING is used, which is a vector storing the
winding of point on the left boundary of pixel. For example the winding of left boundary of pixel
(2,2) in figure.1 can be stored as {<2,0>,<2.2,1>, <3,1>},which means that winding is 0 for points
of 2<=y<2, and 1 for point of 2.2<=y<3. Likly the winding of left boundary of pixel(4,3) is
{<3,1>,<3.3,2>,<3.7,1>, <4,1>}.
The critical point of above algorithm is the function GetArea(), which compute the area
occupied by the polygon according to the w and segments. There are three possibility for the
segment, (i)There is no segment in the pixel, like pixel(1,1), (3,4) in figure.1; (ii)There is one
segment, like pixel(1,2), (1,4), (3,3), (4,5) ; (iii)There are more than one segments, and the
segments do not intersect each other, like pixel(3,1), (3,6), (5,3). (iv)There are more than one
segments, and some pares of them intersect each other, like the pixel (2,3), (4,2). For above four
different conditions,We use different methods to compute the area respectively. The following
algorithm describes the overall logic, the details of which will be explained later.
#define kNoRelation 0
#define kOnLeft 1
#define kOnRight 2
#define kIntersect 3
GetArea(WINDING w, Segment* first, Segment* last)
{
if last-first == 0
GetArea_0(w);
else if last-first == 1
GetArea_1(w, first)
else
{
vector<float> intersect_y;
int32 matrix[32] = {0,0,...0};
for(Segment *i=first; i<last-1; i++)
{
float yi0 = i->begin()->y;
float yi1 = (i->last()-1)->y;
for(Segment *j=i+1; j<last; j++)
{
float yj0 = j->begin()->y;
float yj1 = (j->last()-1)->y;
if(min(yi0,yi1)>max(yj0,yj1)&&
min(yj0, yj1)>max(yi0, yi1))
continue;
4
int relation = Relation2Seg(i,
j,intersection_y);
if(relation == kOnLeft)
matrix[i-first]=0x01<<(j-first);
else if(relation == kOnRight)
matrix[j-first]=0x01<<(i-first);
}
}
if(intersect_y.size()>0)
GetArea_3(w, first, last, intersect_y);
else
GetArea_3(w, first, last, matrix)
}
}
2.2 Pixels with 0 segment
When last-first == 0 , there 抯 no segment going throuth the pixel, the pixel must be
completely inside or outside of the polygon. When EvenOdd filling role is used and the w is odd
or NoZero filling role is used and the w is nozero, the area is 1, else the area is 0.
2.3 Pixels with 1 segment
When last-first == 1 , that there 抯 only one segment going through the pixel, the area is
the sum of the area contributed by w and the area contributed by the segment, contribution of w
can be computed the same way as in condition (i), but this time the area is a fraction. For example
in figure.2, w of pixel (0,2) is {<2,1>, <2.1, 0>,<3,0>}, the area contributed by w equal to the
area of rect dace, which is 2.1-2=0.1.
The contribution of segment is a signed value. If left side of segment is outside of the
polygon and right side is inside of the polygon, the value is positive, like the segment ab in
figure.2. else if left side of segment is inside of the polygon and right side is outside of the
polygon, the value is negtive, like the segment orq in figure.2, else the value is zero. If the
contribution of segment is not zero, it 抯 absolute value is area of the zone between segment and
right boundary of pixel, which is easy to compute.
To judge the leftside and rightside of the segment are inside or outside of the polygon, the
winding of leftside and rightside should be computed. The winding of left side of the segment is w
抯 value at the middle of the segment, for exsample, winding of left side of segment ab in figure.2
equal to the winding at point N, which if 0, and winding of left side of segment orq equal to the
winding at point M, which is 1. If the segment is upward, the winding of right side equal to
winding of left side plus 1, else the winding of right side equal to winding of left side minus 1.
Winding_LR(WINDING w, Segment* segment,
int &leftwinding,int &rightwinding)
{
float y0 = segment->begin()->y;
float y1 = (segment->last()-1)->y;
leftwinding = GetValue(w, (y0+y1)/2);
5
if(y0 < y1)
rightwinding = leftwinding+1;
else
rightwinding = leftwinding-1;
}
2.4 Pixels with multiple segments
When there are more than one segments in the pixel, pare of segment of them can have three
possible relationshap:(a) the y range of them do not overlap, in other words, the top of segment A
is below the bottom of segment B or vice versa, like segment ab and cd in figure.3; (b)they
intersect each other, like gh and pq; (c) segment A is on the left or right of segment B, like
segment de and ef.
We use function Relation2Seg() to compute the the relationship between two segments, the
function has the following prototype:
int Relation2Seg(Segment *A, Segment *b,
vector<float> &intersection_y);
If segment A intersects Segment B, the function will store the y coordinates of the
intersections in intersection_y and return kIntersect, else if A is on left of B, function will
return kOnLeft, else function will return kOnRight. Because segment A and B are both
monotone in y, the function can finish in m+n time, where m and n is the number of the vertices of
A and B.
2.4.1 Pixels with no intersecting segments
If the size of intersection_y is zero, that means the segments don 抰 intersect each other, the
area is the sum of the contribution of w and all the segments. Contribution of w can be computed
the same way as in condition(ii). To compute the contribution of the segments, we need a method
to calculate the winding of leftside and rightside of each of the segments.
Simple observation shows that the winding of left side of a segment A equals to the winding
of the right side of the segment B, which is nearest on the left side of A. For example, winding of
left side of segment ef in figure.3 equal to winging of right side of segment de.
With the relationship between segments given by Relation2Seg(), winding of each side of
each segment can be calculated easily.
As shown in function GetArea(), the relationship of the segments is stored as an array of 32
long words of 32 bits long, which represents a matrix. If ith segment is on left of jth segment, the
ith bit of jth word is set to be 1, else 0.
With the matrix, following code can compute the winding of the left side and right side of all
the segments.
int32 matrix[32];
//leftwinding and rightwinding are arrays to store
//winding of leftside and rightside of segments
int leftwinding[32];
int rightwinding[32];
Winding_LR(WINDING w, Segment* first,
Segment* last, int leftwinding[32],
int rightwinding[32])
{
6
int segcount = last - first;
int remcount = segcount;
queue<int> indexQue;
for(int i=0; i<segcount; i++)
{
if(matrix[i] == 0)
{
float y0 = first[i].begin()->y;
float y1 = (first[i].last()-1)->y;
leftwinding[i] = GetValue(w,(y0*y1)/2);
if(y1>y0)
rightwinding[i] = leftwinding[i]+1;
else
rightwinding[i] = leftwinding[i]-1;
indexQue.push_back(i);
remcount--;
}
}
while(remcount>0)
{
int index = indexQue.pop_front();
for(i=0; remcount>0 && i<segcount; i++)
{
if(matrix[i] == 0x01<<index)
{
float y0 = first[i].begin()->y;
float y1 = (first[i].last()-1)->y;
leftwinding[i]=rightwinding[index];
if(y1>y0)
rightwinding[i] = leftwinding[i]+1;
else
rightwinding[i] = leftwinding[i]-1;
indexQue.push_back(i);
remcount--;
}
matrix[i] &= ~(0x01<<index);
}
}
7
Idea of above algorithm is that: if a segment has no left neighbor, it’s left side winding is the
w’s value at the middle of the segment, like segment de in figure.3; else a queue indexQue is used
to find the nearest neighbor of the segment.
With all the winding of leftside and rightside of all the segments was computed, the area
contributed by the segments can be computed easily the same way as in section 2.3.
2.4.2 Pixels with intersecting segments
When there are some pares of segments intersecting each other, like pixel (1,1) of figure.3,
we use a bruteforce method to compute the coverage area of the pixel. The area of the polygon’s
coverage of the pixel is divded by horizontal lines through vertices of the segments and
intersection points into small trapezoids and triangles, then a simple scan line method is
employed to sum up the areas of all the trapezoid and triangles.
Scan lines through the middle of the trapezoids and triangles help to determine whether a
trapezoid or triangle is inside of the polygon, and help to calculate area of each trapezoid and
triangle. For example, area of trapezoid ghij in figure .3 equals to length of line segment kl
multiplying length of ij.
2.5 Update the w
Another point of GetArea() need to be clarified is the function UpdateWinding(), which
update the w.
UpdateWinding(WINDING &w, Segment* first,
Segment* last)
{
Segment *i;
for(Segment* i=first;i<last;i++)
{
float y0 = i->begin()->y;
float y1 = (i->last()-1)->y;
if(y0 < y1)
Increase_1_In_Range(w,y0,y1);
else
Decrease_1_In_Range(w,y1,y0);
}
}
In above code, Increase_1_In_Range() Increase the winding of y0<=y<y1 with 1, and
Decrease_1_In_Range decrease the winding of y1<=y<y with 1.
3 Filling multiple overlapping transparent polygons
To render multiple overlapping transparent polygons correctly, the polygons must be
rendered simulteneously.
The following code is the M-version of the scan line algorithm of section 2, M standing for
Multiple polygons.
FillPolygon_M(int n, Polygon* polygon,
Color background, Image pict)
//polygon is an array of polygons to be filled,
//the polygons is z-ordered.The first polygon
8
//in the array is the one on the top, and the
//last one is the polygon on bottom
{
vector<Polygon*> activePolygon;
for(int y=0; y<pict.Height(); y++)
{
Get_Active_Polygons(n, polygon, activepolygon);
FillALine_M(y, activePolygon,background,pict);
}
}
#define kNoCoverage 0
#define kPartCoverage 1
#define kFullCoverage 2
FillALine_M(int y,vector<Polygon*> &polygon,
Color background, Image pict)
{
int n = polygon.size();
WINDING w[n];
for(int i=0; i<n; i++)
w[n] = {<y,0>,<y+1,0>};
vector<Segment> segments[n];
for(i=0; i<n; i++)
polygon[i]->Get_Segments_For_Y(y,segments[I]);
for(int x=0; x<pict.Width(); x++)
{
Segment *first[n], *last[n];
vector<int> indexVector;
int nFullCoverage = 0;
for(i=0; i<n; i++)
{
polygon[i]->Get_First_Last_Seg_X(x,
segments[i], count[i],
first[i], last[i]);
int coverage = Coverage(w[i],
first[i], last[i]));
if(coverage != kNoCoverage)
{
indexVector.push_back(i);
9
if(coverage == kFullCoverage)
{
nFullCoverage++;
if(opacity[i]== 1 )
break;
}
else
nFullCoverage = 0;
}
}
Color bkgrd = background;
for( ; nFullCoverageu>0; nFullCoverageu--)
{
i = indexVector.pop_back();
float op = polygon[i]->opacity;
bkgrd = bkgrd*(1-op)+polygon[i]->color*op;
}
if(indexVector.empty())
{
pict[x,y] = bkgrnd;
continue;
}
if(indexVector.size() ==1 )
{
i = indexVector[0];
pict[x,y] = ComputeColor(w[i],first[i],
last[i], polygon[i]->color,
polygon[i]->opacity,bkgrnd);
}
else
{
pict[x,y] =ComputeColor_M(indexVector, w, first, last,
polygon, bkgrnd);
}
for(i=0; i<n; i++)
UpdateWinding(w[i], first[i], last[i]);
}
}
In above algorithm, an active polygon table maintains the polygons of current scan line.
The segments of the active polygons are computed, and then a method similar to that of section 2
10
is used to compute the color of each pixel according to the ws and segments.
Coverage() function evaluate a polygon’s coverage of the pixel, there are three possibility : (a)
no coverage, which means the pixel is completely outside of the polygon; (b) full coverage, that
the pixel is completely inside of the polygon; and (c) part coverage. As discussed in Section2,
the polygon’s area in the pixel is determined by the w and the segments, so we can qualitatively
estimate the coverage quickly in a simple logic without complex calculate.
The algorithm check the polygons from top to bottom until reach a solid one , in the process
the indexVector stores the indices of the polygons visible. The variable nFullCoverage counts the
base polygons, which are on the bottom and cover the pixel fully.
By changing the background color to be the combination of color and opacity of the
basepolygons, and remove the basepolygons from the indexVector, the calculation can by
simplified.
If there’s only one polygon visible except basepolygons,like the pixel(1,1) in Figure4,
ComputeColor() of section 2 can be employed without any change to compute the color of the
pixel, else we employ ComputeColor_M() to do the job.
Like computing the color of
pixels with intersecting segments in section 2,
ComputeColor_M() divide the area of the pixel into trapezoids and triangles by horizontal lines
through the vertices of the segments and the intersection point of the segments, as pixel (1,3) in
Figure 4 shows. Each trapezoid or triangle is either completely inside of a polygon or outside. The
color of the pixel is the average of color of the trapezoids and triangles weighted by their area.
The color of trapezoid is combination of the color and opacity of the polygons the
trapezoid belonging to. Suppose there are 3 polygons containing the trapzoid, the color of the
trapzoid can be calculated with the fomula: ((a(1-b3)+c3b3)(1-b2)+c2b2)(1-b1)+c1b1 , where a
is the background color, bi is the opacity of ith polygon from top to bottom and ci is the color of
ith polygon.
To compute the area of the trapezoid and determine which polygons the trapezoid belongs to
is a trivial job. A scan line method similar to that of GetArea_3() of section 2 can be used.
4. Render curved region
Regions defined by curves such as beziers can be rendered with exact area sample method.
The curves are first divided into monotone ones, then the monotone curves are sored with y
coordinate. The curves can then be discreted into segments using adaptive forward difference
method [6] at scan conversion time line by line.
5 Effectivity and Efficiency
The antialiased rendering algorithm of above sections has been implimented as a plug-in of
Adobe Illustrator CS for testing. The effectivity and efficiency of the method have been tested
with large amount of samples.
Figure 5 to Figure 7 show how exact area sampling method can remove the artifacts of
supersampling methods.
Table 1 lists time comparision between exact area sampling method and supersampling
method.The table shows that the new method has distinct efficiency advantage.
6 Conlution
A simple exact area sampling antialising method has been presented, that can remove the
artifacts of supersampling completely, (see Figure 5 to 7). And the alorithm has distinct advantage
of performance over supersampling methods (see Table 1).
11
References
[1] CATMULL, E. 1978. A hidden-surface algorithm with anti-aliaseing. in Proceedings of
the 5th annual conference on Computer graphics and interactive techniques. 6 - 11 .
[2 ]COOK, R.L. 1986. Stochastic Sampling in Computer Graphics. ACM Transactions on
Graphics 5(1), 51-72.
[3]DOAN, K. 2004. Antialiased Rendering of Self-Intersecting Polygons using Polygon
Decomposition. in Proceedings of the 12th Pacific Conference on Computer Graphics and
Applications (PG?4) ,383-391.
[4] DUFF, T. 1989. Polygon Scan Conversion by Exact Convolution. in Raster Imaging and
Digital Typography. J. ANDRE & R.D. HERSCH, Eds, Cambridge University Press, 154-168.
[5]FEIBUSH, E., LEVOY, M., AND COOK, R. 1980. Synthetic texturing using digital filters,
Computer graphics, 14, 294-301 (proc. SIGGRAPH 80).
[6] LIEN, S.L., SHANTZ, M., AND PRATT, V. 1987. Adaptive forward differencing for
rendering curves and surfaces, in Proceedings of the 14th annual conference on Computer
graphics and interactive techniques, 111 - 118 .
[7] SCHILLING, A. 1991. A New Simple and Efficient Antialiasing with Subpixel Masks.
Computer Graphics 25(4), 133-141. (SIGGRAPH ?1 Proceedings).
[8] WEILER, K. and ATHERTON, P., Hidden surface removal using polygon area sorting,
(Siggraph 1977 proceedings).
12
Download