Page 1 of 1

Triggerable Shaders

Posted: Thu Jan 26, 2012 10:57 pm
by sock
Image
Article - http://www.simonoc.com/pages/articles/trigshaders1.htm

A small article about triggerable shaders showing how they work and things to avoid. I also created a couple of maps and shader files as working examples. (big download link at the top of page!)

Trying to keep all the information together in one location for ease of searching, previous conversation:
obsidian wrote:I was searching for our PM discussion about this problem for "Forever" and you beat me to it. Perhaps the player spawn could trigger a relay which resets the original shader state might work to solve the respawn problem?
The problem with resetting shaders at the beginning of a map reload is that you can cause the client command line to over run and crash the game. (1024 chars is the limit) The TA shader swap feature always seemed like an engine hack to me and not something that can be done on a larger scale. Ideally there should be a new feature created for this, maybe Eraser could code something better?

Here is my PM about shader swapping, if anyone is interested ...

My story so far ...

Solution 1: Originally I used a pair of func_door/buttons to switch shaders around. This was easy to implement and it worked a treat, but Q3 has a 255 hard limit on func items and it is extremely easy to hit this limit if you have multiple brushes for each shader swap. Both brushes have to be in the same position and one has to be moved on start (func_door-start open function) in order to get the lightmaps looking right. The func_ items were moved with -1 speed (instant move) and had the added bonus of additional sound attached to the func_ commands. I originally thought the -1 speed thing was instant but it is not, on one rare occasion I was standing on top of a func_ item and it pushed me upwards as it travelled 'instantly' to its new position. Not really an ideal solution as I was pushed out of the map into the void.

Solution 2: Setup multiple brush surfaces with different shaders and used the remap command via various trigger relay/delay entities. The off shaders were triggered on start of the map to be empty so the 'on' shader was visible. Getting a nodraw texture past the compiler was tricky at first but eventually I settled on the following shader:

Code: Select all

	qer_editorimage textures/common/black_nodraw.tga
	surfaceparm nomarks
	surfaceparm trans
	{
		map textures/common/black_nodraw.tga
		blendFunc GL_ONE GL_ONE
		rgbGen const ( 0 0 0 )
	}	
The trans was needed because the on/off shaders were on top of each and the compiler would randomly remove one. (it usually depended on which one was found first in the map file)

This solution heavily relied on the 'off' shader being setup at the beginning of the map correctly and I soon found a couple of new Q3 hard limits. When the remap command is run, it is instantly run, no delay and several at once will cause the client command line to over run and crash the game. (1024 chars is the limit) Each setup trigger had to be batched and delayed so they did not all happen all at once. The second limit was a maximum of 128 remap shader commands per map and with duplicates for each trigger this can be reached very easily as well.

The final problem is when the remap command is used, the engine assumes the shader assets used in the target shader are cached/loaded into memory and if not will cause huge fps drops (20-40) while it is loaded. The target shaders have to exist somewhere in the map before the remap command is used, so a small gallery box with a target shader is required.

Other compilications come from the shaders and the best results are when the source/target shaders share similiar/same amount of stages. Going from a 1/2 stage shader to a 4/5 stage shader will again cause huge FPS drops. Going down in stage numbers is fine but huge leaps upwards in stages can be serious. I found the best number of stages to use was 3 (lightmap+source+blend). Some of the more compilicated shaders I was testing with had multiple stages of tcmod commands which again 'seemed' to cause fps slowdowns. Eventually I tried to keep all tcmod stages to a bare minimum and not make the target shader too complex.

Solution 3: Based on solution 2 but removing all the startup remap triggers and using only one shader surface instead of two. The start up of the map was getting to be a huge problem with timing co-ordination and it was easier to just setup the source shaders in the shader file first. This solution involved re-working all the shader file but it did mean the map did not have any startup lag issues.

Unfortunately this solution still had one big problem, lightmaps. When the remap command is used the lightmap on the source shader is broken and will often randomly be full bright, black or a different colour shade to existing surfaces around it. I tried various shader tricks to fix the problem (tcgen lightmap, stylemarker and lightmapscale) but nothing worked 100% all of the time. If anything the various extra shader options just made the problem worse and even more random on which remap surfaces would be broken next compile.

Solution 4: Split the remap shader into two parts. The section to be remapped from the original shader was split into a separate part while the background + lightmap stayed static. Again the shader file had to be re-worked to fit with the new layout but it worked perfectly. The target shaders were cached via the gallery box, the remap shader were single staged so no weird engine redraw lag and best of all, the lightmaps were perfect across all surfaces being remapped.

Example shaders:

Code: Select all

// ----------------------------------------------------------------------
// Stuff to remap (sits on top of background)
textures/moteof/trlit_on
{
	qer_editorimage textures/moteof/switch_on.tga
	surfaceparm nomarks
	surfaceparm trans
	{
		map textures/moteof/switch_blend.tga
		blendfunc GL_ONE GL_ONE
		rgbGen wave sin .8 .3 0 0.25
	}
}
// ----------------------------------------------------------------------
// Background + lightmap shader (base)
textures/moteof/trlit_background
{
	qer_editorimage textures/moteof/switch_background.tga
	surfaceparm nomarks
	surfaceparm trans
	{
		map $lightmap
		rgbGen identity
	}
	{
		map textures/moteof/switch_background.tga
		blendFunc GL_DST_COLOR GL_ZERO
		rgbGen identity
	}
}
The stuff that needed to be switched on/off was in a seperate shader and using the previous nodraw shader (above) it was simple to use. Each instance where the shader was required, two patches were created. One had the background shader + lightmap and the second one had the remap shader which was switched on or off depending on remap state. Patches were used because the texture co-ordinates need to align up perfectly and brushes at weird angles do not always work correctly.

Final solution involved no extra func_ entities, two patches (background and remap surface), various trigger relay's to activate the remap commands and the target shaders cached in a gallery box. The remap shaders were all single stage and this was good news for the engine fps.

I have literally spent weeks on this trying to find the best solution and I think I have found a good one but I imagine the best solution will probably come from ydnar as he knows what the compiler is doing to the remap shader surfaces. (unfortunately he has left the Q3 scene so it is impossible to get his input anymore)

Re: Triggerable Shaders

Posted: Thu Jan 26, 2012 11:46 pm
by obsidian
Nice write-up! :up:

For many surfaces, it might be suitable to just use vertex lighting (remove lightmap stage and use rgbGen vertex) to sidestep the lightmap issue. In your switch example for instance, there probably wouldn't be much of a perceivable difference in appearance.

ydnar is zipping around SF on Ducati's these days because pedal power isn't fast enough. I don't think he would be able to answer much anyway, most of what goes on is probably engine coded. TTimo might know more, or anyone who is a little more well versed with the Q3 source could take a peek.

Re: Triggerable Shaders

Posted: Fri Jan 27, 2012 12:27 am
by sock
I tried vertex light on brushwork and patches and both surfaces were really dark. (bordering on black in some case). Eventually I got frustrated by it all and went back to the normal lightmap solution instead. I know you can tweak the brightness of vertex light on per shader basis, but that would be crazy for a lot of surfaces. Especially if lighting changed, it would involve re-tweaking everything again. Its certainly needs another set of eyes looking at it. Maybe if you get a chance, you could try vertex lighting the switches in the sample map I supplied.

Re: Triggerable Shaders

Posted: Fri Jan 27, 2012 12:57 am
by obsidian
I'll give it a shot in the next few days.