RenderOperation sorting
As seen in the previous article about the rendering subsystem, drawing operations are incapsulated inside a "RenderOperation" class. Each instance is queued by the renderer and sorted in order to prevent graphical artifacts (and to improve performance).
Deep down in the hardware, all rendered polygons must be rasterized to the backbuffer and painted. One of the simplest methods to determine which parts of each polygon are visible and which are not is the painter's algorithm: all polygons are ordered by the distance from the camera and then rendered. This means that, starting from the farthest polygons of the scene, nearer objects will be rendered on top of already drawn pixels.
This algorihm works well in most cases, but: a) it's inefficient, because you will draw the same pixel multiple times, b) it doesn't work in some special case (overlapping polygons).
Z-Buffer
A much easier approach, currently supported by practically all video cards, is Z-buffering: the video device keeps a secondary image, as large as the backbuffer, where it stores the "depth" values of the rendered polygons. If a pixel is about to be drawn, but its "depth" would be higher than that already present in the Z-buffer (meaning that the new pixel is farther away than the one rendered before), the pixel is simply discarded and no drawing occurs.
This means we can generally render all polygons in arbitrary order: the Z-buffer will handle the object's positions with pixel-per-pixel precision. The result will always be correct! Yay, problem solved! ![]()
...not quite.
There is one thing the painter's algorithm did right, which the Z-buffer cannot: correctly rendering alpha blended pixels. Suppose we are rendering a solid blue circle and a partially transparent red square on top of it. Since the order of these operations shouldn't influence the outcome because of Z-buffering, we could end up rendering the circle before the square:

The result would be exactly what we wanted.
Now assume that the two draw calls are reversed: this time our device renders the red square and correctly blends it with the background. The Z-buffer too is filled with the square's pixels (as usual). Then we render the blue circle: since the Z-buffer already contains the pixels from the square (which is closer to the viewer than the circle) some of the circle's pixels are discarded and won't be blended with the red pixels.

Not pretty.
Therefore we'll have to sort our render calls in order to draw all "solid" geometry (the order is ininfluent thanks to the Z-buffer) and then draw the "transparent" objects (ordered from farthest to nearest). Check out another explanation of the subject on the Cornflower blue blog.
By the way, Z-buffering also has other problems, like Z-fighting. The Z-buffer isn't a silver bullet solving all drawing order problems, but nonetheless it saves us a lot of pain. ![]()
Implementation
As said before, in my game's engine all drawing calls are encapsulated inside a RenderOperation instance. Each instance can specify a rendering mode combining one or more of the following options:
[Flags]
public enum RenderMode : int {
None = 0,
DisableCulling = 1,
Wireframe = 2,
AlphaBlend = 4,
PointSprite = 8,
AddictiveBlend = 16
}
The RenderOperation class implements the IEquatable<RenderOperation> and the IComparable<RenderOperation> interfaces in order to be automatically sorted by the list where they are kept (by the way, I use the excellent sorted tree from the awesome C5 Generic Collection Library for .NET).
public int CompareTo(RenderOperation other) {
//Calc distance from camera
Vector3 dist = (_owningPort.ActiveCamera.CurrentPosition - _material.WorldTransform.Translation);
Vector3 otherDist = (other.OwningPort.ActiveCamera.CurrentPosition - other.Material.WorldTransform.Translation);
//Point sprites are always last
if ((_mode & RenderMode.PointSprite) != 0) {
if ((other.Mode & RenderMode.PointSprite) != 0) {
//Both sprites, sort by inverse distance
return CompareDistance(otherDist, dist);
}
else
return 1;
}
if ((_mode & RenderMode.AlphaBlend) != 0) {
//I am alpha-blended
if ((other.Mode & RenderMode.AlphaBlend) != 0) {
//Other is not, me last
return 1;
}
else {
//Other is too, farthest away first
return CompareDistance(dist, otherDist);
}
}
else {
//I am not alpha blended
if ((other.Mode & RenderMode.AlphaBlend) != 0) {
//But other one is, I go first
return -1;
}
}
//Otherwise, both solid, closest goes first
return CompareDistance(otherDist, dist);
}
The CompareDistance method is a simple numerical comparison between the two distances. Also note that, while alpha-blended objects are rendered far-to-near (as in the traditional painter's algorithm), solid objects are actually drawn using the reverse painter's algorithm. This gives us a little performance improvement because near (and therefore large) objects are rendered first and will hopefully cover large part of the screen, giving a higher probability for the next objects to be discarded by the Z-buffer without actually drawing any pixels to screen.
Note: since the TreeBag class of C5 discards "equal" elements (it "keeps repetitions by counting", as you'll read in the documentation) we must be sure never to return '0' in this method. If we did, one or more RenderOperations would be discarded by the Tree and would never be drawn to screen. In case two RenderOperation instances are indistinguable by this method (same distance from camera) we arbitrarily return "-1" (there must be some arbitrary order after all
).



