Extending the number of dynamic lights

Post tutorials on how to do certain tasks within game or engine code here.
Post Reply
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Extending the number of dynamic lights

Post by mh »

Stock ID Quake caps the max number of dynamic lights to 32, but let's face it, lots of dynamic lights make the world a much more interesting place. So let's increase it.

Unfortunately we can't just bump the value of MAX_DLIGHTS because which dynamic lights hit a surface is stored in a 32-bit integer, so we need something more.

First thing, let's find the #define of MAX_DLIGHTS and move it to the top of quakedef.h - we do this because we're going to be referencing it in a number of header files so it's handier to have it here. While we're at it, let's also increase the number; I picked 128 for this example but you can pick something else if you want (I recommend a multiple of 32).

Code: Select all

#define MAX_DLIGHTS 128
Now find the dlightbits member of the msurface_t struct; for GLQuake it should be in gl_model.h (this also works with software Quake although some of the names are different, and should be even easily adaptable to Quake II), and change it to:

Code: Select all

int			dlightbits[(MAX_DLIGHTS + 31) >> 5];
I do it this way so that if I ever change the value of MAX_DLIGHTS I don't need to go hunting through multiple locations for anything that depends on it.

There's also a dlightbits member of entity_t (in render.h), but that's not used in either GLQuake or software Quake. Delete it if you want, it might save you some confusion later on (and save you 4 bytes of memory per entity if you're into that kind of thing).

OK, so we've changed how we store dynamic lights, now we need to change how we mark them. Find the definition of R_PushDlights in render.h and change it to:

Code: Select all

void R_PushDlights (struct mnode_s *headnode);
This is just a convenience so that we can use the same function for brush models as we use for the world.

Next, here's the new R_PushDlights (gl_light.c):

Code: Select all

void R_PushDlights (mnode_t *headnode)
{
	int i;
	dlight_t *l = cl_dlights;

	for (i = 0; i < MAX_DLIGHTS; i++, l++)
	{
		if (l->die < cl.time || (l->radius <= 0))
			continue;

		R_MarkLights (l, i, headnode);
	}
}
And the new R_MarkLights (also gl_light.c):

Code: Select all

void R_MarkLights (dlight_t *light, int num, mnode_t *node)
{
	mplane_t	*splitplane;
	float		dist;
	msurface_t	*surf;
	int			i;

	if (node->contents < 0)
		return;

	splitplane = node->plane;
	dist = DotProduct (light->origin, splitplane->normal) - splitplane->dist;

	if (dist > light->radius)
	{
		R_MarkLights (light, num, node->children[0]);
		return;
	}

	if (dist < -light->radius)
	{
		R_MarkLights (light, num, node->children[1]);
		return;
	}

	// mark the polygons
	surf = cl.worldmodel->surfaces + node->firstsurface;

	for (i = 0; i < node->numsurfaces; i++, surf++)
	{
		if (surf->dlightframe != r_framecount)
		{
			memset (surf->dlightbits, 0, sizeof (surf->dlightbits));
			surf->dlightframe = r_framecount;
		}

		surf->dlightbits[num >> 5] |= 1 << (num & 31);
	}

	R_MarkLights (light, num, node->children[0]);
	R_MarkLights (light, num, node->children[1]);
}
Now, this R_PushDlights function is called from a pretty stupid place - view.c - so let's move it to R_DrawWorld in gl_rsurf.c instead; pop it just before the call to R_RecursiveWorldNode like so:

Code: Select all

R_PushDlights (cl.worldmodel->nodes);
You'll note that this has enabled us to get rid of r_dlightframecount and use r_framecount instead, which is more nice cleaning up.

Moving on to brush models, look for R_DrawBrushModel (also in gl_rsurf.c) and change this mess:

Code: Select all

	if (clmodel->firstmodelsurface != 0)
	{
		for (k = 0; k < MAX_DLIGHTS; k++)
		{
			if ((cl_dlights[k].die < cl.time) || (!cl_dlights[k].radius))
				continue;

			R_MarkLights (&cl_dlights[k], 1 << k, clmodel->nodes + clmodel->hulls[0].firstclipnode);
		}
	}
To this not-so-mess:

Code: Select all

	if (clmodel->firstmodelsurface != 0) R_PushDlights (clmodel->nodes + clmodel->hulls[0].firstclipnode);
More cleaning up here.

So, all that's left is to change how we actually update the lightmaps for each surface, so still in gl_rsurf.c look for R_AddDynamicLights and change this:

Code: Select all

if (!(surf->dlightbits & (1 << lnum))) continue;
To this:

Code: Select all

if (!(surf->dlightbits[lnum >> 5] & (1 << (lnum & 31)))) continue;
And that's it. Have fun.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Post by qbism »

This tute could be subtitled "cleaning up a lot of dlight garbage". R_MarkLights is greatly cleaned up. Silly r_dlightframecount goes away. Thank you!

In SW engine, R_PushDlights call would go in R_RenderView under R_SetupFrame.

BTW, R_PushDlights declaration could go to r_local.h.
leileilol
Posts: 2783
Joined: Fri Oct 15, 2004 3:23 am

Re: Extending the number of dynamic lights

Post by leileilol »

mh wrote:There's also a dlightbits member of entity_t (in render.h), but that's not used in either GLQuake or software Quake. Delete it if you want, it might save you some confusion later on (and save you 4 bytes of memory per entity if you're into that kind of thing).
Could've been cutting room floor for bitflags for unreal-esque dynamic light effects such as this:

Image


Hopefully this dlight cleanup code helps my shitty shadow implementation lol
i should not be here
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Post by qbism »

That shimmer would be cool around water/ lava.

Darklights still bleed through like regular dlights unless expensive vis checks... checks that slow platforms can't cash.
Post Reply