d4 is a simple software rasterizer written in the D programming language. It was originally a part of my Matura exam (comparable to the British A levels) in mathematics, along with a scholarly paper on the mathematical basics of realtime 3D rendering – you can find the paper here as well, but it probably won’t be of much use unless you understand German.
One of my design goals was to stick fairly close to the architecture of the 3D pipeline of DirectX/OpenGL so that actual code could be used to illustrate some parts of the paper. I ended up not including any code in the text, but the design remained the same. As a consequence, for instance the semantics of the vertex and pixel shaders are very similar to the »real« ones.
Another requirement was that even people who had little to no experience with 3D programming should be able to make sense of the code. Thus, the sources are documented fairly well, and, more important, I did not sacrifice much readability for performance.
Working on the code has turned out to be quite fun, and so I created a few small demo applications, two of which are included in the repository. While I was writing the code for them, I almost felt like if I had been a thin engine layer above DirectX (minus its semi-OO design, naturally, which I am not too fond of). Mission accomplished.
As mentioned above, the architecture of d4’s 3D pipeline resembles the ones found on modern graphics cards. This also means that the set of supported features is quite similar, at least from a high-level point of view. For example, the renderer supports perspectively correct interpolation, backface culling, z-buffering, vertex and pixel shaders, texturing, and so on. The project also includes a tiny SDL based application »framework« to make playing around with the rasterizer easier.
Please note, however, that d4 is still about demonstrating the basics of realtime rasterization and not about replicating a GPU in software. Therefore, you can also find quite a number of features on recent graphics cards which d4 does not support: geometry shaders, instancing, multiple render targets, HDR rendering and mip mapping (and thus trilinear/anisotropic filtering), to name a few.
Most of the above would be easy to implement, but there is another obvious problem which would render these more advacnced features virtually useless: Performance. It is widely known that even the fastest, most optimized software renderers are embarassingly slow when compared to today’s graphics cards. Additionally, as I stated in the introduction, my primary goals were readability and clean design, not maximum performance. No hand-crafted SIMD assembler (well, no inline assembler at all), no fixed-point math, no fancy programming tricks, and no optimization specifically for a single application – which inevitably means: bad performance.
However, I still wanted my simple demo applications to run at an acceptable speed, so wasting lots of compution time was not what I intended either. Apart from general precautions (like avoiding allocations and GC activity), I also used parts of the extensive metaprogramming facilities of D to create a shader system based on templates. The big advantage of this is that it enables the compiler to inline the shader into the rasterization loop, but remains straightforward in use. After discovering the LLVM-based LDC which offers much more sophisticated optimizations than the standard Digital Mars compiler, I decided that I won’t do any big optimizations manually because the performance is already acceptable.
There are also some downsides to my approach, though: Firstly, templates are evaluated at compile-time, so runtime modifications are not possible (at least not without sacrifying performance). For future versions, I plan to implement dynamic shader (re-)compilation to work around this. Secondly, relying on the compiler to optimize speed-critical code obviously causes the quality of its optimizer to greatly influence the performance. For example, binaries produced by LDC are typically several times faster than the ones DMD generates. Unfortunately, LDC is not of production quality on Windows yet; most notable it lacks support for exceptions.
The Open Asset Import Libary (Assimp) is used for reading model files. Thanks to this excellent library, d4 can read over twenty different model file formats:
- Collada (*.dae)
- 3D Studio Max 3DS (*.3ds)
- 3D Studio Max ASE (*.ase)
- Wavefront Object (*.obj)
- Stanford Polygon Library (*.ply)
- AutoCAD DXF (*.dxf)
- LightWave (*.lwo)
- Stereolithography (*.stl)
- AC3D (*.ac)
- Valve Model (*.smd, *.vta)
- Quake I (*.mdl)
- Quake II (*.md2)
- Quake III (*.md3)
- Return to Castle Wolfenstein (*.mdc)
- Doom 3 (*.md5)
- Biovision BVH (*.bvh)
- DirectX X (*.x)
- Quick3D (*.q3d)
- Irrlicht Mesh (*.irrmesh)
- Neutral File Format (*.nff)
- Sense8 WorldToolKit (*.nff)
- Object File Format (*.off)
- PovRAY Raw (*.raw)
- Terragen Terrain (*.ter)
- 3D GameStudio (*.mdl)
- 3D GameStudio Terrain (*.hmp)
Another jewel of a library is DevIL, which is used to read texture data. Thanks to it, this program can cope with 30 different pixel image formats:
d4 currently includes two small applications demonstrating the features of the rasterizer: Viewer, a minimal model viewer which loads a model from disk and displays it on sceen using the textures or vertex colors from the file (if any), and SpinningLights, which demonstrates more complex per-pixel lighting by illuminating the scene with two colored omni lights (point lights) rotating around the y-axis.
Both programs accept several options at the command line, pass the
--help option for an overview. Keyboard commands:
- W, S, A, D: Move the camera.
- Cursor keys: Rotate the camera.
- U, I: Move the lights in and out, holding Alt changes the direction. (SpinningLights only)
- Shift: Move/rotate faster.
- Y, Z: Toggle materials and Gouraud shading. (Viewer only)
- X: Toggle wireframe mode.
- C: Toggle backface culling.
- V: Toggle auto world rotation. (Viewer only)
- B: Use a colorful animated background. (Viewer only)
The downloads below contain the source tree of the d4 release 0.1. To start playing around, you need a working, not-too-old D1 compiler configured for use with Tango (if in doubt, just try the packages from the Tango website), a build tool and the DerelictIL/SDL and dAssimp bindings (either in your system-wide inclue path or in the libs/ directory). A configuration file for DSSS is included, although I personally use xfBuild now.
If you do not have a D1/Tango environment available and just want to see what the rasterizer produces, you can also download one of the pre-built versions:
The Windows package contains two binaries, one built with DMD and another one built with LDC. The LDC one runs significantely faster, but when any error occurs, it aborts with an interal error instead of displaying a useful message (see above). The package also contains a recent version of all the needed DLLs, it should work out of the box.
The linux version, however, only has a copy of the not-yet-released Assimp along with the binary. If the widely-used DevIL and SDL libraries are not yet present on your system, you have to install them via your package manager of choice first.
The packages also include a free model of a dwarf by an artist named Psionic so that you can play around with d4 in case you don’t have another one handy.
The full source tree of this project, representing the latest state of development, is available at GitHub. You are absolutely encouraged to fork the project and start hacking right away.
d4 is free Open Source software and is distributed under the GNU GPL 3 (or any later version). However, please do not hesitate to contact me if you want to use parts of it under a more permissive license.
The BSD-licensed Assimp library is © ASSIMP Development Team; DevIL and the multimedia library SDL are used under the terms of the GNU LGPL.