ScrumbleShip is the most accurate space combat simulation devised to date. Gather resources, construct a capital ship out of individual blocks, then pilot it with AI or human help against other players.
We increased the number of polygons on screen by a factor of 10. Here's how.
Posted by dirkson on Sep 20th, 2013
Go ahead, click both of those images so they show up full size. Don't worry, they'll pop up in new tabs.
Ten days ago, my game looked like the image on the left. Today, it looks like the image on the right.
Wow! Looks a lot better, doesn't it? But every graphical improvement has a price... So how much slower does the game run now?
Well, all of these graphical improvements come at the cost of a huge performance improvement.
Before we start, let's stop for a moment and talk about voxels, just so everyone's on the same page.
No 3D object in ScrumbleShip is textured.
ScrumbleShip renders its world using voxels, which are the 3D equivalent of your monitor's 2D pixels. In ScrumbleShip, these are shown as small, untextured cubes. We also have "blocks", which are collections of voxels 16 long, 16 thick, and 16 tall. So every block we put on the screen is composed of 16*16*16, or 4096 tiny cubes. This allows for some cool tricks, one of which we'll talk about today.
Now on to the improvements!
The most obvious improvement between the two images is the view distance, which has at least quadrupled. Hundreds of blocks that used to be rendered as featureless blanks suddenly pop into full view.
The big thing that allowed me to do this was changing the order in which I hand voxels to the graphics card. Nearly every opengl tutorial I've seen makes at least passing mention of drawing the scene from front to back, but I had always discarded this as a potential minor optimization - Nice for some day, but not really vital for now.
I was very, very wrong.
It turns out that modern graphics cards are remarkably efficient when scenes are drawn front-to-back. For the average ScrumbleShip scene, they're over ten times as efficient. So where we used to struggle along at 20-30fps with 100,000 voxels, now we can see a million voxels at 30-40fps.
End result? Wonderful scenes like these, rendered at playable speeds:
When I originally re-ordered the voxels, I had some very interesting results. My render times instantly went "Non-linear". In other words, I could suddenly render 1 million voxels at the same frame rate as 100,000 voxels.... And that frame rate was 22 frames per second.
22FPS is... not awesome.
So what was going on? If I could render a million voxels slowly, why couldn't I render 100,000 voxels quickly?
The answer turned out to be in the vsync features of ScrumbleShip. Before the graphical update, more than 30fps was basically wasted effort, so I'd told one of our libraries to "sync" the image with every other frame your monitor displayed. Well, due to the particular sub-millisecond timings of these events, the best the engine could do was sync with a small handful of these events.
In short, turn off the vsync, and the framerate soars to 30-60fps.
Another large graphical improvement comes in the from of an absolutely tiny effect - I vary the color of each side of each voxel by 10%. This simulates some of the effects of diffuse lighting in the real world, and suddenly the 3D nature of objects "pops", and the tiny cubes actually look like tiny cubes.
Here's an example using some heavy damage on some factory blocks:
I do use a little trickery here - I color the "top" of the voxel a little brighter than the "bottom". This poorly simulates the way light in space would work, but it correctly simulates our everyday experiences walking around with the sun overhead, so it ends up looking more natural to the human eye.
This process is done entirely in something called the "Fragment shader" of the graphics card. This fragment shader is designed to manipulate hundreds of megabytes of textures across the entire screen. Since ScrumbleShip doesn't deal with textures, we've been badly under-utilizing the fragment shader. It's basically like having Vincent Van Gogh paint your house.
Since this lightening/darkening process is done within an under-utilized portion of the graphics card, it cost us 0fps - A free improvement!
Another change I made is the introduction of a Level-Of-Detail system. Previously in ScrumbleShip, any individual block was either "High Resolution" (16x16x16 voxels), or "Block Resolution" (1 big voxel). This created a somewhat jarring effect when trying to look at things in the distance. Because, well, you couldn't.
The big breakthrough was when I realized that people couldn't really see 16x16x16 blocks in the distance. Stuff got all blurry and it looks more like a lower resolution block, like maybe 8x8x8... Hey. Why not average some of the voxels and *actually* display an 8x8x8 instead of the 16x16x16 block? And after that, maybe a 4x4x4 block!
You can see the results of this system in the link above. The two images contain the same blocks, but the one with level of detail is miles ahead of the one without. The level-of-detailed blocks just end up looking slightly blurry.
Well, there are still optimizations to make, that's for sure. I doubt I'll find another 10x improvement... But then, if you'd told me I would find a 10x improvement two weeks ago, I would have laughed at you.
All these changes are in the latest "bleeding edge" release, and will be making their way into the full, released version in another week or two.
The most obvious improvement to make is to send less redundant data to the video card. Because of the amount and frequency of the voxel data we send, there's a tiny hiccup every 5 frames or so, causing that frame to show up on the screen for 1-2ms longer than it normally would. Not a huge difference, but it might be worth a few FPS to fix.
Other than that, I really think it's time to focus on features other than the graphics. With these changes, I've finally gotten the graphics to a point where they truly look and perform well. Maybe I should focus on combat? Atmospheric simulation? Ship stress simulation? Tell me your thoughts in the comments, please!