Monday, October 24, 2011

Well That's Dumb

I resolved my rendering issue thanks to this XNA forum post which indicates that Viewports (which determine which portion of the screen actually gets drawn on) initialize with maximum and minimum depth set to zero.  This explains why the problem on xbox only showed up when I was using split-screen, as it involves creating new viewports.  So basically all the depth calculations were getting clamped between 0 and 0, effectively rendering the depth buffer useless.  A quick change to explicitly set appropriate minimum and maximum depths (0 and 1) fixed the problem completely.

What I don't understand now is why the problem DIDN'T show up on Windows, but I really don't care at this point.  There are other weird problems happening on Windows that don't happen on xbox (for some reason, my game sounds don't work on Windows, but they work on Xbox), and I can't be bothered to track down the cause.  Probably I need to do a fresh reinstallation on my laptop, but again, can't be bothered.

Anyway, the important thing is it's working correctly now!  I can carry on with fruitful development instead of banging my head against bugs.

Render WTF

This past weekend I successfully got a four-player local split-screen demo running on the xbox, and encountered a severe render bug that I have yet to quash, because it only occurs on xbox.

For some reason that I haven't even begun to uncover, my models on xbox render as though the depth buffer is turned off.  I've checked the code and the depth buffer is definitely turned on; in fact, the same code renders the same model at a different point in the game with no errors whatsoever.  This one's gonna be tough to track down.

Anyway, it does work correctly on my development machine, and I'll post a video soon.  It looks pretty awesome if I have to say so myself. ;-)

Thursday, October 20, 2011

Billboards, an Update

Last night I finally had a breakthrough moment with the billboard thing I discussed in my previous post.  It took some time to get it right, because my bullets (which are modeled after a popular open-source game's design) have alpha gradients.  The trouble with alpha in general is that it doesn't look right if objects are drawn out of depth order. 

To illustrate, imagine two objects, A and B. A is nearer the viewer and a semi-transparent color.  B is farther from the viewer, partially obscured by A (i.e. visually overlapping), and nearly opaque.

If B is drawn first followed by A, you'll get what you expect: a view of A, with the obscured part of B semi-visible through A.  However, if A is drawn first, the portion of B overlapped by A will appear in front of A, even though foreshortening and other depth cues would indicate it is behind.

This would be easier to explain with a diagram, but I'm not somewhere I can easily draw one.  Google around for alpha blend depth sort if you want to learn more.

Anyway, the well-known solution to this is to always draw alpha-blended objects in reverse depth order; that is, draw objects that are farther away from the viewer before drawing objects that are nearer.  This of course requires additional processing to sort the objects prior to passing them to the render pipeline.

After doing a lot of searching and seeking of examples, I finally landed on the solution of creating an unsorted vertex buffer and a sorted index buffer.  This helps to minimize computational overhead in the sorting process, as it needs only to move 4-byte integers around instead of entire vertex declarations (which, in this example, are 64 bytes in size).  Of course, sorting does require knowing the distance from the viewer, which means doing extra work in the form of applying the view and perspective transformations to every vertex to discover their depth.  Fortunately that happens pretty quickly, even in software.

I also decided to reduce the likelihood of garbage collection trouble and made the vertex buffer a static array, and use it in a ring queue form.  The index buffers are destroyed and recreated every frame, but the vertex array stays put in memory for the lifetime of the containing object.  Since the index buffers are usually fairly short (six indices per shot, times however many shots are active on the screen at once which usually isn't many and can be limited by game rules), garbage collection isn't too much of a concern.  Again, it's 6 x 4 (six 4-byte indices), or 24 bytes per shot, instead of 4 x 64 (four 64-byte vertices), or 256 bytes per shot.

SO, after a wild evening of coding, I now have a working blaster.  I may post a video; I'm rather proud of my accomplishment!