"anorms.h", "anorms_dots.h"

Discuss programming topics for the various GPL'd game engine sources.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

"anorms.h", "anorms_dots.h"

Post by Baker »

What data is in these headers and how are they applied? (They are used for lighting model vertexes/faces, but I want to understand what it is doing and should these really be in headers?)

"anorms.h" - Both WinQuake and GLQuake use (I see no evidence of FitzQuake using this at all during rendering or light calculation). All the numbers are between 0 and 1, some negative. So these are angle vectors (the x,y,z slope) for each vertice?

Code: Select all

#define NUMVERTEXNORMALS	162

float	r_avertexnormals[NUMVERTEXNORMALS][3] 
...
{-0.525731f, 0.000000f, 0.850651f},
{-0.442863f, 0.238856f, 0.864188f},


Why are there 162 of these? Is some data compressed to a byte in the model? Can this be reduced to code instead of a header file?

"anorms_dots.h" - Only GLQuake use. I think MH said somewhere he has something that generates this in code.

Code: Select all

// precalculated dot products for quantized angles
#define SHADEDOT_QUANT 16
float	r_avertexnormal_dots[SHADEDOT_QUANT][256] =
#include "anorm_dots.h"
;
float	*shadedots = r_avertexnormal_dots[0];
This is used to do a lookup table based on the yaw (0 to 360 angle entity is facing)

shadedots = r_avertexnormal_dots[((int)(ent->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1)];

And then the table is applied to each vertex during draw:

Code: Select all

					vertcolor[0] = shadedots[verts1->lightnormalindex] * lightcolor[0];
					vertcolor[1] = shadedots[verts1->lightnormalindex] * lightcolor[1];
					vertcolor[2] = shadedots[verts1->lightnormalindex] * lightcolor[2];
					glColor4fv (vertcolor);
And this light normal index comes from model load (and is a number from 0-15) :

Code: Select all

		pframe[j].lightnormalindex = pinframe[j].lightnormalindex;
Is that data realistically needed inside the .mdl file? It seems to me that information should be a byproduct of a face that the vertex participates in?
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Re: "anorms.h", "anorms_dots.h"

Post by Spike »

each vertex in an mdl has an index into the 'r_avertexnormals' array, which specifies the vertex normal for that vertex. While you can recalculate the vertex normal at run time, its easier to just look it up in the array (good practise says that you should use the normals the editor expects you to use, at least if you're using normalmaps and stuff which do care about that sort of thing).
I don't know why id stuck to only 162 instead of 256, but meh.

anorms_dots.h is a table of dotproduct(vertexnormals, lightdirection) or so. We don't have the original code to regenerate the table, what we do have appears to require some weird factors, and I'm not sure what the error margin on those is, but its close enough that noone really cares.
The table is a 2d table of entity angle and vertex normal, which gives you a way to avoid doing something like:

Code: Select all

vlight = dotproduct(r_avertexnormals[verts->lightnormalindex], lightingdirection) * r_shadelight;
if (vlight < 0) vlight = 0;
vlight += r_ambientlight;
VectorScale(lightcolor, vlight, vertcolor);
glColor4fv(vertcolor);
(obviously I don't know the exact scalers offhand (would need to check with mh's code), but it should be possible to get something equivelent with the above.)
(remember, with two normalized/unit vectors, the result of the dotproduct is equal to cos(angle), where the angle is the angle between the two vectors, in 3d space. written another way, angle = acos(dotproduct(v1,v2)); written another way, 1 where the vectors are equal (angle=0), 0 where they are perpendicular(angle=90), and -1 where they point in exactly different directions(angle=180). hopefully that helps explain to any noobs reading this why dotproduct is so useful here)
On an old machine with a weak fpu+weak branch prediction, the speedup from a table can be quite significant. On anything beyond an athlon its less significant. On more recent CPUs, the memory cost of an extra table can be slower than just recalculating it (especially if you're doing it in a vertex program/shader). SSE!
The major down side of using a table is that you're limited to only 16 light directions. This is why lighting on your weapon will transition much more smoothly as you turn with the more advanced engines (ones that do the dots), while its a bit juddery in glquake (with its 22-degree precision).
Interestingly enough, software rendering just eats the dots, but I suppose it skips it if the entity is further away, so is slightly less important there.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: "anorms.h", "anorms_dots.h"

Post by Baker »

Hehe, well trying to demystify oddball table of numbers with strange size. Meditation time ..

(Interesting information about the 16 light directions)
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
ericw
Posts: 92
Joined: Sat Jan 18, 2014 2:11 am

Re: "anorms.h", "anorms_dots.h"

Post by ericw »

Yeah, I looked in to these for doing the glsl mdl renderer for QS (not yet merged in, but here)

"anorms.h":

- if they didn't care about the size of mdl files, each vertex in the mdl could just have a float[3] vector to store the vertex normal. So the idea of anorms.h is, each vertex can only have one of the 162 predefined normals, so you can store the normal in 1 byte instead of several bytes.

- regarding why store them in the model when they could be calculated, Preach mentions manipulating them in the model editor to achieve interesting lighting effects: https://tomeofpreach.wordpress.com/2012 ... e/#more-24

- MH pointed out somewhere that the number 162 comes from an icosahedron subdivided twice, afaik subdividing icosahedrons is one standard way to make approximate spheres for computer graphics. I think the level-2 subdivided icosahedron has 162 faces and 320 vertices, so they probably took the center of each face and used that as one of the normals. So assuming that's correct, the only question is how they arrived at the order of anorms.h.

- you're right, FitzQuake doesn't use this in alias model rendering, although R_EntityParticles uses it (looks like it just uses it as points on a sphere.)


"anorms_dots.h":

Here's a program that generates it, and then checks the generated table against id's (place "anorms.h" and "anorms_dots.h" in the same directory); this is just based on MH's code from here: http://forums.inside3d.com/viewtopic.php?p=39361#p39361

In my glsl branch, I run the r_avertexnormal_dot function in the vertex shader (just to avoid the hassle of getting the anorms_dots table onto the gpu).

Code: Select all

#include <math.h>
#include <stdio.h>

typedef double	vec3_t[3];
#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2])

double VectorNormalize (vec3_t v)
{
	double	length, ilength;

	length = sqrt(DotProduct(v,v));

	if (length)
	{
		ilength = 1/length;
		v[0] *= ilength;
		v[1] *= ilength;
		v[2] *= ilength;
	}

	return length;
}

#define NUMVERTEXNORMALS	162
double	r_avertexnormals[NUMVERTEXNORMALS][3] =
{
#include "anorms.h"
};

#define SHADEDOT_QUANT 16
double	r_avertexnormal_dots[SHADEDOT_QUANT][256];
double	r_avertexnormal_dots_id[SHADEDOT_QUANT][256] =
{
#include "anorm_dots.h"
};

double r_avertexnormal_dot(vec3_t vertexnormal, vec3_t shadevector)
{
        double dot = DotProduct(vertexnormal, shadevector);
        // wtf - this reproduces anorm_dots within as reasonable a degree of tolerance as the >= 0 case
        if (dot < 0.0)
            return 1.0 + dot * (13.0 / 44.0);
        else
            return 1.0 + dot;
}

int main(int argc, const char **argv)
{
	int quantizedangle;
	for (quantizedangle = 0; quantizedangle < SHADEDOT_QUANT; quantizedangle++)
	{
		int normal;
		double radiansangle = (quantizedangle / 16.0) * 2.0 * M_PI;
		vec3_t shadevector;

		shadevector[0] = cos(-radiansangle);
		shadevector[1] = sin(-radiansangle);
		shadevector[2] = 1;
	
		VectorNormalize(shadevector);

		for (normal=0; normal<NUMVERTEXNORMALS; normal++)
		{
			r_avertexnormal_dots[quantizedangle][normal] = 
				r_avertexnormal_dot(r_avertexnormals[normal], shadevector);
		}
	}

	// print differences with id's table
	for (quantizedangle = 0; quantizedangle < SHADEDOT_QUANT; quantizedangle++)
	{
		int normal;
		for (normal=0; normal<NUMVERTEXNORMALS; normal++)
		{
			printf("%f\n", r_avertexnormal_dots_id[quantizedangle][normal] - r_avertexnormal_dots[quantizedangle][normal]);
		}
	}
	return 0;
}
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: "anorms.h", "anorms_dots.h"

Post by Baker »

ericw wrote:- you're right, FitzQuake doesn't use this in alias model rendering, although R_EntityParticles uses it (looks like it just uses it as points on a sphere.)
:evil:
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Re: "anorms.h", "anorms_dots.h"

Post by mh »

ericw's comments above pretty much sum it up, yeah. For what it's worth, here's the 12 unique verts for an icosahedron; this is straight out of the OpenGL red book and you can compare with the anorms table in Quake:

Code: Select all

#define X .525731112119133606
#define Z .850650808352039932

static GLfloat vdata[12][3] = {
	{-X, 0.0, Z}, {X, 0.0, Z}, {-X, 0.0, -Z}, {X, 0.0, -Z},
	{0.0, Z, X}, {0.0, Z, -X}, {0.0, -Z, X}, {0.0, -Z, -X},
	{Z, X, 0.0}, {-Z, X, 0.0}, {Z, -X, 0.0}, {-Z, -X, 0.0}
};
If memory serves, these are even in much the same order (it's been a while since I checked) and you'll recognise some of the numbers.

The 13/44 part was found through trial and error and has no evidential basis whatsoever aside from the fact that the calculation results match the anorm_dots table close enough.

The end result of doing that (slightly) weird calculation is that triangles in an MDL facing away from a light don't go to full-black, which is actually desirable because otherwise MDL lighting would look hellishly weird (due to the fact that they're lit as though it were only by one light). Light.exe uses a more traditional calculation.
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
Post Reply