Tutorial: Adding Alpha Transparency to stock GLQUAKE

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: Adding Alpha Transparency to stock GLQUAKE

Post by Baker »

Image

Adding Alpha Transparency to stock GLQUAKE

Alpha support lets an engine render func_walls and other map objects with transparency.
Currently alpha brush supporting:

DarkPlaces, FTEQW, JoeQuake, Qrack and aguirRe GLQuake and probably a number of modder engines.

Currently NOT alpha brush supporting:

FitzQuake, ezQuake, FuhQuake, TyrQuake, most others.
There are some other modified engines with alpha brush support, but I'm not going to do research on it except I know the following engines do NOT have it:

This tutorial is for support of alpha brushes (i.e. not monsters and sprites). I can't think of a good use of transparent monsters or sprites, but I do like the option of maps with glass looking objects in them.

This tutorial won't give WinQuake this effect, but nothing in this tutorial adversely affects WinQuake (it will compile and run fine with this code).

Before we begin:

1. A good example map is Forwards Compatible (link)

2. Enabling alpha support requires a custom progs.dat file. Merely grab the QuakeC 1.06 source (download). Open up defs.qc and add this to the bottom of the file and compile it with your QuakeC compiler of choice (like FTEQCC).

Code: Select all

.float alpha;
3. To make a func_wall or other map object transparent, make sure the brush has an alpha field with some value less than 1 but greater than 0 in the .map file (you'd probably need to manually add the field to a map editor entity definition file.)

Code: Select all

{
"classname" "func_wall"
"alpha" "0.2"
"targetname" "fiendfield"
...
}
Instuctions adding it to stock GLQuake

We are going to do some light changes to NINE files.

Let's hit the header files first.

1. glquake.h
glquake.h - Locate "texture_t *R_TextureAnimation ..."; insert this line right after:

Code: Select all

#define ISTRANSPARENT(ent)	((ent)->istransparent && (ent)->transparency > 0 && (ent)->transparency < 1)
We are defining the test of whether or not a brush is transparent.
2. progs.h
progs.h - At bottom of file add ...:

Code: Select all

#define	GETEDICTFIELDVALUE(ed, fieldoffset) (fieldoffset ? (eval_t *)((byte *)&ed->v + fieldoffset) : NULL)
// alpha support
extern	int	eval_alpha;
We are borrowing JoeQuake's speedy field lookup shortcut for checking entities. The field we will be using this with is alpha, of course.
3. protocol.h
protocol.h - Find "#define U_LONGENTITY (1<<14)", insert after:

Code: Select all

// alpha support
#ifdef GLQUAKE
#define	U_TRANS		(1<<15)
#endif
We need to add a transparency bit to the protocol definition.
4. render.h
render.h - Find "struct mnode_s *topnode;", insert after:

Code: Select all

#ifdef GLQUAKE
		
		// alpha support
		float			transparency;
		qboolean 		istransparent;
#endif
We need the transparency characteristics of the entities stored and readily available.
Now let's do the actual code ...

5. cl_main.c
cl_main.c: Find our CL_RelinkEntities. Find this at the end of the procedure ...

Code: Select all

		if (cl_numvisedicts < MAX_VISEDICTS)
		{
			cl_visedicts[cl_numvisedicts] = ent;
			cl_numvisedicts++;
		}


Add this immediately after:

Code: Select all

#ifdef GLQUAKE
		if (!ent->transparency)
			ent->transparency = 1;
#endif
This was in JoeQuake 0.14 Build 839. Seems superfluous to me. Looks like it is making sure any value of 0 is set as 1. I'm pretty sure this isn't needed, but just to be safe.

6. cl_parse.c
cl_parse.c: Find "void CL_ParseUpdate (int bits)". Find this ...

Code: Select all

	if ( bits & U_NOLERP )
		ent->forcelink = true;


Add immediately before:

Code: Select all

#ifdef GLQUAKE
	if (bits & U_TRANS)
	{
		int	temp;

		temp = MSG_ReadFloat ();
		ent->istransparent = true;
		ent->transparency = MSG_ReadFloat ();
	}
	else
	{
		ent->istransparent = false;
		ent->transparency = 1.0;
	}
#endif
This is where the client is reading the data from the server and if the transparency bit is set, it needs to read in the transparency and set the entity attributes accordingly.
7. gl_rsurf.c
gl_rsurf.c: Find "void R_DrawBrushModel (entity_t *e)". Find this ...

Code: Select all

	if (R_CullBox (mins, maxs))
		return;

	glColor3f (1,1,1);


(7A) Replace with this:

Code: Select all

	if (R_CullBox (mins, maxs))
		return;

	if (ISTRANSPARENT(e)) {
		glEnable (GL_BLEND);
		glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		glColor4f (1, 1, 1, e->transparency);
	} else {
		glColor3f (1,1,1);
	} 
There is where we are drawing the brush models and if there is transparency we need it to render tranparently.

Now we need to turn it off afterwards ...

FIND about 30 lines further down in gl_rsurf.c:

Code: Select all

	R_BlendLightmaps ();

	glPopMatrix ();
(7B) Add after:

Code: Select all

	if (ISTRANSPARENT(e))
	{
		glColor3f (1,1,1);
		glDisable (GL_BLEND);
	}
8. pr_edict.c
pr_edict.c: Find:

Code: Select all

typedef struct {
	ddef_t	*pcache;
	char	field[MAX_FIELD_LEN];
} gefv_cache;

static gefv_cache	gefvCache[GEFV_CACHESIZE] = {{NULL, ""}, {NULL, ""}};


(8A) Add this immediately after:

Code: Select all

// alpha specific
int	eval_alpha;

ddef_t *ED_FindField (char *name);

int FindFieldOffset (char *field)
{
	ddef_t	*d;

	if (!(d = ED_FindField(field)))
		return 0;

	return d->ofs*4;
}

void FindEdictFieldOffsets (void)
{
	eval_alpha = FindFieldOffset ("alpha");
}
(8B) Now at bottom of PR_LoadProgs find:

Code: Select all

	for (i=0 ; i<progs->numglobals ; i++)
		((int *)pr_globals)[i] = LittleLong (((int *)pr_globals)[i]);
And replace with:

Code: Select all

	for (i=0 ; i<progs->numglobals ; i++)
		((int *)pr_globals)[i] = LittleLong (((int *)pr_globals)[i]);

	FindEdictFieldOffsets ();
Speedy field lookup functions.
9. LAST: sv_main.c
sv_main.c
: Find "SV_WriteEntitiesToClient" ... you will see:

Code: Select all

	int		e, i;
	int		bits;
	byte	*pvs;
	vec3_t	org;
	float	miss;
	edict_t	*ent;


(9A) Add this immediately after:

Code: Select all

#ifdef GLQUAKE
	eval_t  *val;
	float	alpha;
#endif
Find:

Code: Select all

		if (ent->baseline.modelindex != ent->v.modelindex)
			bits |= U_MODEL;
(9B) Add this immediately after:

Code: Select all

#ifdef GLQUAKE
	// nehahra: model alpha
		if ((val = GETEDICTFIELDVALUE(ent, eval_alpha)))
			alpha = val->_float;
		else
			alpha = 1;

		if (alpha < 1 && alpha > 0)
			bits |= U_TRANS;
#endif
Find:

Code: Select all

		if (bits & U_ANGLE3)
			MSG_WriteAngle(msg, ent->v.angles[2]);
(9C) Add this immediately after:

Code: Select all

#ifdef GLQUAKE
		if (bits & U_TRANS)
		{
                MSG_WriteFloat (msg, 2);
                MSG_WriteFloat (msg, alpha);
		}
#endif
The above is where the server sends the data to the client if the tranparency bit is set.

THE END
avirox
Posts: 137
Joined: Wed Aug 16, 2006 3:25 pm

Post by avirox »

Baker you are a life saver! I've been trying to figure out getting alpha to work on brush models in ezquake. I'll give this a shot tomorrow night, and hopefully if it works the ezq devs will add my alpha .patch to the SVN build along with your brush alpha method. Thanks!
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

MSG_WriteFloat (msg, 2);
MSG_WriteFloat (msg, alpha);


I'm sorry, but... EGADS!
the first float is redundant, the second float is 4 times as large as is really useful.

looks good other than that though.
metlslime
Posts: 316
Joined: Tue Feb 05, 2008 11:03 pm

Post by metlslime »

currently, this tutorial creates an engine that is only good for single-player, and won't even be able to read stock quake demos. might be good to:

1. change the protocol number so other clients will know they can't talk to this modified server, and can't play demos recorded with this modified client.

2. have the modified client check for protocol version and not read the extra bytes if protocol == 15, otherwise you won't be able to read stock protocol 15 demos.

3. get rid of most of the #ifdef GLQUAKE lines, otherwise the win and gl compiles from the same codebase won't even be able to talk to each other. The only thing winquake should do differently from glquake is ignore transparency when rendering. The network code and server-side stuff should be identical between the two builds.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

metlslime wrote:currently, this tutorial creates an engine that is only good for single-player, and won't even be able to read stock quake demos.
I'm going to write another tutorial replacing this one with the rest of the Nehahra supported added in, so the protocol used in the demos at least matches something that exists.

I'll have to check and find out what protocol number Nehahra uses (I hope it does have its own and isn't using 15).

The above is derived from JoeQuake and it will play regular Quake demos. And write regular Quake demos if the transparency feature isn't being used (id1, etc.), but if a demo were recorded running a mod with the alpha transparency field ...

I'll do some research into what aguirRe's convert demos utility does and see maybe if there is some way to add some sort of universal [ish] demo compatibility.

So I'll see what the existing Nehahra standard is.
Last edited by Baker on Fri Nov 21, 2008 11:40 pm, edited 1 time in total.
metlslime
Posts: 316
Joined: Tue Feb 05, 2008 11:03 pm

Post by metlslime »

actually, i realize i was wrong about demos, it won't actually choke on quake demos, since without that U_TRANS flag, it won't try to read extra floats.

But i think it is correct that if the modified server claims to use protocol 15, regular clients will crash when they see a transparent entity. And a demo with transparent entities in it will crash a standard client.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

metlslime wrote:actually, i realize i was wrong about demos, it won't actually choke on quake demos, since without that U_TRANS flag, it won't try to read extra floats.

But i think it is correct that if the modified server claims to use protocol 15, regular clients will crash when they see a transparent entity. And a demo with transparent entities in it will crash a standard client.
Ah you replied while I was editing.

This topic needs some more research. Because I can see some very fun protocol breaking extras that would keep running into this issue again, again above and beyond this.

Not to mention whatever you have cooked up for FitzQuake 0.85 (aguirRe's protocol?) ;)
metlslime
Posts: 316
Joined: Tue Feb 05, 2008 11:03 pm

Post by metlslime »

Baker wrote:This topic needs some more research. Because I can see some very fun protocol breaking extras that would keep running into this issue again, again above and beyond this.

Not to mention whatever you have cooked up for FitzQuake 0.85 (aguirRe's protocol?) ;)
I think in general, when making a new protocol you should use a new number, and have both client and server be able to handle both 15 and any new number you want to implement. There are several protocol 15s out there now, and most of them do operate on the philosophy that "if you happen to run a map/mod that doesn't use these features, it will devolve into standard protocol 15."

That may be true, but the whole point of a protocol number is to tell the client whether the information is going to be comprehensible. But with these protocols, you get a "maybe it'll work, maybe you'll crash halfway through."

As for fitzquake's protocol, my plan has been to basically pack as much as i reasonably can into one new protocol, rather than slowly adding a feature at a time and then having 10 different versions to support. There will still probably be a need for another version someday, but I'd rather have a few major version than many minor versions floating around.
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Post by goldenboy »

The only thing winquake should do differently from glquake is ignore transparency when rendering. The network code and server-side stuff should be identical between the two builds.

Exactly.

I think "Winquake" should simply not render transparent brush entities - so you can still look through windows. All other transparent entities should just be solid in software.

Sure, you'll alert monsters etc - but that's the mapper's problem. Monsters behind an .alpha func_wall really should be alerted, too.
leileilol
Posts: 2783
Joined: Fri Oct 15, 2004 3:23 am

Post by leileilol »

Then add alpha transparency to stock winquake too to make it fair - just rip the code from Quake2!
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

leileilol wrote:Then add alpha transparency to stock winquake too to make it fair - just rip the code from Quake2!
Looks like that might take a little bit of work
Quake 2 source wrote: pixel_t *alphamap; // 256 * 256 translucency map
It is my understanding that the winquake window is initialized in a 256 color palette mode. It looks like the q2 method uses 256^2 colors.

Options:

1. Increase the # of colors available in WinQuake to 2^16 or 2^24 so the alpha effect is possible (might reduce speed, then again Quake 2 doesn't seem slow in software rendering mode .. but I don't have a slow 400 Mhz machine to be able to know).

2. Render transparent brushes in WinQuake as a "screen" where 25% of the pixels [or some other ratio] are fully transparent (Half-Life appears to do this.] using a predefined mask.

3. Some sort of improvised 256 color method that fakes an alpha table with the palette.
leileilol
Posts: 2783
Joined: Fri Oct 15, 2004 3:23 am

Post by leileilol »

*facepalm*

You completely misunderstood the code. Quake2 uses 256 colors. What that comment means a lookup table at the size of 256x256 pixels big.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

leileilol wrote:*facepalm*

You completely misunderstood the code. Quake2 uses 256 colors. What that comment means a lookup table at the size of 256x256 pixels big.
Color mixing table: color (0-255) with alpha (0-255) = result color2 (0-255)? If so, wouldn't this table be sort of a pain to figure out for Quake 1.

Either way, that leads to a simple idea on how to quickly make a translucent looking glass in software not as fancy as Quake 2.

Image

Create a function to return the palette position a couple of columns shifted based on the alpha. It could look wrong with colors in the fullbright rows, but a minor side downside on a small minority of colors.

Or at least in theory it might look ok.

Unless someone has an idea on how the Quake 2 method (that I still may or may not completely understand) could be employed.
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

Spike wrote:MSG_WriteFloat (msg, 2);
MSG_WriteFloat (msg, alpha);


I'm sorry, but... EGADS!
the first float is redundant, the second float is 4 times as large as is really useful.

looks good other than that though.
the 1st float of 2 it to set FULLBRIGHT entities in Nehahra,

Code: Select all

	if (bits & U_TRANS)
	{
		int	fullbright, temp;

		temp = MSG_ReadFloat ();
		ent->transparency = MSG_ReadFloat ();
		if (temp == 2)
			fullbright = MSG_ReadFloat ();
	}
which seems odd cause temp is always going to be 2

Code: Select all

		if (bits & U_TRANS)
		{
			MSG_WriteFloat (msg, 2);
			MSG_WriteFloat (msg, alpha);
			MSG_WriteFloat (msg, fullbright);
		}
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Post by goldenboy »

I can run Quake 2 on a Pentium 1, and software transparency on those windows seems no problem at all in 512x384 or 400x300. It's fluid. Same FPS as Quake on that machine. Well maybe 10% less or something. But it runs OK.

I think I remember transparent water in software in FTE - dunno if it also does alpha entities. I should test. :-E
Post Reply