If you look at the artwork in old 2D 8-bit games, you see a lot of noise and dithering in the hand-drawn bitmap art. For an example, take a look at the grass or concrete in this Transport Tycoon screenshot:

Transport Tycoon screenshot

When the gaming world moved to 3D 32-bit vector art, we lost some of that level of detail. We got lots of smooth areas. Eventually we mostly got the detail back by applying textures to the polgons. However, it often looks worse to me than the old hand-drawn art.

With my Flash experiments, I've been playing with 2D procedural vector art, and I've been trying to figure out how to make it look nicer without drawing textures by hand. The simplest thing I found has been to apply noise to the art. On the left is some terrain without noise and on the right is some with noise:

Test image without noise Test image with noise

I like the noisy version much better.

The noise layer is fairly easy to apply; I use BitmapData.noise() to generate it, and then use BlendMode.ADD to add it to the original layer.

  var noiseTexture:BitmapData = new BitmapData(128, 128);
  noiseTexture.noise(Math.round(Math.random()*65536), 
                     0, 8, 7, true);
  var noise:Shape = new Shape();
  noise.graphics.beginBitmapFill(noiseTexture);
  noise.graphics.drawRect(0, 0, size, size);
  noise.graphics.endFill();
  layer.draw(noise, null, null, BlendMode.ADD);
  noiseTexture.dispose();

It's nice in that it inherits the color already there; the noise doesn't impose its own colors. However, this only works nicely on my background terrain, and it feels somewhat slow on my low-end machine.

For foreground sprites, the noise layer doesn't move when those sprites move or rotate. An alternative would be to draw the noise on top of my sprites, using Graphics.beginBitmapFill(), but that would require that I have a way to compute the outline of my procedural art, so that I can draw the noise on top as a polygon. Another alternative would be to use bitmap fills for every polygon, but that requires that I have a noise bitmap for each color. And yet another alternative is to draw every polygon twice, once for the color and once for the noise.

With Flash 10 I had hoped that the pixel shaders would allow me to apply noise to anything. I played around with them a bit. The shader receives the output coordinates in a function outCoord(), and can compute a color for that location. It can optionally include parameters (like a noise bitmap). The big problem is that the output coordinates are in screen space. This means that when the sprite moves or rotates, or if you zoom in, the noise would stay fixed relative to the screen. I tried both using shaders for fills and shaders for filters, and neither gave me what I wanted.

That's a serious problem for my use. To address this problem, I can pass in additional parameters like rotation and offset. However, I have to re-fill the shape every time I change the shader parameters. Even worse, the pixel shader is recompiled every time you fill.

So it looks like pixel shaders in Flash 10 just don't do what I want. I want a way to get the pixel's location in the sprite's coordinate system, after transforms are applied, but instead I only get the screen's coordinate system.

I think my best bet for performance is to not apply noise to vector backgrounds (applying noise to a bitmap won't impact performance). This will make me sad but smoothness matters a great deal. I should also try using tiles to see if that is any faster. For foreground objects, it's probably not too bad to draw everything twice, but I'll have to test this. It may not matter if I switch to bitmap sprites eventually; they'd let me draw a lot more details.

Labels: ,

8 comments:

Anonymous wrote at June 29, 2009 5:23 AM

Concrete in Transport tycoon? How did you do that? Looks like somebodies stopping access to that coal mine from their competitor - I see an anti-trust law suit in the mits! ;-p

...I used to love ramming their lorries off of the road with trains; Oah the satisfaction. Although in the original game the competitors used to wind me up as they're ruin the whole landscape digging up miles and miles of land and trees to place a few tracks - If I did that the town'd stop me from building so I say use every tick in the book against em!!!

Amit wrote at June 29, 2009 2:40 PM

Hi Anonymous! The concrete comes from a third party graphic file add-on. I placed it around “my” coal station to keep competitors away ;-)

Open Transport Tycoon has some new computer AI. I haven't tried it yet but I'd guess that they build much better tracks than the original game. :-)

Jotaf wrote at July 04, 2009 9:16 AM

How about trying to render the procedural sprites (which are vector-based) to bitmaps, applying the noise and then use just those bitmaps? (Or maybe that was already implied in youy post!)

For the background, drawing the noise at once seems to ask for too much memory, if you want decent sized maps... Is there a function to ask for the noise value at a specific location, without computing the whole noise image? If not, maybe it wouldn't be a bad idea to calculate it "by hand" in the shader. Otherwise, I don't see how you could get a continuous noisy background the size of many screens :) Just bouncing around some ideas.

Amit wrote at July 04, 2009 10:32 AM

Hi Jotaf! I may try rendering to bitmap sprites; the main disadvantage is that the edges of the sprite can become jagged with rotation.

For the background, my hope was to use several noise tiles scattered in a way that wouldn't make it obvious it's a pattern. But I haven't tried that yet.

makc wrote at October 16, 2009 10:47 AM

you could also add some kind of simple edge detection using convolution filter to serve as mask to not add noise on edges, but it looks good any way.

Amit wrote at December 02, 2011 5:58 PM

Skyrim now has a mod for adding noise: http://www.skyrimnexus.com/downloads/file.php?id=1598

Grrrrrrrrr wrote at July 24, 2012 2:49 PM

I actually used noise in Scarygirl (scarygirl.com) a lot. All art (most) was vector, straight from Illustrator, and we load it as such before adding noise, and sometime a glow to it to display in Flash. The game was 2mb per level instead of 50mb and looked a LOT better!

Anonymous wrote at December 13, 2018 11:45 AM

I couldn't refrain from commenting. Very well written!