{"id":3639,"date":"2015-04-17T20:17:28","date_gmt":"2015-04-17T10:17:28","guid":{"rendered":"http:\/\/www.vectorstorm.org\/?p=3639"},"modified":"2015-04-17T20:17:28","modified_gmt":"2015-04-17T10:17:28","slug":"bloom-madness","status":"publish","type":"post","link":"https:\/\/www.vectorstorm.com.au\/2015\/04\/17\/bloom-madness\/","title":{"rendered":"Bloom madness"},"content":{"rendered":"

\"Screen<\/a>I took a day off from development today.\u00a0 But that doesn’t mean that I was away from the game;\u00a0 just that I wasn’t working on the things which were actually on the task list for the milestone 10 build (I’m almost, almost through it, now;\u00a0 only three major tasks left to complete).<\/p>\n

Instead, I spent a lot of time looking at the old VectorStorm “bloom” effect.\u00a0 I thought that I’d finally put it to bed and fixed all its problems, but I’ve recently been noticing that the engine’s standard bloom filter was having lots of problems when making narrow lines glow, particularly at low resolutions (say, about 800 pixels tall or less).<\/p>\n

Short version:\u00a0 I figured out a solution.\u00a0 The screen here is what it looked like the first time it ran.\u00a0 For the very first time on VectorStorm, I actually had to tone down<\/em> the bloom effect (it’s now down to 3 passes, from 5).\u00a0 Which means that MT2 (and any future VectorStorm games) will now be slightly less demanding of your GPU than in the previous milestone builds.<\/p>\n

Below the fold is the gory technical details of how it works.<\/p>\n

<\/p>\n

The basics of bloom<\/h2>\n

Fundamentally, a “bloom” filter works by taking a copy of the screen image, blurring it, and then adding it back on top of the screen image.\u00a0 The tricky part is the “blurring it” step.<\/p>\n

(In the case of VectorStorm, we don’t actually blur the screen image;\u00a0 instead, while rendering each frame we draw a separate “glow” image.\u00a0 We blur the “glow” image and add that over the screen image the same way that standard games do bloom.\u00a0 But it’s the same general idea).<\/em><\/p>\n

A naive “blurring it” implementation would draw a blurred version of the screen by, for each pixel, checking the screen color of all the nearby pixels, and then averaging those color values together.\u00a0 If we want to blur by 4 pixels (for example), then each output pixel needs to sample every pixel inside a 9×9 box (that’s the pixel itself, and every pixel within 4 pixels in each direction).\u00a0 That’s 81 texture samples in total, per pixel.\u00a0 This works.\u00a0 But it will also be extremely slow, since sampling all those pixels for every screen pixel is slow.\u00a0 As a general rule of thumb, you can generally get away with doing up to three or four texture samplings in a shader — more than that is starting to push your luck.\u00a0 81 is completely beyond the pale, and that would only get you a four pixel bloom;\u00a0 which is pretty tiny by anybody’s standards!<\/p>\n

So instead of doing that, people will generally do a “separable blur”.\u00a0 In a separable blur, you do your blur in two passes, instead of one.\u00a0 So instead of sampling that 9×9 box of pixels for each pixel, you only sample horizontally;\u00a0 you sampling a 9×1 box instead of a 9×9 one — just 9 pixels from the screen image, instead of 81.\u00a0 And then you take the texture produced by that horizontal blur, and do the same thing again, only blurring vertically.\u00a0 The result of taking an image and blurring it horizontally, and then blurring vertically is actually surprisingly close to doing a full Gaussian blur.\u00a0 This means you’ve done the whole blur process with only 18 texture lookups per pixel, instead of 81.\u00a0 That’s a huge improvement! (though still probably much too expensive for a real game)<\/p>\n

And you can go further than that;\u00a0 you can take advantage of a cunning maths trick and do a “linear separable blur”, where you don’t even sample all nine of those pixels.\u00a0 Instead, you sample in between<\/em> the pixels, and let the GPU implicitly do some of the texture samples for you, in a way which is effectively free.\u00a0 This can bring a 9-wide blur filter down to needing only 5 texture samples per pixel.\u00a0 Which is starting to sound downright reasonable!<\/p>\n

But again, we still only have a blur that spans four-pixels, which is pretty small, and we’re doing about as many texture lookups as we can afford to do.\u00a0 What if we want our blur to be bigger?\u00a0 (the absurdly huge blur in the screenshot above is about 40 pixels).\u00a0 Well, what people normally do is they take the screen image, and make a copy of it that’s half the size.\u00a0 They then blur\u00a0both<\/strong> the large and small versions of the screen image, and blend them together.\u00a0 When we’re operating on a half-size image, the 4-pixel blur corresponds to 8 pixels of blur on the full-size image.\u00a0 So we get blur that extends a further distance, without needing to do an absurd number of samples.\u00a0 And if we want our blur even blurrier, we can halve the size of the screen image again, and have another, even blurrier image to include in the composited blur image.<\/p>\n

This last bit is how virtually every modern game does its bloom effect.\u00a0 And it’s how VectorStorm has done its version of the effect as well.\u00a0 (We use just three texture samples each, at five levels of “smaller screen image”)\u00a0 But there’s a problem.<\/p>\n

The problem with bloom<\/h2>\n

I casually mentioned “take a half-size screen image” before, as though that was a trivial thing to do.\u00a0 Actually, it isn’t always trivial.\u00a0 When you have fine image detail, that fine detail tends to get lost when copying the image into a smaller picture.\u00a0 For most games this isn’t a big problem.\u00a0 But in VectorStorm games, I use a lot of very thin lines, which I want to glow brightly and evenly.<\/p>\n

And this is a problem.\u00a0 Let’s say that I have a pure white two-pixel-wide line in my screen image, over a flat black background.\u00a0 And let’s say that I want to make an identical version of the screen image with half the image size (for use in the blurring process, mentioned above).\u00a0 If both pixels of the two-pixel-wide line happen to line up precisely with one of the pixels on the smaller image, the line will show up as a single-pixel-wide pure white line.\u00a0 But if it doesn’t line up precisely, then it will appear as a two-pixel-wide grey line on the smaller image (it’s technically “in between” the pixels of the smaller version of the screen image).\u00a0 And there’s no really good solution to this;\u00a0 there are some very clever techniques which try to be smarter about downsampling textures into smaller images without losing image detail, but nothing fast enough that you really want to be running it in real-time.<\/p>\n

So if this line is moving around on the screen (for example, because you’re dragging its window around), its glow will be flickering bright and dark, as it alternately lines up and moves out of sync with the pixels of the various smaller versions of the screen image.\u00a0 It’s a very unpleasant, strobe-like effect.\u00a0 And there’s no obvious way to fix it.\u00a0 And it seems like nobody else on the Internet has posted a solution for the problem.<\/p>\n

Until I had this thought (and this is where the exciting new idea comes in):<\/p>\n

“Self,” I thought, “I’m making all these smaller copies of the screen image, then blurring each of them, and then combining them back together.\u00a0 And the problem is coming from aliasing in the smaller copies of the screen image.\u00a0 And the aliasing is caused by detail levels which are too high.\u00a0 But I’m just going to blur all these images anyway.\u00a0 So what if I blurred each screen image before<\/em> copying it to the next smaller version, instead of after?\u00a0 That way, the images have already been blurred when the copy happens, which means that there’s no “high detail” to be lost during the copy.\u00a0 <\/em><\/p>\n

The answer, of course, to why I don’t do that is that doing that would mean that I’m no longer technically doing a correct Gaussian blur;\u00a0 the screen image ends up multiply blurred, and smeared much further out than it really ought to.\u00a0 Which is what you can see in the screenshot above;\u00a0 that’s blur-before-copy instead of blur-after-copy, using exactly the same settings.<\/p>\n

But I don’t actually care that what I’m doing now isn’t mathematically Gaussian any more — if I just tone it down a little, it looks great.\u00a0 And since things blur further out in fewer passes, I can get away with doing fewer blur passes, and still have an equivalent glow effect.\u00a0 Plus, doing the blur first means the old “flicker” effect on moving lines is now completely gone.\u00a0 Almost makes me want to go back and update all the old games to use the new engine, just so their bloom would be fixed.<\/p>\n

Almost.\u00a0 But there are more important things for me to be working on!\u00a0 I’m talking with artists, I’m almost ready for milestone 10, and Greenlight will come shortly after that.\u00a0 It’s all finally coming together!<\/p>\n","protected":false},"excerpt":{"rendered":"

I took a day off from development today.\u00a0 But that doesn’t mean that I was away from the game;\u00a0 just that I wasn’t working on the things which were actually on the task list for the milestone 10 build (I’m almost, almost through it, now;\u00a0 only three major tasks left to complete). Instead, I spent…<\/p>\n","protected":false},"author":1,"featured_media":3640,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":""},"categories":[4,3],"tags":[],"jetpack_featured_media_url":"https:\/\/www.vectorstorm.com.au\/wp-content\/uploads\/2015\/04\/Screen-Shot-2015-04-17-at-6.48.04-pm.png","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/po9WK-WH","_links":{"self":[{"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/posts\/3639"}],"collection":[{"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/comments?post=3639"}],"version-history":[{"count":1,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/posts\/3639\/revisions"}],"predecessor-version":[{"id":3641,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/posts\/3639\/revisions\/3641"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/media\/3640"}],"wp:attachment":[{"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/media?parent=3639"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/categories?post=3639"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.vectorstorm.com.au\/wp-json\/wp\/v2\/tags?post=3639"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}