Doing physically correct lighting, many things in the q3map2 code look wrong, the code contradicts itself in multiple ways
I'm requesting comments from @Garux and @divVerent, for various reasons:
- because I may have found bugs in q3map2
- there would be interesting knowledge to share about doing great mapping with NetRadiant
- maybe most of idTech-derivated game engines are wrong (I would actually bet on it without hesitation)
- and maybe this talk may lead to game-changing (haha) improvements in the DarkPlaces/Dæmon/ioquake3 gaming/mapping scene!
I'm actually investigating the ability to provide physically-correct lighting with q3map2 and the Dæmon engine (a Quake3/XreaL engine fork), but that may benefit to many games
I want to make sure both q3map2 map compiler and quake3-like renderers do the expected things to get a physically correct light computation.
And I deeply want to allow mappers to make great maps that look awesome in space.
The q3map2 contradictions
In the q3map2 builtin help for the -light
stage I read:
-wolf Use linear falloff curve by default (like W:ET)
In q3map2's light.c
I read:
else if ( !strcmp( argv[ i ], "-wolf" ) ) {
/* -game should already be set */
wolfLight = qtrue;
Sys_Printf( "Enabling Wolf lighting model (linear default)\n" );
}
then:
/* ydnar: quake 3+ light behavior */
if ( wolfLight == qfalse ) {
/* set default flags */
flags = LIGHT_Q3A_DEFAULT;
/* linear attenuation? */
if ( spawnflags & 1 ) {
flags |= LIGHT_ATTEN_LINEAR;
flags &= ~LIGHT_ATTEN_ANGLE;
}
// …
}
/* ydnar: wolf light behavior */
else
{
/* set default flags */
flags = LIGHT_WOLF_DEFAULT;
/* inverse distance squared attenuation? */
if ( spawnflags & 1 ) {
flags &= ~LIGHT_ATTEN_LINEAR;
flags |= LIGHT_ATTEN_ANGLE;
}
// …
}
and:
/* attenuate */
if ( light->flags & LIGHT_ATTEN_LINEAR ) {
add = angle * light->photons * linearScale - ( dist * light->fade );
// …
else
{
add = ( light->photons / ( dist * dist ) ) * angle;
and:
/* attenuate */
if ( light->flags & LIGHT_ATTEN_LINEAR ) {
add = light->photons * linearScale - ( dist * light->fade );
if ( add < 0.0f ) {
add = 0.0f;
}
}
else{
add = light->photons / ( dist * dist );
}
So unless I missed something obvious, the q3map2 help says the -wolf
option enables “linear falloff curve”, but the code sets the LIGHT_ATTEN_LINEAR
flag if -wolf
is not used. Both seems to contradict together.
Also, when LIGHT_ATTEN_LINEAR
is set, the computation doesn't look like a linear attenuation computation.
When LIGHT_ATTEN_LINEAR
isn't set, anyway, what is done looks to be a quadratic attenuation.
Here is what the Gamma Correction page on learnopengl.com
says:
Something else that's different with gamma correction is lighting attenuation. In the real physical world, lighting attenuates closely inversely proportional to the squared distance from a light source. In normal English it simply means that the light strength is reduced over the distance to the light source squared, like below:
float attenuation = 1.0 / (distance * distance);
However, when using this equation the attenuation effect is usually way too strong, giving lights a small radius that doesn't look physically right. For that reason other attenuation functions were used (like we discussed in the basic lighting chapter) that give much more control, or the linear equivalent is used:
float attenuation = 1.0 / distance;
The linear equivalent gives more plausible results compared to its quadratic variant without gamma correction, but when we enable gamma correction the linear attenuation looks too weak and the physically correct quadratic attenuation suddenly gives the better results. The image below shows the differences:
[images]
So, if first computation was a linear attenuation, if that learnopengl.com
page is right (I don't know) I would expect the code to be something like:
if ( light->flags & LIGHT_ATTEN_LINEAR ) {
add = light->photons / ( dist );
}
else {
add = light->photons / ( dist * dist );
}
But anyway, if that learnopengl.com
page is right, what I need for physical lighting is a quadratic attenuation in q3map2
and a gamma correction on engine side. So I don't really mind what is the other attenuation algorithm if that's not the one I'm looking for.
Some other parts of the code may suggest that the other lighting options when not using -wolf
may be “standard Lambert attenuation”, with an option to enable the “half Lambert attenuation” variant.
Questions
So, well…
- Is q3map2 help totally wrong because it computes some attenuation in quake3 lighting mode and quadratic attenuation in wolf lighting mode, despite saying the wolf lighting mode is linear ?
- Should wolf lighting be quadratic, linear, or that other attenuation currently implemented ?
- Is the q3map2 help wrong and Wolf:ET actually expects quadratic attenuation but then the
-wolf
option selects the right computation despite the comments being wrong? - Is the
learnopengl.com
page wrong in any way? For example:- Do I need a quadratic attenuation for a physically correct lighting ?
- Is the given formula the quadratic formula ? There is a power of 2 though, so that may be right.
- In fact, what are the formulas I need on map compiler side and on engine side?
What I understood
So, the summary about what I understood to get physically correct lights:
- On compiler side I need a quadratic attenuation.
- On engine side I need to linearize images before doing light computations, then gamma-encoding the output. If the lightmap was sRGB encoded, it should also be linearized before doing the light computation.
For what I understood from old talks with divVerent, despite lightmaps are linear data, encoding them as sRGB may be used as a trick to have more precision in the dark areas (at the expense of others) while keeping an 8-bit per channel format. But then the engine should not forget to relinearize them before processing.
What I suspect
Now I suspect that “Enabling Wolf lighting model (linear default)” was misinterpreted. To be honest the wording is bad. The one writing the builtin help wrongly thought that was meaning Wolf lighting was liner, but it was probably that, on the contrary, when wolf lighting is not used, the default is linear.
Then, there would be a mistake where lambert lighting would be wrongly named linear.
It would mean that to get physically correct lighting one should use -wolf
light option on q3map2 side and linearize before and gamma-encode after computation in engine.
Note: This article is very interesting: https://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/