Tutorial *102*

I still suck ass at OpenGL. But I was able to hack Unreal-style vertex lighting (a.k.a. "fake directional" lighting) for models. This tutorial assumes you've already implemented Phoenix's model interpolation tutorial, and possibly muff's MD2 tutorial. Unfortunately, due to the massive number of ways that the MD2 and MDL drawing routines have been hacked, I can't say this will work out-of-the-box for everyone.

First things first: Create a file called "vertexlights.h", and throw the following code in it:

extern byte anorm_pitch[162];

extern byte anorm_yaw[162];

extern byte vlighttable[256][256];

float VLight_LerpLight(int index1, int index2, float ilerp, float apitch, float ayaw);

Now, create a file called "gl_vlights.c" and put the following code in it:

// RIOT - Vertex lighting for models

#include "quakedef.h"

#include "vertexlights.h"

cvar_t	vlight_pitch = {"vl_pitch", "45", true};

cvar_t	vlight_yaw = {"vl_yaw", "45", true};

cvar_t	vlight_highcut = {"vl_highcut", "128", true};

cvar_t	vlight_lowcut = {"vl_lowcut", "60", true};

byte anorm_pitch[162];

byte anorm_yaw[162];

byte vlighttable[256][256];

float VLight_GetLightValue(int index, float apitch, float ayaw)


	int pitchofs, yawofs;

	float retval;

	pitchofs = anorm_pitch[index] + (apitch * 256 / 360);

	yawofs = anorm_yaw[index] + (ayaw * 256 / 360);

	while(pitchofs > 255)

		pitchofs -= 256;

	while(yawofs > 255)

		yawofs -= 256;

	while(pitchofs < 0)

		pitchofs += 256;

	while(yawofs < 0)

		yawofs += 256;

	retval = vlighttable[pitchofs][yawofs];

	return retval / 256;


float VLight_LerpLight(int index1, int index2, float ilerp, float apitch, float ayaw)


	float lightval1;

	float lightval2;

	lightval1 = VLight_GetLightValue(index1, apitch, ayaw);

	lightval2 = VLight_GetLightValue(index2, apitch, ayaw);

	return (lightval2*ilerp) + (lightval1*(1-ilerp));


void VLight_ResetAnormTable()


	int i,j;

	vec3_t tempanorms[162] = {

#include "anorms.h"


	float	forward;

	float	yaw, pitch;

	float	angle;

	float	sp, sy, cp, cy;

	float	precut;

	vec3_t	normal;

	vec3_t	lightvec;

	// Define the light vector here

	angle	= DEG2RAD(vlight_pitch.value);

	sy		= sin(angle);

	cy		= cos(angle);

	angle	= DEG2RAD(-vlight_yaw.value);

	sp		= sin(angle);

	cp		= cos(angle);

	lightvec[0]	= cp*cy;

	lightvec[1]	= cp*sy;

	lightvec[2]	= -sp;

	// First thing that needs to be done is the conversion of the

	// anorm table into a pitch/yaw table



		if (tempanorms[i][1] == 0 && tempanorms[i][0] == 0)


			yaw = 0;

			if (tempanorms[i][2] > 0)

				pitch = 90;


				pitch = 270;




			yaw = (int) (atan2(tempanorms[i][1], tempanorms[i][0]) * 57.295779513082320);

			if (yaw < 0)

				yaw += 360;

			forward = sqrt (tempanorms[i][0]*tempanorms[i][0] + tempanorms[i][1]*tempanorms[i][1]);

			pitch = (int) (atan2(tempanorms[i][2], forward) * 57.295779513082320);

			if (pitch < 0)

				pitch += 360;


		anorm_pitch[i] = pitch * 256 / 360;

		anorm_yaw[i] = yaw * 256 / 360;


	// Next, a light value table must be constructed for pitch/yaw offsets

	// DotProduct values

	// DotProduct values never go higher than 2, so store bytes as

	// (product * 127.5)

	if(vlight_highcut.value <= vlight_lowcut.value || vlight_highcut.value > 256 || vlight_highcut.value < 10)

		Cvar_SetValue("vl_highcut", 256);

	if(vlight_lowcut.value >= vlight_highcut.value || vlight_lowcut.value < 0 || vlight_lowcut.value > 250)

		Cvar_SetValue("vl_lowcut", 0);



		angle	= DEG2RAD(i * 360 / 256);

		sy		= sin(angle);

		cy		= cos(angle);



			angle	= DEG2RAD(j * 360 / 256);

			sp		= sin(angle);

			cp		= cos(angle);

			normal[0]	= cp*cy;

			normal[1]	= cp*sy;

			normal[2]	= -sp;

			precut = ((DotProduct(normal, lightvec) + 2) * 63.5);

			precut = (precut - (vlight_lowcut.value)) * 256 / (vlight_highcut.value - vlight_lowcut.value);

			if(precut > 255)

				precut = 255;

			if(precut < 0)

				precut = 0;

			vlighttable[i][j] = precut;




void VLight_ChangeLightAngle_f(void)




void VLight_DumpLightTable_f(void)


	COM_WriteFile ("lighttable.raw", vlighttable, 256*256);


If you're getting errors because DEG2RAD is undefined, then add the following line to the top of the file:

#define DEG2RAD(a) (a * M_PI / 180.0)

That was the easy part. Now for the annoying part. Open gl_rmisc.c, and add this right before the R_Init function:

extern	cvar_t	vlight_pitch;   // RIOT - Vertex lighting

extern	cvar_t	vlight_yaw;

extern	cvar_t	vlight_lowcut;

extern	cvar_t	vlight_highcut;

void VLight_ResetAnormTable();

Now, add this in R_Init right before R_InitParticles():

// RIOT - Vertex lighting






Open host_cmd.c, and add this to the top of the file:

void VLight_ChangeLightAngle_f(void);   // RIOT - Vertex lights

void VLight_DumpLightTable_f(void);

Now, add following lines to the bottom of Host_InitCommands:

Cmd_AddCommand ("vl_changeangle", VLight_ChangeLightAngle_f);

Cmd_AddCommand ("vl_dumplight", VLight_DumpLightTable_f);

Now for the action. Open up whatever file has GL_DrawAliasBlendedFrame (or whatever your MDL draw function is), and add right after #include "quakedef.h":

#include "vertexlights.h"

Now, look for the line:

l = shadedots_mdl[verts1->lightnormalindex];

Replace it with this:

// RIOT - Vertex lighting

l = VLight_LerpLight(verts1->lightnormalindex, verts2->lightnormalindex, blend, apitch, ayaw);

Now, if you support MD2 models in your engine, open up whatever file contains GL_DrawQ2AliasFrame, and add right after #include "quakedef.h":

#include "vertexlights.h"

Now, look for the line:

l = shadedots_md2[verts1->lightnormalindex];

Replace it with this:

// RIOT - Vertex lighting

l = VLight_LerpLight(verts1[order[2]].lightnormalindex, verts2[order[2]].lightnormalindex, ilerp, apitch, ayaw);

There's only one problem left: You need to relay pitch and yaw to the lerp functions. The changes needed to do this are numerous, so you'll have to hunt them down yourself. The definite thing is that you need to add "float apitch, float ayaw" to the parameter lists of R_SetupQ2AliasFrame, GL_DrawQ2AliasFrame, and GL_DrawAliasBlendedFrame. R_SetupQ2AliasFrame also needs to pass the pitch/yaw values to GL_DrawQ2AliasFrame. This should not be very difficult at all, assuming your code editor has a Find function.

Finally, find R_DrawAliasModel. Make the following changes:

GL_DrawAliasBlendedFrame (currententity->frame, paliashdr, currententity);


GL_DrawAliasBlendedFrame (currententity->frame, paliashdr, currententity, e->angles[0], e->angles[1]);

R_SetupQ2AliasFrame (currententity->frame, pheader, currententity);


R_SetupQ2AliasFrame (currententity->frame, pheader, currententity, e->angles[0], e->angles[1]);

Compile, fix any mistakes I missed, and you'll have vertex lighting. Here are your cvars:
  • vl_pitch and vl_yaw - Control the angle of incoming light
  • vl_lowcut and vl_highcut - 0-256, values below lowcut will be black, values about highcut will be completely bright. Any values inbetween get shaded accordingly.
  • vl_changeangle - Console command that rebuilds the lighting table. You must execute this if you change any of the above mentioned cvars.
  • vl_dumplight - Dumps data to a file called "lighttable.raw", which should be read as a 256x256 grayscale image if you want to debug the generated light table.

    This does, by the way, pave the way for directional lighting. What you'd need to do is tweak the pitch/yaw input values for VLight_LerpLight to point towards the light source.

    Oh, and one last thing.... All code in this document (c)2001 Orbiter Productions and is protected by the terms and conditions described in the GNU General Public License, downloadable at http://www.gnu.org/copyleft/gpl.html. In other words, don't steal my shit.

    Not logged in
    Sign up