XNA RoundLines in Pixel Coordinate Space
Two people recently sent me questions about setting up the world/view/projection matrices to work with my XNA RoundLine code. All of my programs so far have specified line coordinates in an abstract world coordinate space, with a view matrix based on a 2D camera and a projection matrix to adjust for nonsquare viewports. But if you want to write a 2D game that uses pre-authored bitmap graphics, it's often easier to work in a pixel-based coordinate system. The XNA SpriteBatch class, for instance, lets you specify coordinates in pixels, and things just work without having to think about the 3D transformation pipeline. So I spent a little time looking at what is involved in using RoundLines in a game that uses pixel-space coordinates.
So let's say you want to specify RoundLine coordinates in this space where (0, 0) is the upper-left corner of the screen, and something like (640, 480) is the lower-right corner of the screen. Before any polygons get rasterized by Direct3D, they need to be transformed into clip space. In this space (ignoring the Z dimension), the upper-left corner is (-1, 1) and the lower-right corner is (1, -1). Anything outside this rectangle gets clipped. We want the projection matrix to transform the vertex coordinates between these two spaces. (We'll just use an identity matrix for the view matrix.)
Horizontally, we want to change the coordinate system from (0 to 640) to (-1 to 1). So we divide x by 640 (so we're now 0 to 1), then multiply by 2 (so we're now 0 to 2), then subtract 1 (so we're now -1 to 1). Done!
Vertically, things are similar except there is a vertical flip: we want to change the coordinate system from (0 to 480) to (1 to -1). So we divide y by 480 (so we're now 0 to 1), then multiply by 2 (so we're now 0 to 2), then multiply by -1 (so we're now 0 to -2), then add 1 (so we're now 1 to -1).
Here's one way to do that in code:
viewportWidth = (
viewportHeight = (
float scaleX = 1.0f / (viewportWidth / 2);
float scaleY = 1.0f / (viewportHeight / 2);
Matrix.CreateScale(scaleX, scaleY, 1) *
.CreateScale(1, -1, 1) *
Matrix.CreateTranslation(-1, 1, 0);
There's one more twist involved. By inverting Y, the culling rule changes. Culling is more relevant in 3D apps than 2D -- it lets the pipeline discard polygons that are facing away from the camera, to save the time that would otherwise be wasted rendering them. The vertices for my RoundLine polygons were specified in clockwise order. But if you flip Y, clockwise becomes counter-clockwise:
The RoundLine effect file was specifying "CW" culling for each technique. With the Y inversion, all the polygons became counter-clockwise and were getting clipped. The solution is to specify "None" culling instead, so changing the coordinate system doesn't inadvertently cull all the polygons.
So I've attached a little sample program that draws a SpriteBatch image and some RoundLines together, doing everything in screenspace. Hopefully this code and explanation is useful, especially for people writing games in pixel space.