Fully Automatic Transparent Water

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

Fully Automatic Transparent Water

Post by Baker »

This can equally apply to a WinQuake with transparent water support, there is nothing specific to GLQuake in this. It is easier than the below makes it look.

The general idea is that with a modernish engine that expand water visibility near water portals (FitzQuake derivatives in particular).
1) If you are near a water portal, if you have ever been away from one and haven't seen a transparent water before that frame, you don't have transparent water for the map.
2) If you aren't near a water portal, and see transparent water, you have it for the whole map.

It's that simple. But reducing it to code requires a few variables and a number of checks ...

Setup:
1) Make sure loading the .bsp model has the code marking surfaces underwater. If I recall, FitzQuake 0.85 removes that. It is just a line or 2 of code and I think a single flag. Make sure it only marks these for the worldmodel, not healthboxes and stuff.
2) Make 3 variables as part of cl. (client map state) The cl. structure gets cleared on every new map automatically ...
2a) cl.transparent_water_seen
2b) cl.transparent_water_valid_test_count
2c) cl.transparent_water_known
Implementation:
0) if cl.transparent_water_known, skip all of this (because you already have a conclusive result)
1) Determine if the frame has expanded visibility for being near water. Set a variable like frame_nearwater to 1 or 0.
2) If frame_nearwater is 0 ..
2A) ... If transparent_water_valid_test_count > 0 and cl.transparent_water_seen = 0 then you don't have transparent water for the map so set cl.transparent_water_known to 1.
3) Check for visibility of an underwater surface and a NOT underwater surface. Set frame_transparent_water_seen to 1 or 0.
4) If frame_nearwater is NOT 0 ...
4A) ... Increment cl.transparent_water_valid_test_count.
3B) ... If frame_transparent_water_seen = 1 you have transparent water for the map so set cl.transparent_water_known to 1 and cl.transparent_water_seen to 1.
5) If cl.transparent_water_known = 0 and frame_nearwater = 1 ...
5A) ... then use, use the r_wateralpha value to render the water. (This scenario is almost impossible, I've never been able to create it on purpose).
6) Otherwise if both cl.transparent_water_known = 1 and cl.transparent_water_seen = 1, render water using r_wateralpha value otherwise render it as non-transparent.
An option is to check during .bsp load time to see if any surfaces have been marked as underwater or if any water textures were loaded for the bsp. If no surfaces have been marked as underwater then you can set cl.transparent_water_known to 1 and cl.transparent_water_seen to 0.
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 ..
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Fully Automatic Transparent Water

Post by Baker »

I don't know if this is particular helpful because the implementation in Mark V does extra stuff so isn't very simple, but it works like this:

1. model.h (handles both WinQuake and GLQuake in Mark V)

flag values for a surface

Code: Select all

#define SURF_DRAWLAVA		0x400
#define SURF_DRAWSLIME		0x800
#define SURF_DRAWTELE		0x1000
#define SURF_DRAWWATER		0x2000
And keeping track of info for the level

Code: Select all

typedef struct
{
// The following are definitely known by client/server immediately because they occur in model load.
	cbool	water;
	cbool	lava;
	cbool	slime;
	cbool	teleporter;
	cbool	sky;

// The following are not immediately known.  Although could probably have loader cross these off if no liquid textures.
	cbool	water_vis_known;
	cbool	water_vis;
} level_info_t;

extern level_info_t level;
Keeping track of info for the frame

Code: Select all

typedef struct
{
	cbool oldwater;			// Doesn't apply to WinQuake, just GL and the Direct3D version forces to 1 every frame.
	cbool nearwaterportal; 	// GL + WinQuake
	cbool has_underwater; 	// GL + WinQuake
	cbool has_abovewater; 	// GL + WinQuake
	cbool has_sky; 			// Baker: Could work for WinQuake but not relevant.
	cbool has_mirror; 		// Baker: Would be real hard to make it work for WinQuake.

// Baker: Direct3D wrapper doesn't have stencil, so will have to have the mirror some other way.
// Baker: Will have to draw the sky the traditional way

	float liquid_alpha; 		// WinQuake:  A per surface indicator of how much alpha to apply for current liquid texture.

	float wateralpha; 			// GL + WinQuake, alpha for the frame
	float slimealpha; 			// GL + WinQuake, alpha for the frame
	float lavaalpha; 			// GL + WinQuake, alpha for the frame
	float mirroralpha; 			// Baker: Don't see this working in WinQuake easily, unless I add a stencil buffer to WinQuake
} frame_render_t;

extern frame_render_t frame;
2. model.c (handles both WinQuake and GLQuake in Mark V)

Code: Select all

		else if (out->texinfo->texture->name[0] == '*')		// warp surface
		{
			out->flags |= (SURF_DRAWTURB | SURF_DRAWTILED);
			if (!strncmp (out->texinfo->texture->name, "*lava", 5))
				level.lava = true, out->flags |= SURF_DRAWLAVA;
			else if (!strncmp (out->texinfo->texture->name, "*slime", 6))
				level.slime = true, out->flags |= SURF_DRAWSLIME;
			else if (Is_Texture_Prefix (out->texinfo->texture->name, r_texprefix_tele.string))
				level.teleporter = true, out->flags |= SURF_DRAWTELE;  // *tele ... so that r_wateralpha doesn't affect teleporters.
			else level.water = true, out->flags |= SURF_DRAWWATER;
#ifdef GLQUAKE_RENDERER_SUPPORT
			Mod_PolyForUnlitSurface (out);
			GL_SubdivideSurface (out);
#endif // GLQUAKE_RENDERER_SUPPORT
#ifdef WINQUAKE_RENDERER_SUPPORT
			for (i = 0;  i < 2; i++)
			{
				out->extents[i] = 16384;
				out->texturemins[i] = -8192;
			}
#endif // WINQUAKE_RENDERER_SUPPORT
			continue; // Baker: Done with warp surface
		}
3. r_common.c (common renderer file shared between WinQuake and GLQuake) -- SetLiquidAlpha -- done once per frame

Code: Select all

void R_SetLiquidAlpha (void)
{
	if (!level.water_vis_known && frame.has_abovewater && frame.has_underwater)
	{
		// Baker: Don't let this scenario lock us into a false reading.
		// Although this would be an extremely hard scenario to generate, would take an incredibly well placed saved game
		// and I tried hard to stand somewhere a save game cause this problem and couldn't despite my best efforts.

		if (!frame.nearwaterportal && !r_novis.value)
		{
			Con_DPrintf ("AUTO WATER VIS:  Level is vised!\n");
			level.water_vis_known = true;
			level.water_vis = true;
		}
	}

	if (r_novis.value)											frame.liquid_alpha = true;  // r_novis 1
	else if (level.water_vis_known && level.water_vis)			frame.liquid_alpha = true;  // Known to be watervised
	else if (level.water_vis_known && !level.water_vis)			frame.liquid_alpha = false; // Known to be not watervised
	else if (frame.has_abovewater && frame.has_underwater)		frame.liquid_alpha = true;	// Weird situation almost impossible
	else														frame.liquid_alpha = false;	 // Vis not known yet, but no water brushes in scene

	if (frame.liquid_alpha)
	{
		frame.wateralpha	= CLAMP(0, r_wateralpha.value, 1.0);
		frame.slimealpha	= CLAMP(0, r_slimealpha.value, 1.0);
		frame.lavaalpha		= CLAMP(0, r_lavaalpha.value, 1.0);
	} else frame.wateralpha = frame.slimealpha = frame.lavaalpha = 1;
}
4. R_MarkLeaves in WinQuake in r_main.c, R_MarkSurfaces in FitzQuake renderer

Code: Select all

	// Baker:
	{
		msurface_t **mark;
		for (i = 0, mark = cl.r_viewleaf->firstmarksurface; i < cl.r_viewleaf->nummarksurfaces; i++, mark++)
			if ((*mark)->flags & SURF_DRAWTURB)
				frame.nearwaterportal = true; // Baker: frame vars are reset to false each frame
	}

...

	if (frame.nearwaterportal && !level.water_vis_known)
	{
		// Baker: This is to avoid a spawn nearwaterportal situation confusing the detection which could be
		// in theory caused by a save game happening to save in such a rare place.  This is an almost impossible
		// scenario.
		if (cl.r_visframecount != 0)
		{
			Con_DPrintf ("AUTO WATER VIS:  Level is NOT vised!\n");
			level.water_vis_known = true;
			level.water_vis = false;
		}
	}
5. R_DrawTextureChains_Multitexture in GL, R_RecursiveWorldNode in WinQuake

Code: Select all

				if (!level.water_vis_known)
				{
					if (s->flags & SURF_UNDERWATER)
						frame.has_underwater = true;
					else
						frame.has_abovewater = true;
				}
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: Fully Automatic Transparent Water

Post by mh »

The idea I had was that if the current PVS contains both a CONTENTS_WATER (or lava/slime/etc) leaf and a leaf that isn't, then you have translucent water and can accept the value of r_wateralpha, otherwise you don't. It only needs to be evaluated when the PVS changes and is robust with changing values of r_novis/etc. I seem to remember that one time I coded it up I got a few false positives and false negatives, but I've just done so again and it seems solid. Maybe I had a different bug back then?

Also, I've long been puzzled by FitzQuake's "near water portal" thing. Checking if the contents are different between r_viewleaf and r_oldviewleaf seems to work just as well for handling water transitions (and also works great if you noclip into a wall).

Anyway, something like:

Code: Select all

	// go to a new visframe
	r_visframecount++;
	r_refdef.pvflags = 0;

	// add the main viewleaf
	R_AddViewLeaf (r_viewleaf);

	// set PV flags based on the main viewleaf only so that merging the transition leaf (if needed) doesn't falsely trigger translucent water
	R_SetPVFlags (cl.worldmodel->nodes);

	// check for a contents transition and add the old viewleaf if there was one
	if (r_oldviewleaf && r_viewleaf->contents != r_oldviewleaf->contents)
		R_AddViewLeaf (r_oldviewleaf);

	// store back main to old
	r_oldviewleaf = r_viewleaf;
And:

Code: Select all

void R_SetPVFlags (mnode_t *node)
{
	// don't frustum cull this because the frustum can change even if the PVS doesn't
	if (node->contents == CONTENTS_SOLID) return;
	if (node->visframe != r_visframecount) return;

	// if a leaf node, draw stuff
	if (node->contents < 0)
	{
		switch (((mleaf_t *) node)->contents)
		{
		case CONTENTS_WATER: r_refdef.pvflags |= PV_UNDERWATER; break;
		case CONTENTS_SLIME: r_refdef.pvflags |= PV_UNDERWATER; break;
		case CONTENTS_LAVA: r_refdef.pvflags |= PV_UNDERWATER; break;
		default: r_refdef.pvflags |= PV_ABOVEWATER; break;
		}

		return;
	}

	// recurse down both sides
	R_SetPVFlags (node->children[0]);
	R_SetPVFlags (node->children[1]);
}
And:

Code: Select all

#define PV_ABOVEWATER	1
#define PV_UNDERWATER	2
#define PV_WATERTRANS	(PV_ABOVEWATER | PV_UNDERWATER)
And:

Code: Select all

if (r_wateralpha.value < 1 && r_refdef.pvflags == PV_WATERTRANS) // it's OK to do translucent water now
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
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Fully Automatic Transparent Water

Post by Baker »

Looks like that could trim down the implementation quite a bit. I don't like how my check spans over a few different functions.
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: Fully Automatic Transparent Water

Post by mh »

In case you're interested, the missing R_AddViewLeaf function is this:

Code: Select all

void R_AddViewLeaf (mleaf_t *leaf)
{
	byte *vis = Mod_LeafPVS (leaf, cl.worldmodel);

	for (int i = 0; i < cl.worldmodel->numleafs; i++)
	{
		if ((vis[i >> 3] & (1 << (i & 7))) || r_novis.value)
		{
			mnode_t *node = (mnode_t *) &cl.worldmodel->leafs[i + 1];

			do
			{
				if (node->visframe == r_visframecount)
					break;

				node->visframe = r_visframecount;
				node = node->parent;
			} while (node);
		}
	}
}
That allows any arbitrary leaf to be merged into the current PVS, but (unless you're doing something really weird) you'll normally only merge r_viewleaf and sometimes r_oldviewleaf.
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
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Fully Automatic Transparent Water

Post by Baker »

mh wrote:That allows any arbitrary leaf to be merged into the current PVS, but (unless you're doing something really weird) you'll normally only merge r_viewleaf and sometimes r_oldviewleaf.
Eventually a mapper would produce a map that spawns somewhere so that in frame 0 of the map you are near a water portal and don't have a r_oldviewleaf to rely on :D Or a player would save a game that happened to in such a place. Or a teleporter teleports you there.

But isolating that trivial and bizarre possibility would be easy.
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: Fully Automatic Transparent Water

Post by mh »

Baker wrote:
mh wrote:That allows any arbitrary leaf to be merged into the current PVS, but (unless you're doing something really weird) you'll normally only merge r_viewleaf and sometimes r_oldviewleaf.
Eventually a mapper would produce a map that spawns somewhere so that in frame 0 of the map you are near a water portal and don't have a r_oldviewleaf to rely on :D Or a player would save a game that happened to in such a place. Or a teleporter teleports you there.

But isolating that trivial and bizarre possibility would be easy.
I don't think that's a problem.

The specific issue that's fixed is when you cross from underwater to abovewater, and the PVS changes. So long as you are in the same leaf there is no issue.
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
Cobalt
Posts: 445
Joined: Wed Jun 10, 2009 2:58 am
Location: New England, USA
Contact:

Re: Fully Automatic Transparent Water

Post by Cobalt »

While you guys are talking about water, what are your thoughts about an "underwater sound" system?

I have messed with it a little via ssqc, but probbaly much well working engine side I suppose.

Boils down to :

1) Declaring what ents can make underwater sounds IE: Player, missile, some doors / secret doors (e2m1 for ex)
2) Findradius to locate clients within a paramater near the sound (I selected about 1000 quake units as a start) and are waterlevel > 2.
NOTE: Trick with No. 2 is determining if they are in the same "body" of water as the ent making the sound.
3) Pitching the sound / selecting proper sounds for the event. Also the normal Quake Attenutaion scales seem to be ok in my tests, but real sound physics for underwater are different - sounds seem to travel at a linear fixed attenuation no matter the distance to the event.

4) Related to No. 3 , the issue attenuation has now is it does not take account for passing through water , so the attenuation level basicly is the same as if it occured out of water or in it.


In my crude tests, I also was able to scale the attenuation_exponent cvar (client side) in the engine so the deeper in water the client is, the lower they get as water depth increases. Apparently this cvar does not effect ambient sounds, but the method has its drawbacks such as on a map like e1m4 where there are pockets the client can resurface into, and sounds in that area most likely would not be heard at all unless present in that same pocket.

As a possible solution I thought of spawning large trigger fields over the water brushes that match their dimensions, but it seems to boil down to stuff engine side in the end that may be done alot better by someone with the skills, so thought I would mention it.
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Re: Fully Automatic Transparent Water

Post by r00k »

The specific issue that's fixed is when you cross from underwater to abovewater, and the PVS changes. So long as you are in the same leaf there is no issue.
So, the benefit of using an automatic water transparency system is to only draw underwater leafs in addition to the rest of the PVS, instead of with r_novis 2 and just drawing everything all the time?

Code: Select all


	if (!in || r_novis.value == 2)//BPJ
	{	// no vis info, so make all visible
		while (row)
		{
			*out++ = 0xff;
			row--;
		}
		return decompressed;		
	}
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Fully Automatic Transparent Water

Post by Baker »

My interest in automatic underwater transparency is:

1) The ability to default r_wateralpha to 0.5 or some other transparency value
Because an r_wateralpha value under 1 looks real shitty showing HOM (hall of mirrors) on a map that doesn't support transparent water. With automatic underwater transparency, you are able to correctly default r_wateralpha to a transparency value while ignoring the cvar entirely on a map with no water transparency available.
2) The correct presentation of a single player map (from the perspective of the map data). Without requiring the user to muck around and experiment or otherwise waste time thinking about playing with their settings.
Example, Travail doesn't have transparent water. Most modern maps do. Rather than force the user to mess around and try to determine if the map has transparent water, I want it happen automatically.
3) If the map doesn't have transparent water, why honor r_wateralpha at all in single player? In deathmatch, you could argue for transparent water on DM6 where seeing through the teleporter texture in the mega health room is an asset, but I'm just pointing out that example for completeness rather than to take it seriously.

4) Weakness: From a technical standpoint, the above implementations of automatic detection of transparent water don't know if the server is using transparent water (i.e. the server may not be showing entities underwater if the server isn't using water transparency, then again a client couldn't deal with the opposite case --- the server using water vised maps but the client not having them.]

5) Deathmatch: Deathmatch can benefit from automatic underwater transparency by detecting transparent water and therefore not using r_novis, because if the map actually has transparent water then r_novis isn't necessary. And you would get better performance than r_novis >= 1
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 ..
metlslime
Posts: 316
Joined: Tue Feb 05, 2008 11:03 pm

Re: Fully Automatic Transparent Water

Post by metlslime »

explanation of "nearwaterportal"

This part is from glquake:

if camera is close to a waterplane, something called "FatPVS" is used to combine the PVS from a cube of 8 sample points. This is to compensate for the water plane being clipped by the near clipping plane in openGL (resulting in a view that includes both sides of the water surface.) With regular PVS you've have HOM on part of the screen. With software rendered the water surface isn't clipped (and the camera is always 1/16th a unit off grid to prevent it being coplanar with the water.)

This part was added in fitzquake:


I cache the PVS whenever possible, for example as long as you stay in the same leaf. The problem is, that with FatPVS, the vis data could change even while you stay in the leaf. So i disable caching if "nearwaterportal" is true.
mankrip
Posts: 924
Joined: Fri Jul 04, 2008 3:02 am

Re: Fully Automatic Transparent Water

Post by mankrip »

Baker wrote:4) Weakness: From a technical standpoint, the above implementations of automatic detection of transparent water don't know if the server is using transparent water (i.e. the server may not be showing entities underwater if the server isn't using water transparency, then again a client couldn't deal with the opposite case --- the server using water vised maps but the client not having them.)
This server info should be added to the network protocol then.
Ph'nglui mglw'nafh mankrip Hell's end wgah'nagl fhtagn.
==-=-=-=-=-=-=-=-=-=-==
Dev blog / Twitter / YouTube
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Re: Fully Automatic Transparent Water

Post by mh »

mankrip wrote:
Baker wrote:4) Weakness: From a technical standpoint, the above implementations of automatic detection of transparent water don't know if the server is using transparent water (i.e. the server may not be showing entities underwater if the server isn't using water transparency, then again a client couldn't deal with the opposite case --- the server using water vised maps but the client not having them.)
This server info should be added to the network protocol then.
I'm more inclined to say that it should be standardised in the BSP format and then everything would just work, with all of these messy cases just going away.

Surface flags are one major omission from the Q1 format, and tools integration is the only thing preventing it from happening. Not the compilers, but map editors.

But there's only so much that one can continue patching the Q1 formats. If everyone would just agree to move over to the Q2 BSP format, so many things would become a whole lot easier.
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
mankrip
Posts: 924
Joined: Fri Jul 04, 2008 3:02 am

Re: Fully Automatic Transparent Water

Post by mankrip »

Sure, but maps that already exists should still be supported.
Ph'nglui mglw'nafh mankrip Hell's end wgah'nagl fhtagn.
==-=-=-=-=-=-=-=-=-=-==
Dev blog / Twitter / YouTube
Post Reply