Tutorial: Killing z-fighting in a stock Quake engine

Post tutorials on how to do certain tasks within game or engine code here.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Tutorial: Killing z-fighting in a stock Quake engine

Post by Baker »

I have hated this problem for ages and tonight wanted for myself to solve the problem in my own engine project.

--

The area near the Quad secret on E1M1 is notorious for flickering or changing appearance from various angles even with some previous called "z-fighting fixes" that don't really do the job.

Image

The only engine I've tried with a proper z-fighting fix is:

ezQuake (I confess I have not tested FTE for this)

There are a number of engines with ALMOST a proper z-fighting fix. The closest in the almost category is Qrack. However, I get an outline in the start map atrium as a side-effect.

I still get various types of weirdness in the E1M1 area with:

DarkPlaces, FitzQuake, aguirRe Quake, QMB and every other GL engine I've ever used and thought to look for the problem.


Qrack side effect, not in ezQuake:
Image

I don't like trading a fix in one area for a problem in another eventually I tried to identify the difference between the Qrack and ezQuake fix.

I've tried the ezQuake fix in 2 engines with the same result of a perfect solution (gl_ztrick 0 is required, some engines don't even support gl_ztrick 1 anymore).

-----

Here is the ezQuake fix:

z-fighting fix step 1: in gl_vidnt.c .. or sometimes vid_wgl.c or vid_commongl.c or vid_gl<operating sys>.c

1. Goto GL_INIT or the equivalent
2. You will see OpenGL code resembling the following ...
glClearColor (0.15,0.15,0.15,0);
glCullFace(GL_FRONT);
glEnable(GL_TEXTURE_2D);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.666);
3. Add after the glAlphaFunc line (although it really doesn't matter)
// Get rid of Z-fighting for textures by offsetting the
// drawing of entity models compared to normal polygons.
// (Only works if gl_ztrick is turned off)
glPolygonOffset(0.05, 0);
z-fighting fix step 2: in gl_rmain.c

1. Find void R_DrawEntitiesOnList (void)
2. Locate " case mod_brush:"
3. After that, but before the "break;", insert:

Code: Select all

				// Get rid of Z-fighting for textures by offsetting the
				// drawing of entity models compared to normal polygons.
				// (Only works if gl_ztrick is turned off)
				if(!gl_ztrick.value)
				{
					glEnable(GL_POLYGON_OFFSET_FILL);
				}

				R_DrawBrushModel (currententity);
				
				if(!gl_ztrick.value)
				{
					glDisable(GL_POLYGON_OFFSET_FILL);
				}
				
				break;
I'm rather weak in a lot of the OpenGL calls and the .bsp stuff to quite understand why Qrack's method produces the start map line and ezQuake's only slightly different method doesn't.

I'm guessing that Qrack's use of 1 vs. 0.05 makes the difference in seeing the line, but I'm not 100% certain?
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

NAME
glPolygonOffset - set the scale and units used to calculate
depth values


C SPECIFICATION
void glPolygonOffset( GLfloat factor,
GLfloat units )


PARAMETERS
factor Specifies a scale factor that is used to create a
variable depth offset for each polygon. The initial
value is 0.

units Is multiplied by an implementation-specific value to
create a constant depth offset. The initial value is
0.

DESCRIPTION
When GL_POLYGON_OFFSET is enabled, each fragment's depth
value will be offset after it is interpolated from the depth
values of the appropriate vertices. The value of the offset
is factor * DZ + r * units, where DZ is a measurement of
the change in depth relative to the screen area of the
polygon, and r is the smallest value that is guaranteed to
produce a resolvable offset for a given implementation. The
offset is added before the depth test is performed and
before the value is written into the depth buffer.

glPolygonOffset is useful for rendering hidden-line images,
for applying decals to surfaces, and for rendering solids
with highlighted edges.
0.05 seems to me its rounding to the nearest unit down.

I chose not to use a blanket value for all brushes as there were conflicts with other textures. Decals need to be pushed out a bit since the texture chain is offset. Also dynamic team skins need to be offset from the base skin as certain animation frames cause the z-fighting. The slight outline in the start map could be rectified by tweaking the value a bit.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

I'm still learning what half of the OpenGL calls do.

I played Quake all the way through with god mode on the Mac and there is some REALLY annoying z-fighting on maybe 1/3 of the id1 maps.

Ever since, was it Lightning Hunter?, brought "mainstream" attention to the z-fighting thing, I just don't enjoy seeing the flickering in E1M1 type areas that are scattered about especially in the id1 maps and just about every engine does it in some shape or form.

And at the same time, knowing that the start map tends to get played heavily on a certain servers, I was concerned about the outline and getting the inevitable question or complaint (or worse --- not getting it).

I considered adding "if map !=start" code as a solution. I didn't think about decals or other special effects.
Wazat
Posts: 771
Joined: Fri Oct 15, 2004 9:50 pm
Location: Middle 'o the desert, USA

Post by Wazat »

This looks like a well-made tutorial. I like the way you've outlined areas in the picture to illustrate the problem. I've noticed z-fighting a lot and it would be nice to get rid of it in some of the mainstream engines... the question is what method is best for dealing with all cases.

Glad I'm not an engine programmer. Can't blame me for not doing it myself. ;)

(though watch LH come in and blame me, hehe)
When my computer inevitably explodes and kills me, my cat inherits everything I own. He may be the only one capable of continuing my work.
LordHavoc
Posts: 322
Joined: Fri Nov 05, 2004 3:12 am
Location: western Oregon, USA
Contact:

Post by LordHavoc »

I disagree with the use of the first parameter in glPolygonOffset, that pushes the polygon along its normal, which can easily cause artifacts when the bmodel is not a simple flat plane, furthermore it can cause artifacts where it meets the world geometry - for example that line you cited in Qrack can result from use of the first parameter in glPolygonOffset.

DarkPlaces and Quake3 use the second parameter, which pushes it only a tiny amount without moving the polygon at all.

I've heard of better results in DarkPlaces when using r_polygonoffset_submodel_offset 14 (default is 2), but 2 works fine on nvidia, perhaps 14 is needed on ATI?

You can experiment with different values for both parameters in the DarkPlaces console.

r_polygonoffset_submodel_factor - first parameter (default 0)
r_polygonoffset_submodel_offset - second parameter (default 2)
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

LordHavoc wrote:r_polygonoffset_submodel_factor - first parameter (default 0)
If I set that cvar's value to 1, the z-fighting in e1m1 disappears.

I played with r_polygonoffset_submodel_offset values from 1 to 25 and although it was less distinct, the z-fighting never totally went away for me without setting the former parameter. (GeForce 5200fx, not ATI)
LordHavoc
Posts: 322
Joined: Fri Nov 05, 2004 3:12 am
Location: western Oregon, USA
Contact:

Post by LordHavoc »

Baker wrote:I played with r_polygonoffset_submodel_offset values from 1 to 25 and although it was less distinct, the z-fighting never totally went away for me without setting the former parameter. (GeForce 5200fx, not ATI)
This concerns me, because factor causes artifacts (polygon gaps, or overlap) AND is less effective in the distance (the z-fighting returns if you get far enough away), offset does not suffer either problem, so I really can't accept factor as a solution, but if offset isn't working either then we have a problem...

Note: to make matters worse, in OpenGL 3.0 the glPolygonOffset feature is deprecated (meaning should not be used in 3.0 and can't be used in apps that want to make use of OpenGL 3.1 and later features), and it never existed in Direct3D at all (to give you an idea of how discouraged it is).

Note2: ATI performance suffers on regions of the screen that were affected by glPolygonOffset (because the hierarchical polygon depth culling does not support the offset features) - I don't think it accelerates glDepthRange either for that matter, which is the other way to do an offset.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

LordHavoc wrote:
Baker wrote:I played with r_polygonoffset_submodel_offset values from 1 to 25 and although it was less distinct, the z-fighting never totally went away for me without setting the former parameter. (GeForce 5200fx, not ATI)
This concerns me, because factor causes artifacts (polygon gaps, or overlap) AND is less effective in the distance (the z-fighting returns if you get far enough away), offset does not suffer either problem, so I really can't accept factor as a solution, but if offset isn't working either then we have a problem...

Note: to make matters worse, in OpenGL 3.0 the glPolygonOffset feature is deprecated (meaning should not be used in 3.0 and can't be used in apps that want to make use of OpenGL 3.1 and later features), and it never existed in Direct3D at all (to give you an idea of how discouraged it is).

Note2: ATI performance suffers on regions of the screen that were affected by glPolygonOffset (because the hierarchical polygon depth culling does not support the offset features) - I don't think it accelerates glDepthRange either for that matter, which is the other way to do an offset.
I'll make some screenshots with the console down in that area to verify:

a. I made no mistakes
b. show how it renders with these different cvar settings
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

LordHavoc wrote:
Baker wrote:I played with r_polygonoffset_submodel_offset values from 1 to 25 and although it was less distinct, the z-fighting never totally went away for me without setting the former parameter. (GeForce 5200fx, not ATI)
This concerns me, because factor causes artifacts (polygon gaps, or overlap) AND is less effective in the distance (the z-fighting returns if you get far enough away), offset does not suffer either problem, so I really can't accept factor as a solution, but if offset isn't working either then we have a problem...

Note: to make matters worse, in OpenGL 3.0 the glPolygonOffset feature is deprecated (meaning should not be used in 3.0 and can't be used in apps that want to make use of OpenGL 3.1 and later features), and it never existed in Direct3D at all (to give you an idea of how discouraged it is).

Note2: ATI performance suffers on regions of the screen that were affected by glPolygonOffset (because the hierarchical polygon depth culling does not support the offset features) - I don't think it accelerates glDepthRange either for that matter, which is the other way to do an offset.
Did some more testing and this time took screenshots.

r_polygonoffset_submodel_offset 2 (default), fairly noticeable z-fighting.

r_polygonoffset_submodel_offset 14; 99.8% solved but some z-fighting did occur if was running around the area and very carefully looking around to try to get an angle that would cause a little flickering.

r_polygonoffset_submodel_offset 25; 100% solved; try as I might, I couldn't locate anywhere to stand or look that would do any z-fighting.

Image

Image

/Yeah, that r_polygonoffset_submodel_factor cvar had an undesireable side effect in the start map atrium.
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

Code: Select all

void GL_PolygonOffset (int offset)
{
	if (offset > 0)
	{
		glEnable (GL_POLYGON_OFFSET_FILL);
		glEnable (GL_POLYGON_OFFSET_LINE);
		glPolygonOffset(0.0559, offset);
	}
	else if (offset < 0)
	{
		glEnable (GL_POLYGON_OFFSET_FILL);
		glEnable (GL_POLYGON_OFFSET_LINE);
		glPolygonOffset(-0.0559, offset);
	}
	else
	{
		glDisable (GL_POLYGON_OFFSET_FILL);
		glDisable (GL_POLYGON_OFFSET_LINE);
	}
}
seems to work better on my end. only the start map shows VERY faint offset outline now.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

r00k wrote:

Code: Select all

void GL_PolygonOffset (int offset)
{
	if (offset > 0)
	{
		glEnable (GL_POLYGON_OFFSET_FILL);
		glEnable (GL_POLYGON_OFFSET_LINE);
		glPolygonOffset(0.0559, offset);
	}
	else if (offset < 0)
	{
		glEnable (GL_POLYGON_OFFSET_FILL);
		glEnable (GL_POLYGON_OFFSET_LINE);
		glPolygonOffset(-0.0559, offset);
	}
	else
	{
		glDisable (GL_POLYGON_OFFSET_FILL);
		glDisable (GL_POLYGON_OFFSET_LINE);
	}
}
seems to work better on my end. only the start map shows VERY faint offset outline now.
There is always the old fashioned way ...

Code: Select all

if (map != MAP_START) {

...

}
:D :D
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

LordHavoc wrote:because factor causes artifacts (polygon gaps, or overlap) AND is less effective in the distance (the z-fighting returns if you get far enough away), offset does not suffer either problem, so I really can't accept factor as a solution, but if offset isn't working either then we have a problem...
I've noticed some undesireable side effects of the ezQuake "factor" method when testing out some of the maps in my engine using the ezQuake code.

I guess I'll play around with DarkPlaces and Qrack some more and maybe add r_submodel_polygon_offset and r_submodel_polygon_factor and let the user decide.
r00k wrote:

Code: Select all

void GL_PolygonOffset (int offset)
{
	if (offset > 0)
	{
		glEnable (GL_POLYGON_OFFSET_FILL);
		glEnable (GL_POLYGON_OFFSET_LINE);
		glPolygonOffset(0.0559, offset);
	}
	else if (offset < 0)
	{
		glEnable (GL_POLYGON_OFFSET_FILL);
		glEnable (GL_POLYGON_OFFSET_LINE);
		glPolygonOffset(-0.0559, offset);
	}
	else
	{
		glDisable (GL_POLYGON_OFFSET_FILL);
		glDisable (GL_POLYGON_OFFSET_LINE);
	}
}
seems to work better on my end. only the start map shows VERY faint offset outline now.
Rook, in my head ... and this is just how I think ...

The 2 most frequently played maps are start and e1m1.

If the outline in the start map wasn't there and was instead on E3M3 near the exit, who knows if I would ever notice or even care?

I think I got preoccupied with e1m1 because that area's flickering always irritates me. But if that didn't happen on e1m1 and instead happened near the exit on Ziggurat Vertigo, who knows if I'd even be aware of it.

----

I think of "start" as the cover, "e1m1" as the first page inside the cover and maybe e1m3 as the first chapter just because it plays in the starting demos (except in engines like Qrack).

So for me, I think I'll try to "solve" the problem by any means necessary in ProQuake even with an evil (map == start) hack.

My logic is that the "standard issue" interpolation code makes exceptions for torches and I use a shotgun box skin "corrector" found in aguirRe's engine. My understanding is that the muzzleflash code in Qrack and ezQuake selectively removes the muzzleflash stuff off the v_weaps.

To the unknowing reader: shotgun shell "evil fix hack" fixes this:
Image

DarkPlaces is clean with no evilness. No other engine is ... not even glquake.

I guess I'll solve this somehow using an evil method!
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Post by revelator »

thats one thing that also allways bugged the crap out of me in some engines with zfighting.

tenebrae was notorious for that but well that was most likely because of the shadow volume code.

mh's engine i work with atm doesnt show any flickering as far as i can notice but im old so my eyes might not be the best ;).

one thing i found out that can really take a punch at zfighting and hold on this is mundo weird. if you got a replace texenv at the end of
drawtexturechains delete it and put a blendfunction with the value
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
instead.

it seems for some reason the blendfunction gets modified comming out of there especially visible on engines using multitexture "all newer engines".

cant say what modifies it but this drastically reduces zfighting even more if you do the same at the end of the brushmodel code.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Tutorial: Killing z-fighting in a stock Quake engine

Post by Baker »

The truth about Z-fighting:

It can't really be "fixed" by any tricks. Any tricks that fix one scenario will act up in another scenario. And with the polygon offset trick and relatives, what might look right on one graphics cards might have a line outline around a secret door or leave artifacts in places.

There isn't a legit win.

However, there is the illegit win. In some GLQuake engines, they CRC32 the shotgun shells box texture to verify it is the original and then in the engine it patches it up to fix it. Likewise replacing the E1M1 flickery spot with this:
Fix E1M1 by CRC32ing the world entities string to verify it is unmodified and the expected one and then changing one entity string as such... wrote:// Just give the platform a little negative lip, pushing it down 2 units. The numbers seem reversed but they aren't.
{
"classname" "func_door"
"targetname" "t4"
"lip" "-4"
"angle" "-2"
"spawnflags" "1"
"sounds" "2"
"model" "*15"
"origin" "0 0 -2"
}
And don't even do any attempt of a zfix trick And 99.9% people who hate the E1M1 quad area flickery issue will think your engine is zfixed.

And without ruining modern mapper's maps by leaving an outline around their properly crafted secret doors and stuff.
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 ..
frag.machine
Posts: 2126
Joined: Sat Nov 25, 2006 1:49 pm

Re: Tutorial: Killing z-fighting in a stock Quake engine

Post by frag.machine »

IMHO a more sane way to do that would be to have an external file where you store info about known maps where Z-fighting occurs and what's required to fix it (for example, the entity classname, its targetname, origin offsets, etc). This way, you not only gives a solution for existing problems but you also create a way to fix less known community maps where this happens. You can even use existing file parse functions to implement this. Shoving hardcoded data into your engine always make it less flexible (besides, you always have the remote chance of CRC collision screwing something in other maps for no apparent reasons).
I know FrikaC made a cgi-bin version of the quakec interpreter once and wrote part of his website in QuakeC :) (LordHavoc)
Post Reply