[comp.graphics] A-Buffer source

jorice@maths.tcd.ie (Jonathan Rice) (07/17/90)

I'm writing this program to draw perspective pictures of landscapes and I could
do with some help on a couple of fronts. What I start out with is a gridded
height map and a satellite photo overlay image. To render the thing I use just
about the simplest method possible - I treat neighbouring heights in the height
map as the corners of quadrilateral surface tiles. I perspectively transform
these corner points to give me a 2-D quadrilateral in screen space, which I
fill with a colour read out of the photo overlay. Hidden surface stuff is done
with back-to-front overplotting along scan-lines of the height map as in
Coquillart and Gangnet's "The Shaded Display of Digital Maps". I've got a sort
of a prototype going in an X environment and it's okay as far as it goes, but
in order to improve image quality, I'm going to have to renounce the graphics
functions of X and do my own anti-aliased 24-bit polygon or triangle filling.
What I think I need is something along the lines of Carpenter's A-Buffer - has
this algorithm been improved on in any substantial way since it was published
in '84? One paper I've read claims better quality images by anti-aliasing on an
area greater than just the current pixel ("Efficient Alias-free Rendering by
using Bit-masks and Look-up Tables" - Abram, Westover & Whitted, '85).  How
much does that matter? I'm going to be trying to put my images together into a
short video, so what I'm particularly interested in is removal of
frame-to-frame aliases rather than absolute rendering accuracy. Also, I have a
HELL of a lot of very small triangles or quadrilaterals to draw - are there any
optimisations that can be made to the scan-conversion process when no polygon
ever gets larger than about five pixels? Any information that anyone wishes to
grace me with would be appreciated. I'd particularly love some code that
implements this stuff, particularly an A-buffer or such-like. I am no
masochist.

-- Jonathan

o----------------------o----------------------------o--------------------------o
|    Jonathan Rice     | Email: jorice@cs.tcd.ie    | He was a common fly      |
|----------------------| Tel: 353.1.772941 x2156 (w)| With a taste for fashion |
|Computer Science Dept.|      353.1.6245415      (h)| They were thrown together|
|  Trinity College     | Fax: 353.1.772204          | In a heat of passion     |
|     Dublin 2,        |               woof /\___/  |         - "Human Fly",   |
|     Ireland.         |                     /| |\  |          The Horseflies  |
o----------------------o----------------------------o--------------------------o

markv@gauss.Princeton.EDU (Mark VandeWettering) (07/17/90)

In article <1990Jul16.225553.2623@maths.tcd.ie> jorice@maths.tcd.ie (Jonathan Rice) writes:

	[ Jonathan needs to draw large numbers of small polygons quickly
	  and with proper antialiasing.  Wants suggestions over A buffer
	  or Abram-Westover-Whitted's method ]
	
You might also examine the Reyes paper in the 86 Siggraph (I think, all
this stuff is once again, not in my office).  Reyes basically just did
a scanline Z buffer at some higher resolution, and then filtered the
results apprpriately to get an nice image.   I believe the aliasing 
artifacts will be lessened because imformation from adjacent pixels can
be used for filtering, allowing a wider filter to be used.

Mark

robert@texas.esd.sgi.com (Robert Skinner) (07/18/90)

In article <1264@idunno.Princeton.EDU>, markv@gauss.Princeton.EDU (Mark
VandeWettering) writes:
|> In article <1990Jul16.225553.2623@maths.tcd.ie> jorice@maths.tcd.ie
(Jonathan Rice) writes:
|> 
|> 	[ Jonathan needs to draw large numbers of small polygons quickly
|> 	  and with proper antialiasing.  Wants suggestions over A buffer
|> 	  or Abram-Westover-Whitted's method ]
|> 	
|> You might also examine the Reyes paper in the 86 Siggraph (I think, all
|> this stuff is once again, not in my office).  Reyes basically just did
|> a scanline Z buffer at some higher resolution, and then filtered the
|> results apprpriately to get an nice image.   I believe the aliasing 
|> artifacts will be lessened because imformation from adjacent pixels can
|> be used for filtering, allowing a wider filter to be used.
|> 
|> Mark

actually, the Reyes implementation discussed in the paper divides the 
screen up into rectangular chunks, I expect from 16-64 pixels on a
side.  

I thought the most interesting thing about Reyes was the way it
processed primitives *before* they were scan converted (its simple and
can be made object-oriented).  The back-end scan conversion is
independent; Pixar used a Z buffer.  

I implemented a renderer with a Reyes-like front end, and two scan 
conversion back ends:  scanline Z buffer with 4x4 or 8x8
stochastically jittered subsamples,  an alpha buffer with 4x4 or
8x8 masks.

The back-ends were like ying and yang.  The Z buffer was about 3-4
times as slow as the alpha buffer, because you have to test a group of
samples for inclusion in a polygon.  You can't use coherence because 
the subsamples are jittered (well you could use some, but its not worth
it because the polygons only cover a few subsamples).  The alpha
buffer was optimized to convert polygon edges into alpha masks
representing half spaces.  These half-space alpha masks were then
logically AND'ed to get the mask(s) for a single polygon.  The mask
was then inserted into the sorted list for the pixel it covered.
Logical occlusion operations were performed on the mask as it was
inserted.  If the mask was found to be totally occluded before its insertion
point was found, it was thrown away.  All masks behind the new one were 
then occluded by it, and if any of them went to zero, they were thrown
away.  Believe it or not, doing all of those extra occlusion operations
was faster than putting them off to the end, because they kept the
size of the list small.  (Oops, I slipped into thesis mode there.
That's probably more detail than you wanted.)

On the other hand, I never was able to fully debug the alpha buffer.
Because of all sorts of odd boundary conditions, I occasionally
created incorrect masks.  The Z buffer was much simpler to code (about
1/7th the size of the alpha buffer), and worked flawlessly from the beginning.

The two algorithms produced almost identical images.

Robert Skinner
robert@sgi.com

You don't know what we could find.  Why don't you come with me,
 little girl, on a magic carpet ride

mark@masscomp.ccur.com (Mark Thompson) (07/19/90)

Reply-To: mark@calvin.westford.ccur.com (Mark Thompson)
Organization: Concurrent Computer Corp. Westford MA.

In article <10491@odin.corp.sgi.com> robert@sgi.com writes:
>I implemented a renderer with a Reyes-like front end, and two scan
>conversion back ends:  scanline Z buffer with 4x4 or 8x8
>stochastically jittered subsamples,  an alpha buffer with 4x4 or
>8x8 masks.
>
>I never was able to fully debug the alpha buffer.
>Because of all sorts of odd boundary conditions, I occasionally
>created incorrect masks.  The Z buffer was much simpler to code (about
>1/7th the size of the alpha buffer), and worked flawlessly from the beginning.
>
>The two algorithms produced almost identical images.

I'll bet those images weren't identical when rendering piles of
overlaping partially transparent objects!
+--------------------------------------------------------------------------+
|  Mark Thompson                                                           |
|  mark@westford.ccur.com                                                  |
|  ...!{decvax,uunet}!masscomp!mark   Designing high performance graphics  |
|  (508)392-2480                      engines today for a better tomorrow. |
+------------------------------------------------------------------------- +

markv@gauss.Princeton.EDU (Mark VandeWettering) (07/19/90)

>Reply-To: mark@calvin.westford.ccur.com (Mark Thompson)
>In article <10491@odin.corp.sgi.com> robert@sgi.com writes:

>>I never was able to fully debug the alpha buffer.
>>Because of all sorts of odd boundary conditions, I occasionally
>>created incorrect masks.  The Z buffer was much simpler to code (about
>>1/7th the size of the alpha buffer), and worked flawlessly from the beginning.
>>The two algorithms produced almost identical images.

This is consistent with my intuition as to the costs involved.  Although
I am suprised that your Z buffer was slower.  I was glancing through my notes
and found a fragment of code that was supplied to me by Pat Hanrahan that
did "point-in-polygon" testing very efficiently for quadrilaterals.  If we
call this routine "sample", then rendering a micropolygon becomes something 
like:
	compute the screen bounds xmin-xmax ymin-ymax
	for (x = floor(xmin) ; x <= ceil(xmax) ; x++) {
		for (y = floor(ymin) ; y <= ceil(ymax) ; y++) {
			if (sample(poly, x, y))
				color(x, y, color_of(poly) ;
		}
	}

sample uses the Jordan Curve test to figure out if the point is in the polygon.
If an odd number of edges cross the +x axis, then you are inside.  This can
be done with some moderately small number of instructions.   Basically the 
idea is to recenter the polygon around the sample point.  Then, check vertices
0 and 2 to see if they have different signs.  If they do, then the line from 
0 to 1 or the line from 1 to 2 cross the X axis, depending on the sign of
1.  Similarly, the line from 0 to 3 or 3 to 2 crosses the the X axis as well.
If 0 and 2 are both on the same side of zero, then the line from 0-1 or 1-2
crosses dependant on the sign of 1, with similar cases for vertex 3.  
For each edge that crosses the X axis, you need to ensure that the edge
crosses the POSITIVE X axis, which takes two multiplies and a compare.
For each edge that does cross, you set a particular bit in a mask byte.  
At the end, if there are an odd number of bits set, you know that you are
inside and return a one.  Else, you can return a zero.  This can be done using
a table lookup.  All in all quite alot simpler than trying to clip each polygon
and perform all sorts of nonsense with coverage masks.

Mark

robert@texas.esd.sgi.com (Robert Skinner) (07/19/90)

In article <31667@masscomp.ccur.com>, mark@masscomp.ccur.com (Mark
Thompson) writes:
|> Reply-To: mark@calvin.westford.ccur.com (Mark Thompson)
|> Organization: Concurrent Computer Corp. Westford MA.
|> 
|> In article <10491@odin.corp.sgi.com> robert@sgi.com writes:
|> >  [my description of my alpha buffer and Z buffer renderers deleted]
|> >
|> >The two algorithms produced almost identical images.
|> 
|> I'll bet those images weren't identical when rendering piles of
|> overlaping partially transparent objects!

right, I didn't bother to implement transparency, because I didn't 
need it, and I didn't know if I would end up using the Z buffer. 

The Z buffer algorithm can't do transparency.  
I stopped just short of stating (what I thought) was obvious.

Robert Skinner
robert@sgi.com

	"What can go wrong?"

			- Calvin & Hobbes

mark@calvin..westford.ccur.com (Mark Thompson) (07/20/90)

In article <10588@odin.corp.sgi.com> robert@sgi.com writes:
>
>In article <31667@masscomp.ccur.com>, mark@masscomp.ccur.com (Mark
>Thompson) writes:
>|> I'll bet those images weren't identical when rendering piles of
>|> overlaping partially transparent objects!
>
>right, I didn't bother to implement transparency, because I didn't 
>need it, and I didn't know if I would end up using the Z buffer. 
>
>The Z buffer algorithm can't do transparency.  
>I stopped just short of stating (what I thought) was obvious.

Not necessarily true. If you generate an alpha value for each pixel
as well as rgbZ, crude transparency can be implemented still using
a standard Z buffer algorithm. This of course begins to fall apart
when multiple transparent object overlap (because no linked list
of pixel contributions have been maintained). The main reason we used
an A buffer in the system I last worked on was to remove edge artifacts
on abutting antialiased polygons (using alpha lets the background
color seep through the 'cracks'). I'm still surprised that your Z buffer
was slower.
+--------------------------------------------------------------------------+
|  Mark Thompson                                                           |
|  mark@westford.ccur.com                                                  |
|  ...!{decvax,uunet}!masscomp!mark   Designing high performance graphics  |
|  (508)392-2480                      engines today for a better tomorrow. |
+------------------------------------------------------------------------- +

robert@texas.esd.sgi.com (Robert Skinner) (07/21/90)

In article <31673@masscomp.ccur.com>, mark@calvin..westford.ccur.com
(Mark Thompson) writes:
|> In article <10588@odin.corp.sgi.com> I write:
|> >
|> >The Z buffer algorithm can't do transparency.  

I hesitated before making such an absolute statement, because I know 
that people are constantly improving things.

|> 
|> Not necessarily true. If you generate an alpha value for each pixel
|> as well as rgbZ, crude transparency can be implemented still using
|> a standard Z buffer algorithm. This of course begins to fall apart
|> when multiple transparent object overlap (because no linked list
|> of pixel contributions have been maintained). 

ignoring the multiple overlapping transparent objects, tell me what 
happens at a pixel when you have this case:  You have rendered an opaque 
object, then a transparent object in front of it.  I can see how you calculate
the correct color at this point.  Now you render another opaque object
behind the transparent one, you have one of two cases:  

(1) The second
opaque object is behind the the very first one, so its obscured.  The
pixel and Z values don't change.  But how do you know this without
saving an additional Z value for the first obscured object.  

(2) The second opaque object is between the transparent one and the
opaque one.  To compute the correct value of the pixel, you have
to recover the original color of the transparent object, so you have
to save its original color somewhere too.

I just can't see that this works without doing one or more of the
following:

	A:  render objects back-to-front
	B:  save more information at each pixel
	C:  oops, I could have sworn I thought of a third 8^)

Doing A eliminates the render-any-number-of-primitives-in-any-order
advantage of the Z buffer.  Doing B to save just one value doubles the
amount of memory the Z buffer takes, which is already considerable.
If you save much more information, it starts to look like the alpha
buffer.

I would be interested in any references you might have on doing
transparency with the Z buffer.

|> The main reason we used
|> an A buffer in the system I last worked on was to remove edge artifacts
|> on abutting antialiased polygons (using alpha lets the background
|> color seep through the 'cracks'). 

you *want* the color to seep through the cracks?

|> I'm still surprised that your Z buffer
|> was slower.

I've gotten a description of an algorithm that looks faster, but it
will probably be some time before I can look into it or try it.  (Who
knows, maybe I implemented a really fast alpha buffer, and I didn't
realize how fast it really was.)

Robert Skinner
robert@sgi.com

One pill makes you larger, 
 and one pill makes you small,
 and the ones that mother gives you 
 don't do anything at all.
 Go ask Alice when she's ten feet tall. 
 
		-- Jefferson Airplane

cdshaw@cs.UAlberta.CA (Chris Shaw) (07/21/90)

In article <10673@odin.corp.sgi.com> robert@sgi.com writes:
>In article mark@calvin..westford.ccur.com (Mark Thompson) writes:
>|> In article <10588@odin.corp.sgi.com> I write:
>|> >The Z buffer algorithm can't do transparency.  
>I hesitated before making such an absolute statement, because I know 
>that people are constantly improving things.

Well, Z-Buffer is Z-Buffer. Z-Buffer with an Alpha channel is 
Duff's Alpha-compositing. Or something other than Z-buffer.

>..ignoring the multiple overlapping transparent objects, tell me what 
>happens at a pixel when you have this case:  You have rendered an opaque 
>object, then a transparent object in front of it.  I can see how you calculate
>the correct color at this point.  Now you render another opaque object
>behind the transparent one, you have one of two cases:  
>
>(1) The second opaque object is behind the the very first one, 

No problem. Looks fine.

>(2) The second opaque object is between the transparent one and the opaque one

You can't beat this without extra storage. A-buffer uses a data structure in
mainstore to solve this. When all first-pass rendering is done, you run through
each pixel list nearest-to-farthest, adding colour and clipping area.

Fournier's Ultracomputer algorithm just takes the top two contributions.
Weinberg's pipeline architecture does something similar to A-buffer.
Duff's algorithm trades spatial accuracy (no pixel mask) for transparency,
but the fact remains that to get it right, you have to sort in depth.
Fortunately, the stuff being sorted can be *objects* as opposed to *pixels*,
so the sorting task is has a much smaller N in the O(N) sense.

One of Prusiceiwicz's students at U of Regina came up with an algorithm
that combines A-Buffer and Compositing, so you have a mask AND and an alpha
channel.

>I just can't see that this works without doing one or more of the
>following:
>	A:  render objects back-to-front
>	B:  save more information at each pixel
C: Punt. Ignore the problem, push it upstairs.

>Doing A eliminates the render-any-number-of-primitives-in-any-order
>advantage of the Z buffer.  Doing B to save just one value doubles the
>amount of memory the Z buffer takes, which is already considerable.
>If you save much more information, it starts to look like the alpha buffer.

Well, doing A means you may sort 100's of polygons as a single entity. Much
better than sorting the polygons.

>|> The main reason we used
>|> an A buffer in the system I last worked on was to remove edge artifacts
>|> on abutting antialiased polygons (using alpha lets the background
>|> color seep through the 'cracks'). 

You can fix this by rendering common edges aliased. If you have a 
polygonal mesh, you know that the inner edges are each shared by two polygons,
so you can treat these separately, and render the inner edges aliased, with
full coverage. It's an extra check per edge, but the calculation per edge
may be less.

>Robert Skinner   robert@sgi.com


--
Chris Shaw     University of Alberta
cdshaw@cs.UAlberta.ca           Now with new, minty Internet flavour!
CatchPhrase: Bogus as HELL !

gg10@prism.gatech.EDU (Gregory L. Galloway) (07/22/90)

In article <10491@odin.corp.sgi.com>, robert@texas.esd.sgi.com (Robert Skinner) writes:
> I implemented a renderer with a Reyes-like front end, and two scan 
> conversion back ends:  scanline Z buffer with 4x4 or 8x8
> stochastically jittered subsamples,  an alpha buffer with 4x4 or
> 8x8 masks.
> 
> Robert Skinner
> robert@sgi.com

Cook, et al., said that the Reyes system worked really well when splitting
patches, but not polygons.  How about just simple triangles?  Has anyone
tried writing a rendering system based on Reyes which just uses simple
midpoint subdivision (as in fractals but w/o the random displacement)?  I'm
curious to know how the system performs compared to a simple Z-buffer w/ 
straight scan-conversion and no stochastic sampling.

Thanks in advance,

Greg Galloway
gg10@prism.gatech.edu

mark@calvin..westford.ccur.com (Mark Thompson) (07/23/90)

In article <1990Jul20.231437.22325@cs.UAlberta.CA> cdshaw@cs.UAlberta.CA (Chris Shaw) writes:
>In article <10673@odin.corp.sgi.com> robert@sgi.com writes:
>>In article mark@calvin..westford.ccur.com (Mark Thompson) writes:
>>|> In article <10588@odin.corp.sgi.com> I write:
>>..ignoring the multiple overlapping transparent objects, tell me what 
>>happens at a pixel when you have this case:  You have rendered an opaque 
>>object, then a transparent object in front of it.  The second opaque object
>>is between the transparent one and the opaque one
>
>You can't beat this without extra storage. 

One alternative is to separate your transparent objects from your opaque
ones and render them last. This is generally much less work than sorting.

>One of Prusiceiwicz's students at U of Regina came up with an algorithm
>that combines A-Buffer and Compositing, so you have a mask AND and an alpha
>channel.

We also used both an alpha channel and A-Buffer. It allowed for crude
transparency when the A-Buffer was turned off and the alpha provided the
necessary info to extrernally blend to a separate image/video source.

>>|> The main reason we used
>>|> an A buffer in the system I last worked on was to remove edge artifacts
>>|> on abutting antialiased polygons (using alpha lets the background
>>|> color seep through the 'cracks'). 

>You can fix this by rendering common edges aliased. If you have a 
>polygonal mesh, you know that the inner edges are each shared by two polygons,
>so you can treat these separately, and render the inner edges aliased, with
>full coverage. It's an extra check per edge, but the calculation per edge
>may be less.

In fact we provided edge flags for selective antialiasing. The problem
arises however when the shared edge becomes the 'silohette edge' (the
edge of the viewable portion of the object). It is then necessary to
atialias an edge you normally would not have. I imagine other checks
would be feasible to detect this case also but probably at a cost
that exceeds that of the A-Buffer. Any ideas?
+--------------------------------------------------------------------------+
|  Mark Thompson                                                           |
|  mark@westford.ccur.com                                                  |
|  ...!{decvax,uunet}!masscomp!mark   Designing high performance graphics  |
|  (508)392-2480                      engines today for a better tomorrow. |
+------------------------------------------------------------------------- +

robert@texas.esd.sgi.com (Robert Skinner) (07/24/90)

In article <11657@hydra.gatech.EDU>, gg10@prism.gatech.EDU (Gregory L.
Galloway) writes:
|> In article <10491@odin.corp.sgi.com>, robert@texas.esd.sgi.com
(Robert Skinner) writes:
|> > I implemented a renderer with a Reyes-like front end, and two scan 
|> > conversion back ends:  scanline Z buffer with 4x4 or 8x8
|> > stochastically jittered subsamples,  an alpha buffer with 4x4 or
|> > 8x8 masks.
|> > 
|> > Robert Skinner
|> > robert@sgi.com
|> 
|> Cook, et al., said that the Reyes system worked really well when splitting
|> patches, but not polygons.  

that's not how I interpreted the statement:

	"Polygons in general do not have a natural coordinate system
	for dicing.  This is fine in our case, because bicubic patches
	are our most common primitive, and we hardly ever use
	polygons."

I think the first sentence refers to polygons with 5 or more vertices,
in which case I agree.  How do you split or dice a pentagon?  I
believe the second sentence implies that Pixar almost always uses 
more sophisticated primitives than polygons.  I also got the
*impression* that polygons didn't render much faster than bicubic
patches, so why bother with polygon limitations?

|> How about just simple triangles?  Has anyone
|> tried writing a rendering system based on Reyes which just uses simple
|> midpoint subdivision (as in fractals but w/o the random displacement)?  

that is basically what I did.  I rendered triangles and quadrilaterals
and did simple midpoint subdivision (I didn't do anything special for
bow-tie quads.)  

|> I'm
|> curious to know how the system performs compared to a simple Z-buffer w/ 
|> straight scan-conversion and no stochastic sampling.

well, you are going to pay the price for the splitting and dicing that
Reyes does that a plain vanilla Z-buffer doesn't.  In addition, the the
straight scan-conversion algorithm could use coherency effectively on
large polygons.  Reyes micropolygons are much smaller, so you would
lose some there too.

I think Reyes is a beautiful system.  Its clean and simple.  I plan on
re-implementing my Reyes-like renderer in C++ to take advantage of the
(sort of) object-oriented style.  But I think it sacrifices some 
speed for the great God of realism and the potential for parallelism.  

I think the trade-off is worth it.  

|> 
|> Thanks in advance,
|> 
|> Greg Galloway
|> gg10@prism.gatech.edu

Robert Skinner
robert@sgi.com

You will encounter several species of small, furry animals,
 gathered together in a cave and grooving with a pict

coy@ssc-vax.UUCP (Stephen B Coy) (07/24/90)

In article <10673@odin.corp.sgi.com>, robert@texas.esd.sgi.com (Robert Skinner) writes:
> 	A:  render objects back-to-front
> 	B:  save more information at each pixel
> 	C:  oops, I could have sworn I thought of a third 8^)
> 
> Doing A eliminates the render-any-number-of-primitives-in-any-order
> advantage of the Z buffer.  Doing B to save just one value doubles the
> amount of memory the Z buffer takes, which is already considerable.
> If you save much more information, it starts to look like the alpha
> buffer.

A compromise can be reached by rendering all the opaque objects as
you get them but saving the transparent objects for last.  The
transparent objects can then be sorted and rendered back to front.
This works best with scenes that have relatively few transparent
objects.

> Robert Skinner
> robert@sgi.com

Stephen Coy
uw-beaver!ssc-vax!coy