Lines: 2D, thick, rounded line segments for XNA programs
I've written some helper code to draw nice-looking 2D line segments with decent performance in XNA -- perhaps you will find it useful in your own projects. These lines have several advantages over using hardcoded images or standard Direct3D lines:
- They can be made thick enough to look good on televisions (vs. thin lines which may flicker)
- They scale cleanly (they look the same at different resolutions)
- Rounded corners look nice and have a well-defined normal vector everywhere for collisions, lighting, etc.
- They can use my blurred-edge technique to reduce aliasing
- You can create some fun effects with pixel shaders
I create one vertex buffer that is used for every line. I didn't want to be locking and modifying the VB for different line lengths, so I stretch the line out in the vertex shader. I store some special values in the texture coordinates that tell the VS which vertices to scale and/or translate to get them from "unstretched model space" to "stretched model space." The length and radius are passed in per-line as shader constants. Once the line is stretched, I apply the usual world/view/projection transformation as is done for normal geometry. Blurring and other effects are done in the pixel shader.
The image below shows some Lines:
- In unstretched space, wireframe (oops, actually this mesh should be shown with radius 1.0 to represent its unthickened state)
- In stretched model space, wireframe
- With solid fill
- With blurred edges
I looked into using hardware instancing, so a whole list of lines could be drawn in one Direct3D call, but it didn't look to be worth the trouble, and hardware support for instancing varies on the Windows platform.
In the pixel shader, I have information about how far each pixel is from the "true" (zero-thickness) line segment, and I also have the stretched model space X value (ranging from 0 to the length of the segment). There's also a time parameter for animation. These values help enable interesting effects through the pixel shader. The effect file has techniques for each of the following visual effects:
- Standard (blurred edges)
- NoBlur (sharp edges)
- AnimatedLinear (stripes moving along the line)
- AnimatedRadial (stripes radiating out from the center of the line)
- Modern (see the red octagon in the demo)
- Tubular (transparent in the middle, fades to solid on the edges)
- Glow (solid in the middle, fades to transparent on the edges)
Here's the AnimatedRadial effect:
The Draw function takes a List<Line> rather than a single Line, so the effect setup cost gets amortized across a series of Lines. With each call to Draw, you can pass in a constant radius and/or color to use for all the Lines, or you can have Draw use the individual radius / color stored in each Line. All the Lines have to be drawn with the same technique, though.
I also threw in a Line.DistanceSquaredToPoint() method that returns the distance (squared) from the "true" line segment (ignoring the radius) to a Vector2D. That method isn't used by the demo, but could be handy for collision detection.
One thing that's not perfect about these lines is that you get an artifact with the blurring where two Lines' endpoints overlap. The blur draws once for each endcap, and since the alpha blending uses an additive effect, you end up with a slight "bump" in the blur. I couldn't find a blending mode that would eliminate this artifact. A more sophisticated implementation of the Line code might be able to avoid unnecessary double-drawing of the endcaps, but in most cases it doesn't seem to be a problem.
Also, the blur is currently set to a constant fraction of the line radius, so it may be too little or too much blur depending on the zoom level and/or line radius. It should be dynamic based on the thickness of the line in screen space, in order to blur just the right amount.
The LinesDemo program (attached) shows some examples of various usages of the Line code. I've only tested it on Windows so far. It requires a graphics card that supports shader models VS 1.1 and PS 2.0.
The controls are:
- Left stick or arrow keys: move camera
- Right stick or A/Z keys: rotate camera
- Shoulder buttons or S/X keys: zoom camera
- Start button or space key: reset camera
- A button or W key: toggle wireframe
- B button or B key: toggle alternate drawing style
- Back button or Esc key: quit
Feel free to use this code in your own projects and customize it as you like to work the way you want. Let me know if you find it helpful!