Sunday, January 9, 2011

Efficient way of drawing outlines around sprites

Hi,

I'm using XNA to program a game, and have been experimenting with various ways to achieve a 'selected' effect on my sprites. The trouble I am having is that each clickable that is drawn in the spritebatch is drawn using more than a single sprite (each object can be made up of up to 6 sprites).

I'd appreciate it if someone could advise me on how I could achieve adding an outline to my sprites of X pixels (so the outline width can be various numbers of whole pixels).

Thanks in advance,

  • Greg.
  • I'm not sure of effiency, but the easiest way I can see would be to draw a larger version of the sprite in the colour you want selected first. Draw the sprite on top of that. You'll only see the edge of the first sprite, giving the effect of selection.

    EDIT: However, as you can see from the comments, this isn't a good idea.

    Iain : Just scaling up a sprite doesn't give a true outline
    The Communist Duck : I don't think it would, but it would be easy.
    Joe Wreschnig : Plenty of things are easy but don't solve the problem. For sprites with any kind of interesting silhouette a scale-up will produce absolute garbage.
    AttackingHobo : Scaling up will only look good for solid square or circular objects. If the shape is really wide, or tall, or has gaps it will look really bad.
    dash-tom-bang : Scaling up will give better results than doing nothing and would also be a useful step along the path to "perfection." While the graphical results won't be perfect, if this were for an internal tool it may be Good Enough.
  • I guess you need to draw all the pieces who each object to a single sprite first. Then I think you'd have to write a shader to detect the edges of the sprite a draw a pixel where-ever it finds an edge. I expect there must be some shaders out there already to do this, which you could either use or port.

    Joe Wreschnig : This kind of shader is surprisingly annoying to write, I've heard (we used it in one of our 3D games and kept running into unsightly edge cases). One thing you might want to consider is encoding the outline directly into the sprite texture with a specific color like (0, 255, 255, 0), and just have the shader transform that color when the sprite is selected. Then the shader is a trivial color change, and you have a high level of artist control over the outline and any details you want.
    From Iain
  • The simplest brute force approach is to build two copies of each sprite, a normal and a highlighted one. Then just swap them when highlighted.

    If you've got memory to spare there is no need to get more complicated then that. Plus artists have complete control over the look when highlighted so you can do an outline or anything else you'd like.

    Chris Howe : I think part of the problem is that the OP says each object can be made of multiple sprites. So I guess the outline has to be the outline of the combined object, instead of having a separate outline around each component sprite.
    AttackingHobo : Chris, it does not have to be the outline of the combined object, and it will work fine for an object made out of multiple sprites. Just do exactly what wkerslake said, but make sure you draw the highlighted sprites behind all of the normal sprites for that object. That way no matter how many sprites an object is made of, the highlight will only use slightly more than double the draw time for that object, which should be a lot less than generating the outlines at rendertime with the combined sprites.
    From wkerslake
  • How about for each sprite, also have another sprite that's an outline of the base sprite. When drawing an outlined object, draw the base sprites, then make a mask of the combined rendering, then draw the outline sprites excluding the mask.

    Chris Howe : Why not just draw the outline sprites first and draw the regular sprites over them?
    Joe Wreschnig : One reason not to do this (or Chris's suggestion) is because it uses twice as much texture memory; another reason is that the artist workflow is crap because you need to update two files every time you change a sprite.
    Chris Howe : Well yeah, you ideally wouldn't have two actual sprite assets. If you're using alpha-testing for your sprites you could put the outline into the sprite and give it an alpha slightly higher than your transparent areas. Then by controlling the alpha test reference value you can control whether the outline is visible or not. All I was saying is that if you have 2 versions of the sprites (whether its 2 assets or 2 states of the same asset) you should draw the outline version first, and then the normal version. This way you don't require any fancy shaders, masks, or whatever.
    jpaver : Chris's idea has merit; in addition having the artist update 2 files (or even the same asset) can be avoided if you produce a tool to generate the alpha for the outline sprites that runs as part of your asset/content pipeline. Texture memory may or may not be a problem, but separate outline sprites should be highly DXT compressible; possibly 1/6th the size of the original texture in video memory and with no fidelity loss.
    From gray
  • Depending on requirements, what might also be effective is just creating an outline on-demand for the sprite. I'm assuming your sprites have transparency, and are irregularly shaped rather than just being rectangles (while this would work fine for that, outline rectangles should be Trivial).

    when selected:
       outline = new sprite canvas of appropriate size
       for sprite in object:
          # use larger numbers for thicker outlines
          for x in (-1, 0, 1) and y in (-1, 0, 1):
             render sprite mask into canvas at x,y with desired color
    

    Note that you don't need to do this every draw (although I suppose you could), but just create the new outline sprite when switching sprites.

    Kaj : Yes, this is what I wanted to suggest as well. Render the masked sprite 1 pixel to the left, right, top and bottom of the original sprite, under the existing sprite. Many games used this method to outline rendered text, or with only 2 of the 4 positions to create a drop shadow.
  • By far the easiest way to do this (so probably the best way, unless you are really strapped for performance) is to have two copies of your sprites.

    • The regular version
    • A"fat", uncoloured version - basically a white version of your sprite X-many pixels "fatter" than the original.

    Draw your entire object using the "fat" version, then draw the regular version over the top.

    By making the "fat" version white, you can use SpriteBatch's built-in colour tinting to change the selection colour dynamically.

    To generate your "fat" verison I recommend writing a Content Pipeline Extension that can automatically take your original sprites, read their alpha channel, create a new alpha channel by sampling the maximum alpha channel in the original image X-many pixels around each pixel, and setting RGB = (1,1,1).

    You will have to make sure your sprites all have sufficient transparent border to add the outline (you could check this in the content processor - and even make room if necessary).

    If you only have a few sprites, then you could just use a good image editor (GIMP, Photoshop) and do it by hand: Alpha channel to selection, expand selection, selection to alpha, fill colour channels white.

0 comments:

Post a Comment