rss feed of the blog sol stuff discord follow sol email sol Sol::Breakdown

tAAt 2022 new year demo breakdown writeup

On this new year I preset a voxel or two for you:

Like all my new year demos, this is one where I pick an idea and run with it, regardless of what the result is.

I had a few different ideas this time. Thought about some rye fields I bicycled by in the summer, how the more or less perfect lines made interesting patterns. Pondered about generating 3d fractals. Not wanting to get into raymarching, I figured I'd try voxels instead.

Initially I wrote the voxel generator on the CPU side; I'd have a 64x64x64 array that says if a cell is alive or not, and then a rather complex code scans through that array and figures out where the start and end faces are; only needed geometry was generated (I did not backface cull it at that point though, which might have saved some resources). This worked pretty well, except that all that vertex data needs to be transferred to the GPU every frame, and once you start getting to gigabytes per second scale, things aren't running all that well.

Funnily enough, that code hit a visual c++ compiler bug that Microsoft did not manage to patch in time, even though they've known about it for a year or so. I guess it wasn't a priority.

Eventually I figured that by splitting the vertex generation separate from UV and normals, the compiler would be happy.

After the first release candidate I looked at the below-30 fps frame rates and figured I'd have to do something about it.

I looked at different approaches I could take, reading up on geometry shaders and so on, just to realize that geometry shaders are pretty much in the wrong place in the rendering pipeline.. so I figured I'll just use instancing.

So I reimplemented the whole thing using instanced rendering, ditching all of that complicated code and just practically rendering all 256k cubes every frame. And it runs fast. For the more technically minded: I use gl_InstanceID for cube positions and each instance has one float worth of data saying what the texture coordinates should be or if the vertex coordinates should be set to zero, practically eliminating the geometry.

So release candidate 2 ran quite bit faster. A couple of the effects were still a bit slow, so I hit them with an OpenMP pragma to use several threads. I could have written my own threading code which may or may not have been more efficient, but that's a compiler option and a single code line addition, and seems to work.

I actually first tried visual c++:s own "parallelization hint" but that didn't do anything. OpenMP pragma apparently isn't a hint but an order, so it got the job done.

After getting things to run consistently over 100hz on my not-so-bleeding-edge-anymore PC I experimented with increasing the voxel resolution. Upping everything to 128x128x128 dropped the framerate again (probably due to the fractal calculations). 80x80x80 ran pretty well, but the shadows blew up for some reason, and it didn't look all that much different, so I reverted back to 64x64x64. I guess I missed some hardcoded 64 somewhere.

For music I approached Antti Tiihonen who has made music with modular synths (more or less) recently, even some live gigs, and asked if he'd want to make some modular chaos as the soundtrack. He produced two loops, and I asked him what about if we use both, and mix between the two? After testing it out he said that's ok.

The music gets mixed between the two tracks using a triple sine curve, so that when the sum is 0, only one of the tracks is played, and when the sum is 1, only the other is. The demo's background color also subtly changes; when it's tinted red, we're on one edge and when it's tinted blue, the other. The curve change so slowly that it's pretty hard to hear (or see) it happening, but it's there. So if you leave the demo on for a day or two, it should(tm) generate unique (but familiar) output all the time.

I wouldn't leave it on for over a couple of days, though, as the timer uses a 32 bit signed int, so it'll probably go wrong at some point. Since most people won't be running this over a few minutes (and most of you will just watch the capture on youtube anyway), I'll just leave that in as an easter egg..

The effects themselves include some drunkard's walk worms, 3d julia fractal, couple attempts at mandelbulb (which are the heaviest, calculation wise), a simple wavy landscape and some metablobs.

Art wise the cubes use a single texture split into 4x4 regions, so there's 16 different "colored" cubes. Initially I used just a random colored texture but that was just too garish, so I ran it through my Koalizer filter and hey, it's immediately more pleasing to the eye. The space filler cubes (with the "tAAt" texture) were run through a similar (but not the same) filter. I added black borders to the tiles for effect.

And for rendering there's a moving light source and shadow maps, which explains why things get so dark all the time. I suppose I should have tweaked the light's movements to look nicer. Maybe visualize the light source too. But there's only so much tweaking you feel like doing, and you have to let go at some point.

I think that's just about everything of note. Have a better one!

Comments, questions, etc. appreciated.

Site design & Copyright © 2022 Jari Komppa
Possibly modified around: December 30 2021