So we’re doing another one of these things. I know this isn’t as fun as a Good Robot update, but hopefully this can keep us amused until the project gets moving again. While working on this, I found an interesting wrinkle that gives us a reason to talk about poly count vs. fill rate and why a full-blown 3D game runs many times faster than a trivial screensaver.
But first let’s talk about what I’ve made…
Let’s say we generate a simple texture that’s pure white in the center and fades to black at the edge to make a nice blurry circle.
Now let’s slap that texture on a quad (a pair of triangles, really) defined by points a, b, c, and d. We deform this polygon by making opposing points a and d vary their distance from each other according to a sine wave. Likewise, b and c move towards and away from each other on another sine wave with a longer wavelength.
We want to move this polygon around the screen.
|Like this, only with an additional layer of complexity.|
We DO want the movement to be chaotic and unpredictable. We DON’T want the movement to be sharp or jagged. If it moves randomly, it will just make a zig-zagging scribble all over the place. So instead we create a three-body system. (Not like the three-body problem they have in physics, although I guess there are superficial similarities between the two.) We put our polygon at point P1. P1 orbits point P2, which in turn orbits point P3, which orbits the center of the screen. The effect is a bit like a spirograph with an extra layer. This produces large motions that will (over time) send our polygon all over the screen, and do so in sweeping motions instead of abrupt ones.
The path it takes looks like this:
This is a composite (created just for the purpose of this explanation) of what the polygon is doing over about a minute of running time. It’s bright where the poly is moving slow, and dim where it’s moving fast.
Actually, we’re not just moving a single polygon around. We’re creating a trail of them. As the polygon moves around, it leaves behind a copy. These copies gradually move towards the camera, which makes them seem to grow larger as they fade out. Every few seconds we pick a new random color for our polygon.
During rendering, each copied polygon is drawn on top of the previous one, slightly brightening the pixels there. There are a total of 256 polygons in this trail. And so we end up with this blob of undulating, wobbling color that looks kind of like a slow-motion flame or vapor.
Well, I guess we should say there are 512 triangles, since that’s what really matters. In any case, the screensaver dawdles a bit. We’re only drawing 512 triangles. Why is it so slow?
Back in 2004, Doom3 gave us characters that weighed in at about 1,500 or so. For example, the imp…
…was the simplest foe in the game at just 1,100 triangles. So that imp has more than twice as many triangles as my screensaver. And the imp is being drawn using monumentally complex (by comparison, anyway) shaders that do lighting, shadowing, specular gloss, texturing, and bump-mapping. Meanwhile, my screensaver is drawing simple unlit no-shader polygons. And Doom3 shows us not only an imp, but several. Plus all the level geometry. Plus the moving machinery in the room. Plus all the other crap like the HUD and the player’s arms and weapon. And it did all of this on 2004 level hardware at a decent framerate.
Meanwhile, my screensaver chugs on my not too shabby machine. Why can a 2004 game draw many more (perhaps an order of magnitude more) triangles with far more demanding processing faster than my 2013 machine can draw this tiny handful of colored circles?
It’s all about fill rate.
How much of the screen are we filling with polygons? Or more accurately, how many total pixels are we drawing? Let’s assume we’re going to run Doom 3 and Plasma at the same resolution. And let’s just assume, for the sake of having a nice 16:9 ratio, that we’re running at a desktop resolution of 1600×900. (That’s probably small by today’s standards, and large by 2004, standards, and I’m not sure if that was ever a native resolution of a monitor, but whatever. I’m making a point here.) That means you’ve got a total of 1,440,000 pixels. In Doom 3, you will draw every pixel at least once. Some portion of the screen will get drawn more than once. Back to our two imps:
In the circled area, we might end up drawing some of the pixels in the red circle as many as three times. Say we draw the floor first. Fine. We’ve drawn an empty room. Then we draw the imp in the back, which ends up overwriting a little bit of the drawing we did earlier. Then we draw the imp in the front, and all of the pixels he occupies are drawn. So everywhere we have imps, pixels were drawn twice, except for where the arm of the front imp covers that leg of the rear one. Those pixels were drawn three times.
There are, in a broad sense, two stages to rendering. The first is vertex processing: Considering where this imp is in the scene, will it appear on screen, and if so, where? The second is pixel processing: Now that we know what regions of the scene are occupied by the imp, let’s painstakingly go over it a pixel at a time and draw it. For the curious: These two steps are what make up “vertex shaders” and “pixel shaders” that us programmers are always on about. The second step is way, way more expensive. A triangle only has three vertices, but can contain hundreds or even thousands of pixels, and in a game like Doom 3 a pixel is probably more computationally expensive than a vertex. (To draw a pixel you have to do multiple texture lookups to get the texture, specular, and bump map, and then do a page or so of math to turn that into your final color value.)
So the polygon count of our scene basically doesn’t matter. Both of the programs we’re discussing are drawing a trivial number of polygons as far as my graphics card is concerned.
You’ll notice that in my program, polygons don’t occlude each other. It’s not like I’ve got solid imps blocking our view of the scenery. It draws one polygon, and then another on top of that, and another on that. Each time it draws a polygon it must draw all the pixels, adding to what’s already been drawn. It has to. That’s how this effect is achieved.
Let me do a quick test…
That’s a single test polygon exactly halfway through its lifespan, rendered without the fuzzy texture so we can see its shape. I took a few screenshots like this, and this is a very typical sample. Running at our test resolution, it takes up about 176,319 pixels, or ~12% of the area of the screen. So each of our 256 polygons draws, on average, 12% of the screen pixels.
In the Doom example, we rendered every pixel once and a few unlucky ones got drawn a couple of times. Even in our worst-case scenario, I doubt our overdraw was anywhere near double the total pixel count. On average, every pixel is drawn slightly more than once.
In the case of my screensaver, every screen pixel pixel is drawn an average of…
0.12 x 256 = 30.72
And that’s what makes it slow.
Having said all that, I accomplished what I set out to do, which was smear some pretty colors all over the place. There are a lot of things you could do to speed this up, but this effect is fundamentally mean to our existing graphics technology. It actually runs fine on one screen, but in my dual-monitor setup Windows asks the screensaver to handle a gargantuan 3600×1334 window that covers both monitors, which (according to our back-of-the-napkin work above) gives my graphics card 147,529,728 pixels to draw, which is apparently unreasonable? The framerate dips to 20 or so, and the effect loses some of the magic at that speed.
So there it is. Another disposable afternoon project. As before, it’s released under a “do as you please” deal.
Quakecon 2011 Keynote Annotated
An interesting but technically dense talk about gaming technology. I translate it for the non-coders.
Marvel's Civil War
Team Cap or Team Iron Man? More importantly, what basis would you use for making that decision?
Spec Ops: The Line
A videogame that judges its audience, criticizes its genre, and hates its premise. How did this thing get made?
A programming project where I set out to make a Minecraft-style world so I can experiment with Octree data.
The Best of 2013
My picks for what was important, awesome, or worth talking about in 2013.