Category Archives: Programming

Muse3d – My (not so) small deferred lighting 3d engine

Around about Christmas of 2014, I was feeling a little restless. Working on the book in my spare time had meant that I hadn’t done any real coding, bar physics demos for about a year. I was feeling a bit burned out writing the book and needed a small distraction, something of a programming challenge to help me relax.

I came up with the idea of writing a quick and dirty 3d engine in straight C that implemented deferred lighting. It would be something that I could use to quickly test out lots of little idea here and here, maybe even prototype a game idea or two. One of the problems is that, although I have written various bit and bobs to do with rendering in various games, I had never really had a “proper go” at an entire rendering engine, especially once using modern multi-pass rendering techniques.

I chose deferred lighting over deferred shading as I wanted to easily support a varying number of material types, as well as something that would scale down well to hardware where frame buffer/texture bandwidth may be limited (for example; my Mac Book Pro with it’s Intel GPU or mobile platforms).

The deferred lighting implementation consists of three main passes:-

  1. Pre-light Pass – Geometry is rendered, outputting only the depth and surface normal information.
  2. Light-pass – The depth and normal information from the pre-light pass is used to calculate lighting information. This pass consists of two separate stages; the ambient stage which renders a full-screen quad for a global ambient light-source and the actual finite light sources, which are rendered as 3d-geometry. The resulting albedo and specular values are rendered into the light accumulation buffer. Additive alpha blending is used so that pixels affected by more than one light source are lit accordingly.
  3. Material pass – The geometry is rendered again, with the added difference in that the depth-buffer is not written to. The depth test is also different from the Pre-Light pass. the “Depth Equals” test is used so that it passes only on pixels that match the values stored in the depth buffer. Each pixel that passes the depth test has its material evaluated to determine the diffuse and specular components which are combined with the light accumulation buffer to determine how bight the pixel is.
muses_targets

Test render showing the various render targets generated and used during rendering. The targets are Light accumulation buffer (top-left) Final colour buffer (top-right), Camera Space normal buffer (bottom-left), Depth buffer (bottom-right)

And so started my “Muse3d” side-project. Progress was really quick and a lot of fun, too! I had forgotten how fun working in good ol’ C was. How much closer “to the metal” it felt. But as much fun as it was, working in C was also a limiting factor. Writing abstracted interfaces in straight C was becoming a pain, and the lack of templates and inheritance complicated matters somewhat. After a few days I decided to move the whole thing to C++ so I could use language features such as templates and encapsulation and inheritance.

After a week or so, I had something that was rendering models using deferred lighting. It was far from complete, but it had a lot of “under the hood features” such as :-

  • Custom memory allocation – I wrote a nice little memory manager that pre-allocated chunks of memory from the underlying OS and used TLSF to manage the heap within each chunk.I did this for no real reason, other than to help me track memory usage at a later stage.
  • Shader caching – The engine uses one big “uber-shader” for all of the rendering. The shaders are compiled at run-time as needed for each model/material/pass combination. However, these shader variations are only compiled once and cached for re-use.
  • Render batching – To avoid shader and material switches happening too often, draw calls are batched by relevance, in the following order
    1. Pass
    2. Shader
    3. Material
  • Auto insertion into relevant render passes – Not such a big deal, but how models are rendered by each pass is a completely opaque process. I guess this is a standard thing to do for any modern multi-pass renderer, but it was amazing to see how one call to render a model instance would then feed into all of the passes and batching.
muses_initial_render

Full-size colour render target. The final result of the pre-light, light and material pass.

I left the project alone for a while as I concentrated on my book again. I’m still working furiously on the book, trying to get the dammed thing finished but now and again, when I’ve hit a block, working on Muse3d helped relax me a little.

Once of the issues with the engine was that in order to save time, I hadn’t abstracted the graphics code, the bit that actually calls platform specific functions to tell the GPU to do things. I had just written some basic wrappers for things like buffers and textures, and the rest was raw OpenGL calls. However, this was going to present me with a few issues in the future and I decided to fix that. It took a couple of weeks of working on the odd bit here-and-there to achieve this.

I cursed my lack of foresight during this process. But at the same time, having a working 3d-engine (even if it wasn’t feature complete) meant that I could do the work in small steps, and verify that I hadn’t screwed anything up as I went a long. Now the render code and graphics code are completely separate. It was a horrible task to have to do, but well worth the effort.

There’s still a bit of work to be done though. Although I have a working light pass, it doesn’t deal with some things properly such as light sources occluded by objects (the occluding objects appear lit when they shouldn’t be). My “to do” list looks a little like this:-

  • Implement stenciling for light sources during the light pass so that only objects within light volumes are lit (currently 60% done)
  • Implement light volumes for:-
    • Directional lights
    • Spot lights
    • Area lights
  • Custom directional & area lights
  • Implement LOD models for spherical lights
  • Calculate positions of pixels in camera-space, rather than using a texture to hold them. This means that the entire render system will only ever use a single colour render target.
  • Implement a shadow pass using stencil shadows
  • Add support for custom post-processing passes
  • Skinned mesh support using CPU and GPU skinning via Transform Feedback/Streamout
  • Implement support for uniform/constant blocks
  • Skeletal animation system

It’s a big list, but nothing that I can’t deal with given a bit of time. Right now my focus is on the book and once that’s done, assuming I’m not working on another book straight away, I can devote some time to the additional tasks. As it is, I’ll fit in work on certain features whenever I need an hour or two respite from writing pages and pages of text.

My Xcode wishlist

As a long term Visual Studio user (16 years and counting) the transition to Xcode has been a wee bit of a shock. While it’s great having a development environment provided for free, there are some things that irritate the hell out of me while using Xcode.

Rather than gripe about the problems, I’d rather make a list of the features that I’d like added to Xcode to fix the issues that get in the way of being productive.

  • Adding new files remembers where the last fold location you used. Constantly having to navigate to the folder you last used is irritating as hell, not to mention time time consuming.
  • Block-tab/edit – I’m sure there’s a way to do this and it’s probably some sort of twister-like key combination. But if I want to indent/unindent a block of highlighted code, I want to simply press tab and Xcode, like VS, will figure “hey, let’s apply the change to a whole block of code” or “let’s indent this code instead of deleting all the highlighted lines”.
  • Global custom properties across projects in a workspace. Oh dear god, that would be so nice. There’s nothing more error-prone and irritating than having to enter the same custom property for multiple projects within a workspace. I’m not expecting VS style property sheets (as groovy as they are) but just the ability to set shared properties would be very, very awesome.
  • Build dependancy lists – I’d like this. I’d like to have a list of projects within the workspace, and be able to tag which ones a single project is dependant on without necessarily linking to it. Why? Because often, I may be writing a dynamic library that is not hard linked to an executable or another dynamic library – it may be run-time loaded by another project in the workspace, in which cases, build-time linking makes no sense.
  • Please add a list of available macros that I can use in the project build settings. Pretty please! I’m sick of having to look this up in the on-line help.

That’s all I have for now. I’m sure more things will occur to me after writing this, but these are the main niggles that cause me to sigh (minus one luck, according to my Japanese colleagues) groan, moan, curse, squeeze the crap out of my stress ball or wander off to make a cup of tea because “I’m done dealing wit’ dat shit, yo'” (Imagine a middle aged Scottish guy saying that last bit).