The XL Engine is and will remain free, donations are purely optional but greatly appreciated.

Recent Comments

So I’m pretty close to being done with the lighting for DaggerXL. There is still some tweaking to be done but it is very minor at this point. Mostly I have to position the lights for different types of flats a bit differently and maybe tweak the animation and radius a bit more. The rendering for the OpenGL shader mode uses the refactored code from the old DaggerXL project, though tweaked for improved performance and to match the Daggerfall a bit better. However for this post I’ll be talking about the lighting for the software renderer.

Lighting in DaggerXL is rendered per-pixel, or close to it, for all lights even in the software renderer. The reason for this is simple, that was how it was handled in the original DOS game. Despite the numerous flaws with the X-Engine one of it’s coolest features was the real-time per-pixel lighting. Before Quake introduced lightmaps, lighting in software renderers was very simplistic at best. Games like Daggerfall were far too vast – especially in those days – for lightmaps to be practical. Per-vertex lighting, which was used primarily even with early GPU support, was also not very useful for Daggerfall due to the coarse nature of the geometry.

Obviously the number of lights per surface is very important to performance, so there are a couple of things done to keep this manageable:

Light Merging

When loading a dungeon block (or equivalent for other environments), any lights that are at nearly the same height are combined into a single light if their light volumes overlap enough. The position of the new “composite” light is simply the average of all the individual light positions. So, for example, if you have 3 torches right next to each other on one light is generated which would be on the center torch – or in the middle of the torches if they are arrayed in a triangular shape. This allows objects that generate lights, such as torches and candles, to be placed almost at-will without increasing the light count per-surface too much.

Here are a few examples:

3 torches in a line, one light is generated in the center. (8 bit software renderer, 1024×768)

Numerous candles in the rafters, this room actually looks much cleaner and runs much better with the light merging. All the candle lights are merged into a single light. (8 bit software renderer, 1024×768)



Aggressive Per-Polygon Culling and Light per-surface Limits

As suggested, the engine goes through a fair amount of work to cull lights aggressively to limit the number affecting a given surface. First lights that do not move are assigned to meshes during the load process. By doing this at load time the CPU doesn’t have to spend time doing this culling as the player moves around. When a mesh is rendered, this light list is sent to the low-level graphics system. As each polygon is being rendered only the lights from this list that may affect that polygon are used for the per-pixel lighting. The light sphere must overlap the polygon bounding sphere, the light must be above the polygon’s plane (otherwise it is behind the polygon and thus won’t have any visual affect anyway) and the true light to polygon distance must be within range to be affected by the light. From this final list of lights the 4 most likely to have the greatest impact are chosen to light the polygon. By limiting the maximum number of lights per-polygon, the worst case performance is known in advance.


Per-pixel Lighting in Software

It would probably be more accurate to call this per-span lighting, though it is very close to per-pixel. Anyway the relative world space position (i.e. world space – polygon vertex 0, in order to preserve precision with fixed point) is assigned as vertex attributes and is interpolated across the edges and per-span. Basically the rasterizer splits polygons into scanlines and each scanline is then split into Affine Spans, with proper perspective interpolation for the span edges. For resolutions 640×480 and smaller the Affine spans are 8 pixels in size or smaller (such as on edges). All the interpolaters are perspective correct at the span start and end position and are linearly interpolated in-between.

Anyway the relative world space position is perspective correct at the span ends and that is where the lighting is computed. A list of up to 4 lights is processed and then the result is modified by the fog, which is based on the Z distance – the same Z value used for z-buffering. For each light the linear attenuation is computed as well as the dotproduct of the polygon normal – which is a single value for Daggerfall since only polygon normals are used – and the normalized light direction. These values are then linearly interpolated across the spans. In order to make this fast, a table lookup is used. Since we use 16.16 fixed point, we know how much precision is needed and can thus use fast approximations that have sufficient precision.


Picture Time

320×200, 8 bit, software renderer


1024×768, 8 bit, software renderer


16 Responses to “Lighting”

  • Blue Footed Booby:

    Will there be a way for the user to adjust the max number of lights affecting a polygon? That’d be a cool extra for those of us with relatively modern systems.

  • Mike:

    Sounds good. I especially like the way you handled the whole multiple light falloff thing by combining them into one set.

    Also, I’m sure you already know this but your front page no-longer points to the latest blog post. It’s still saying ‘textures, flats…’ is the latest post.

  • Edward:

    I know maybe this is too early to ask, but when +- do you expect the beta will show up? I’m spying this project for more then 2 years now and I’m wondering if I’ll be able to play it at any point. I see that you are putting lot of your free time for this(and I’m very grateful about it), but the work seems to go very slow. When do you expect it may finish? After next 2 years? 5 years? 10 years?

  • Lanus:

    Do you use Mesa for your software engine or you’ve made your own rendering engine? It is interesting to build your own renderer?

    • luciusDXL:

      It is a custom rendering back-end for the XL Engine. The goal is to emulate the original Daggerfall renderer, except for bugs.

  • sepp256:

    Hi Lucius,

    Nice screenshots! I have a question about the one entitled “Lighting8bit_HiRes1.png” – Why are the pixels on the wall polygon on the left slanted? Is this how it looks in the original? My guess is that in the original game, you’ll be using a lower resolution with the screen resolution better matching the texture resolution, and since you’re rendering to a high resolution buffer, then we are seeing the individual slanted pixels in the texture.

    • luciusDXL:

      It’s simply the way the texture coordinates are mapped on the wall, which is the same as the original game. You can play in 320×200 mode if you wish, some of the screenshots in this post are rendered at that resolution. The is also a widescreen 200p mode – basically adjusting the width to adapt to widescreen monitors without stretching.

  • vladdrak:

    hey Lucius! im a lil bit confused. is this very new release gonna contain BloodXL along with DaggerXL and DarkXL as well? not to be rude or impatient, asking out of curiosity only.

    • luciusDXL:

      All the work I’ve shown previously for Blood and Outlaws and of course DarkXL is still there. I’m just focusing on getting a new build with DaggerXL first, then a build for DarkXL, BloodXL and finally OutlawsXL. It’s part of the “Tiered Release” plan I talked about before. After that I’ll work on them all simultaneously, since the majority of the large scale work will be done.

  • Rebel:

    esgasgartwedweewgt8349t4wewgu9-12EWFwghax dbukHfwehfjpa;POjggwegd. Sorry Lucius, just wanted to get that off my chest!

Leave a Reply

The XL Engine is and will remain free, donations are purely optional but greatly appreciated.