wishlist contact sol Sol::Breakdown

tAAt 2013 new year demo breakdown writeup

This one's going to need some background, so here goes. One of the open secrets in the demoscene is the background of the demogroup tAAt. The thing is.. people on the demoscene work behind some alias or artist name (like my alias, 'Sol'). Eventually things tend to get a bit too serious, and you might want to just do some crap and release it and not worry about your "primary" alias as much. Most people joined tAAt on this premise; an alias for an alias, so to say.

So anyway, one of the traditions in tAAt has been to make new year demos, and I'm responsible (stretching that word quite far) for about half of them over the years. Most of my new year's tAAt demos have been based on a single idea, and I ran with said idea every time, whether it was a good one or a bad one in the end.

There have been more misses than hits, as might be expected.

Anyway, as I was shopping for music for this year's demo, !Cube said he had made music a couple years ago already, but I had refused to use it because it would have required more work than I was ready to put into it at the time.

Which was true.

The thing is, !Cube's music is.. well.. let's just say I've ran out of superlatives over the years. Whenever I'm writing a demo to his music, I have to push myself to feel that I'm giving the music the respect it deserves. Whether I've succeeded or not is another matter.

Anyway, I ended up using his music this year (again), and the result was one of the better tAAt new year demos (again).

You can watch a video capture of the demo on Youtube.

Process

My demomaking process starts from listening to the soundtrack, with my eyes closed, and getting a feeling of the music. I got the idea of a side-scrolling landscape, trees, and letters falling from the sky. This was the start of it. Then I loaded up the music to audacity to see some timings, but unlike most of my demo projects, I did not spend a lot of time worrying about timings.

After I had the basic structure together, with the falling letters and trees (generated with SnappyTree and meshes loaded with Assimp, which I removed from the project later on, opting for simpler binary mesh streaming), I figured.. let's go with a deferred shader.

Breakdown

Here's a single frame close to the start of the demo which happens to include all of the elements. Let's look at how it comes together.

Note that you can click on any of the images for the 1024x512 versions (which is the FBO resolution - or internal rendering resolution - used in the demo)

Shading the Letters

The meshes for the letters were a pain to do. They're made in Blender, and I spent a whole evening just separating and merging meshes so I could import them into the demo as separate objects.

The letters are (mostly) forward-rendered, with two layers. First, the base texture is a "fake chrome" map, which uses normals as UV coordinates, more or less. I mixed up some of the three coordinates until it looked good enough.

While doing the normals-to-hacked-UV-coordinates thing, I at one point added the normal values to pixel values for testing, and it looked so good I ended up including it. These are not raw normals though, but scaled down a bit.

Textures

Most of the stuff is simply single-textured materials with no lighting. This is what the demo looked (more or less) before I decided to go with a deferred shading solution. The floor was a checkerboard pattern I discarded later on just so I didn't have to write yet another shader..

A little trick I tried at one point for no real reason was to multiply the colors with themselves, and this gave such a nice, dramatic look to it, I ended up including it, after toning it down a bit. The effect is subtle, but it's there.

The shaders output to multiple render targets: color, depth and original coordinates for shadow map. I also exported normals at one point, but did not end up using them at all. I could have saved one render target by combining the depth with the original coordinates, but, meh.

Z-Fog

The original floor had a checkerboard that dimmed towards the horizon, and I wanted a similar effect. Not wanting to write yet another shader, I simply made stuff darker the further they are from the camera. Yes, it's cheap and nasty but.. it works.

(Note that most of the stuff here is done during a single render pass on a single, rather complex shader, so most of the images here don't really exist during rendering, and were generated for demonstration purposes only).

Ambient Occlusion

For a bit more shading, I added a SSAO solution. I first copied some state-of-the-art shader in the project, but never really got it working well (it tended to make stuff glow instead of just darkening), so I replaced it with a dummy solution that doesn't quite work right. The effect is quite subtle in the final image though, so the quality doesn't matter all that much. It does make it easier to tell the branches apart though. There's also no noise texture, I just copied some low-quality noise function from some forum post.

As a coder, I really like SSAO as it's one of those "low-cost, high-impact" things. Artists may disagree, and they're entitled to.

Bloom

Since there were going to be fireworks (and the glowing logo), there had to be bloom. The bloom is made with the simple render + generate mipmaps + add all mipmaps together technique, and the real trick was to control just how much stuff bloomed. Originally, everything was on full blast, which made the scroller look horrible (one of the bad sides of the add-the-mipmaps method) and the logo bleeded over everything (and I mean everything. The whole screen was white)..

So, when rendering things out, I control the bloom with the alpha channel. The scroller is very toned down (but still has a very subtle bloom), the stars and logo are about midway there and the fireworks are at full blast (just compare the fireworks in the bloom image and the plain texture image!).

Talking of fireworks.. I considered doing some particles in photoshop, but, again, felt too lazy to write yet another shader and made these little spinning triangles instead. I like the way they turned up.

Shadow Maps

While at it, I figured I might as well add projected shadow maps. Tweaking the position of the light to be such that the shadows are not too distracting took more time than I had anticipated (well, actually, I didn't expect this to be a problem in the first place).. figuring out how to do shadow maps in a deferred shader was another little thing as there don't seem to be many tutorials about that around, but it was pretty simple in the end.

Yes, I'm aware the shadow maps are buggy. Did you notice while watching the demo? I wouldn't.

Vignette

To finish things off, everything is still run through a simple vignette that darkens things towards the corners and edges, and reduces the feeling that this is all there is to it (even though that's a very true feeling..).

Camera Shake

Now to some less-visual things.

The camera shake is implemented by changing the look-at matrix (both 'eye' and 'at' coordinates) up or down based on a simple curve: on each physics iteration, the camera shake is multiplied by -0.9. Since the physics loop runs at a different pace than the rendering loop, the result isn't quite as severe as the above graph suggests, but it's strong enough for youtube to suggest image stabilization..

Whenever a letter falls down to the desired level, it's motion is added to the camera shake (with a tweaking multiplier), so that subsequent drops make the shaking worse. As the project progressed, I kept making the camera shake less and less intense.

Background Scroll

The background stars scroll at a specifically designed curve which is shown in the picture above. When the demo starts, the stars are still, and the viewer concentrates on the sideways-scrolling stuff. Slowly the scrolling ramps up, and unless you've been paying close attention to it, the appearance of the logo will be a surprise. Finally the scrolling slows down before stopping, to avoid jarring movement.

Particles

The firework explosions are made by generating a few hundred particles at a point with a random motion direction and a certain speed.

The speed is then reduced on each physics iteration. The result in particle position is graphed above; when young, the particle moves fast, but then slows down, giving the illusion of an explosion. A slight gravity constant is also added on each cycle.

Beginning

I don't know why, but the sideways scrolling sweep just felt like a nice way to start.

Ending

The ending was inspired by the music; I thought of a very old television set shutting down. The implementation was simple; when doing the final "composition" render, I simply scaled the output rectangle in the vertical dimension, and then drew an additive quad on top of that (the only use of actual alpha blending in the whole demo!) to simulate the white-out.

Further Plans

I also considered adding dust particles when the letters dropped, as well as adding music sync to the letters, as well as the early fireworks, but .. well. I could have spent the rest of eternity polishing the demo, but there was fairly little to be gained at this point.

Compatibility

The whole demo is an OpenGL 2.0 application. Yes, it uses the built-in gl_ shader variables a lot, which is bad form, but hey, it's just a tAAt demo, right? "Works on my machine" and all that.

I ran it on my wife's computer which has an ATI graphics card (on purpose, so I can do compatibility testing), and boom - the demo looked weird. It looked as there would be 32x32 pixels or something. I updated the drivers and the demo worked fine.

Some laptop owners (who don't really have the option of updating their drivers) have since complained about the exact same result, and at this point I can do little but shrug about it. At least you can watch the demo through YouTube =)

If I were to write a "real" deferred rendering engine, I'd aim for OpenGL 3.3 (at least). But overall, this was a pretty successful experiment.

Comments, questions, etc. appreciated.

Site design & Copyright © 2014 Jari Komppa
Possibly modified around: January 05 2014