‘Zen Engine’ Source Code

What this is
This is the source code to a graphics engine I wrote about a year ago. Obviously I make no guarantees that it will be useful for anything, but maybe somebody out there will find it helpful. Halfway through development I got a 3DFX card so I ported it to Windows and included support for that. Consequently you’ll need Visual C 4 or 5 (I haven’t tested it with other versions/compilers). You’ll also obviously need a 3DFX card to use that part of the engine. The Windows code is lousy – I basically hacked a load of DOS code into an example DirectX example app. and fiddled till it worked. The code is reasonably portable but I made the mistake of supporting multiple rasterisers (software, 3DFX, etc.) at a very low level. This means much code can be re-used, but it’s difficult to take renderers’ peculiarities into account (for example the coordinate snapping GLIDE needs) in an elegant/efficient way; Quake 2’s DLL approach is superior, I feel.

The Engine
Essentially all this is an implementation of portal rendering, currently in vogue, and described copiously elsewhere. I have not studied other people’s code in great detail, but I feel it’s a reasonable attempt at implementing it. I should point out there is no physics code, and the only thing that’s rendered is the architecture: there are no monsters or anything, but I planned to z buffer them on, as everyone is these days, I suspect.

In case you particularly care, here is the ubiquitous boasting feature list:
 

  • 8 bit textures, each with their own palette
  • 16 bit rendering
  • Mip-mapping
  • Surface cached software rendering a la Quake
  • Bilerped 2 pass rendering with 3DFX
  • Coloured lighting in software and hardware
  • Translucent polys in software and hardware
  • Alpha-blended mirrors in software and hardware
  • Fogging in software and hardware (experimental)
  • Sky box support
  • A few texture coord. effects (moving clouds, swirling water)
  • Why am I releasing this?
    I hope it will help other aspiring engine programmers, but I wouldn’t have released it if I thought I could commercialise it! The reason is that portal rendering, at least as it was implemented here, is simply too slow for Quake-level geometry: complex scenes slow it to a crawl. John Carmack found this with Quake, but the attractive simplicity of portal rendering was too great for me to believe his comments – I was learning 3D programming, and I honestly, to my embarrassment, thought I could construct a Quake-standard graphics engine with a simple elegant idea that circumvents the complexity of all that PVS/BSP/edge-sorting. I was wrong. Descent used portals to great effect, so I guess this could be used for that type of game, with simple tunnels/rooms. Michael Abrash, in his Zen book (the inspiration behind the name, by the way – I couldn’t think of a name, and his book was in front of me), says VSD is soon going to become a huge challenge. I think he exaggerates slightly – there are many, many ways of doing things – but this is not a good way for what I wanted. As a result I am working on something new now, and won’t be developing this source code further.

    I hope you can learn from this, but needless to say I won’t be impressed if you simply steal my code. Unless you implement things yourself I seriously doubt you’ll truly understand them, and you won’t get very far. All in all writing this engine was a huge learning experience, and the results are patchy. I’m not claiming to be a genius, but I’ve put in a lot effort into learning these things, and that’s what counts, in the end. Maybe because of me you can learn a few things faster.

    Lighting
    Lighting is very similar to Quake’s – shadows are precalculated and the resulting lightmaps used for surface caching in software, or second pass src * dest alpha blending in hardware. This makes static lighting fast, but dynamic lighting very slow (particularly in software). On top of this, at a late stage, I added experimental fogging/depth cueing, which was dynamic, leading to many unexplored possibilities. The main difference from Quake is that the framebuffer in software is 16 bit. Thus every texture needs its own colourmaps (16 Kb/texture), and the surface generation is pretty slow. The same tables could be used for white lighting (what I initially did), but with an additional slowdown coloured blending is possible. I am assuming this is what Sin/Half-life are doing to get coloured lighting in software.

    Translucency and fogging
    Portals make translucent portals very easy. On a 3DFX, of course, alpha blending is also very easy, making the whole thing require very little thought. In software, however, blending 2 16 bit colours quickly is difficult (at least I had to think a lot to find out) – unlike 8 bit a table isn’t an option. The trick I used in the end was very simple, but produced near-perfect results. If you care about software rendering (God knows why you would now), I suggest you check it out. I heard that Unreal used dithering for software transparency, which I don’t think would look as good, but would be a bit faster.

    I never did fogging/depth cueing properly, but it was based on the same trick as translucency. The loop used could be adapted to general white-lighting gouraud shading, which had me contemplating a complete change in the lighting model. The 3DFX, as ever, makes things very easy: you get per-pixel fogging for free. N.B. I’m not talking here about proper volumetric fogging, but trivial global fogging (or depth cueing here with a fade to black). I was contemplating a Turok-like far clipping plane (a standard technique) to speed up rendering, but never did this – it would be easy anyway. Note that fogging slows down software rendering a lot – I’m not sure it’s worth doing.

    Optimisation
    The engine as it is is pretty unoptimised in software mode. I spent much time on an assembler texture-mapping loop (with the usual fdiv overlapping, etc.) which can be linked in (assembled with the free assembler NASM), and it works, but crashes after a certain time. I’d be interested in discovering the bug if anyone works it out. When in place the frame rate goes up a lot. I never got to optimising edge-stepping but this was the next bottleneck. I did start on assembler surface generation but never completed this. This would be essential given the jerks happening when new surfaces were encountered. One algorithmic optimisation I never got working was polygon-constant gradient calculation – texture gradients are calculated per-span rather than per-polygon. I never got this quite working: there seemed to be some rounding error I couldn’t find. At the time of writing, however, software rendering is dying, and I had been spending far more time on more lasting things like engine tools. I wouldn’t say, however, that the engine is particularly slow: in 320x200 on my P133 it averages about 25fps without fogging and with pure C rasterisation. Remember, however, that Quake has to do a z fill pass for monster z-buffering, which I’m not doing.

    In 3DFX mode things are much easier, although I never put in assembler geometry code, despite writing it. It’s easy to see why people are so keen to drop software rendering: the amount of work required to get a tight implementation is phenomenal. After the current batch of fps games (Unreal, Half-Life, Sin, Daikatana, etc.) I don’t think we’ll see many software enabled games (except perhaps through Direct3D/OpenGL).

    Algorithmically I think the engine is reasonably sound, optimisation-wise. Naturally if you feel I’m wrong, tell me why…

    The Code
    I had a clear plan of how the engine would work when I started, but things changed, and what you have here is rather messy. The underlying structure isn’t too bad. If you have complaints with my C coding style then I’d be happy to here them: I’m always trying to improve. Things are heavily commented (too heavily, sometimes, I feel), but I’d be happy to explain things if you email me. As I said Visual C is needed to compile the C, and NASM to compile the few bits of assembler. I have an old DOS version, frozen before the introduction of various features (coloured lighting being one), which compiles with DJGPP. If anyone wants this I’ll provide it.

    As I’ve said the windows code is a mess: it’s the only module in C++ for a start. It’s impossible to overstate just how bad the stuff in winmain.cpp is – please ignore it entirely; it is a huge embarassment. There are numerous pieces commented out – it should be clear why. There are also unused functions in nascent modules I never got far enough with to use.

    The assembler is enabled by setting the flag in asmuse.h. If you do this you’ll need to link in mem.obj and tmap.obj. As I said the assembler texture-mapper has a bug, so don’t expect the engine to be stable if you’re using it. The memory copying routine is probably pointless, but originally in DJGPP I was worried about slow memory copies for screen blitting (Abrash mentions this problem with the compiler).

    To build the engine you’ll need the GLIDE and DirectX (most versions should work – I only used DirectInput and DirectDraw), and to link in glide2x.lib, ddraw.lib, dinput.lib and dxguid.lib. I also used multimedia timing functions so you’ll need to link in winmm.lib. In Visual C with warnings set to level 3 there should be no warnings. I didn’t include a project file to keep the download size down; there is, however, the exe. The code/exe have NOT got software fogging enabled – putting it in is simply a question of fiddling with code commenting.

    When I first uploaded the code I did not include GLIDE2X.DLL. The engine is supposed, at startup, to look for a Voodoo compatible 3D card, and if one is there use it. Unfortunately if the GLIDE library is statically to the .exe, it looks for this .dll file, and the program fails to run if it's not there. If you didn't have a 3DFX card then you wouldn't have had this file, so you couldn't have run the the engine. I have therefore included it, pushing the download size up to about 400K. This is seemingly unavoidable. This is an argument for the Quake 2 .dll renderer architecture.

    The Data
    The level supplied shows off most effects. The textures were created by me and I’d appreciate you not stealing them. The level was compiled using tools I wrote, which I’ll distribute if anyone cares – it’s simple because I never wrote a level editor, and typing in coordinates is not very much fun.

    Tools
    I wrote a lot of tools code to go with this engine. There’s a lighting program which had a few intricacies you might be interested in, but was pretty simple fundamentally. I also wrote a program to turn an arbitrary polygon mesh into convex cells joined by portals. This I found quite challenging to write. As well as these there was a simple mip-map generator and I few other things. If these are required, email me. CSG stuff was never completed, but this is something I’m working on now for another engine.

    Bugs/Problems
    There are, alas, a number of known bugs with the code. As mentioned the assembler texture-mapping crashes after a while. This may be connected with some texture-mapping weirdness on some ceiling polys in the main room – I never investigated this really. Winmain.cpp manifests its appalling crapness in a number of ways: changing modes in 3DFX doesn’t work; timing screws up sometimes, probably due to use of DirectInput in a multimedia timer func (something that’s seemingly disallowed).

    Contacting me
    I’m always interested to discuss 3D graphics, and I’d rather like a games programming job (haha). If you have any queries about/comments on my code, feel free to email me. I can be contacted at stuart@imaginator.com

    This code is available at www.imaginator.com/~stuart

    Stuart Abercrombie

    14/7/1998
    Updated 21/7/1998


    Program code and data © 1997/1998 Stuart Abercrombie
    Portions of winmain.cpp © Microsoft