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:
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