"anorms.h", "anorms_dots.h"
Moderator: InsideQC Admins
6 posts
• Page 1 of 1
"anorms.h", "anorms_dots.h"
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?
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.
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:
And this light normal index comes from model load (and is a number from 0-15) :
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?
"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?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Re: "anorms.h", "anorms_dots.h"
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:
(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.
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.
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
Re: "anorms.h", "anorms_dots.h"
Hehe, well trying to demystify oddball table of numbers with strange size. Meditation time ..
(Interesting information about the 16 light directions)
(Interesting information about the 16 light directions)
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Re: "anorms.h", "anorms_dots.h"
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: 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).
"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: 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;
}
- ericw
- Posts: 92
- Joined: Sat Jan 18, 2014 2:11 am
Re: "anorms.h", "anorms_dots.h"
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.)
The night is young. How else can I annoy the world before sunsrise?
Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
Re: "anorms.h", "anorms_dots.h"
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:
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.
- 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
We knew the words, we knew the score, we knew what we were fighting for
-

mh - Posts: 2292
- Joined: Sat Jan 12, 2008 1:38 am
6 posts
• Page 1 of 1
Who is online
Users browsing this forum: No registered users and 1 guest