Game Development Community

dev|Pro Game Development Curriculum

TGE water upgrade

by Manoel Neto · 07/03/2006 (11:33 am) · 347 comments

Download Code File

This resource adds basic pixel shader and vertex shader support to the TGE waterblock, using CG, and support for drawing a reflected view of the world into a texture and using it in the waterblock. Right now it's windows-only, but there's nothing stopping you from making it work on OSX and Linux, since CG is also avaliable in those platforms.

Note: For those worrying about Cg being Nvidia-only, fear not, I've personally tested it in different ATI cards and it's compatible with no visible performance problems. Just make sure your drivers aren't ancient.

Note2:
Alex Scarborough (of the TLK DRL fame) is working on porting this resource to GLSL and making it work on OSX. Might get some news soon.

Installing the source changes:

First you'll need the CG toolkit. You can get it here:
developer.nvidia.com/object/cg_toolkit.html

After installing it you must modify the TGE project to include the Cg/lib and Cg/include folders to the library paths and the include paths, respectively.

Important: you also need to open cgGL.h in the "Cg/include" folder and replace this:
#include <GL/gl.h>
By this:
#include <dgl/dgl.h>
Otherwise you'll get loads of compile errors.

Included in the zip file are the modified source files. To install into a unmodified TGE 1.4, just replace the TGE files by the ones included. To install in modified TGE sources, use a merge tool, or copy the changes by hand (all changes are comment-tagged), then compile.

Script changes and usage:

The example folder in the ZIP contains the script files you need to actually see the water in the game. The "example/CG" folder must be copied to the same folder as your executable. It contains the vertex and fragment shaders. The filenames are very hardcoded into the source code, unfortunally.

In "example/starter.fps", you'll find a modified playGui.cs and a normal map for using with the water (it's actually one of TSE's water textures).

The modified playGui.cs is required to render the reflection texture. The GameTSCtrl control was modified and now features a flag called "isReflection". Turning it on will make the gameTsCtrl render the world using a reflected camera matrix, based on the first existing waterblock.

Waterblocks will search for a GameTSCtrl called "reflectionGui". If it's found, they'll grab the refleciton texture from it.

Waterblocks also have a new field, called NormalMaptexture. You need a suitable normal map there, otherwise the reflection texture coordinates will be badly displaced.

Known bugs/limitations:

- No refraction support;
- No specular;
- No fog (at most times it looks like the water is fogged, because the reflected scene is fogged);
- No proper support for using a static envornment map instead of a full reflection (it does use the texture, but it remains fixed, and won't rotate along the view, as the original did);
- Sometimes the reflection can go strange and "melt away". Something to do with clip planes;
#241
07/24/2006 (1:24 pm)
I didn't mean he was lying, I think your second theory sounds a little more likely.
#242
07/24/2006 (1:30 pm)
@John, heh nice edit, woops :)

guys you might want to start a forum thread to discuss this, it will quickly rack up a lot of posts that don't really contribute to Manoel's excellent resource.

for those who do want to use GLSL, spend a little time converting this to cg1.5 beta 2 and use the GLSL profile, it shouldn't be that much work, and you'll gain some great new skills and knowledge
#243
07/24/2006 (3:04 pm)
Wow, touchy subject.

If it's such a big issue, I can move some of my changes back into Cg. Just things to make the resource fully useable, like fogging, specular, and not freaking out underwater. I'm rather busy at the moment, but I could get to it before IGC.

But if someone else wants to do it (please?). It's not like moving to GLSL really gets you anything other than not having to include an extra lib and possibly some better optimization on ATI cards. GLSL is, believe it or not, not a miracule cure for all OpenGL shaders. It's just kinda spiffy, and the only ARB approved high level shading language for OpenGL.

Oh, don't use the Cg 1.5 Beta 2 GLSL profiles. They're really inefficient at the moment, and produce messy code that makes the driver scream in terror. If you want a fun challenge, write a shader for the fp20 Cg profile.
#244
07/25/2006 (8:41 am)
I do most of my work on a Linux box. The Cg implementation kept giving me Segmentation Faults, I have no problems with GLSL. That was my reason for porting it to GLSL.

I didn't think it would be such a touchy subject either.
#245
07/25/2006 (11:09 am)
@Jason Peterson
If you have a working glsl version of this resource, would you mind posting it online. i myself am having trouble getting the cg version on linux/.
#246
07/25/2006 (12:33 pm)
I'd love to see a GLSL implementation too!

Just tossing my 2-bits in the bucket :)
#247
08/04/2006 (6:34 am)
Very nice resource :)

I don't know if these problems have been resolved, but if anyone's interested, I've somehow managed to fix the fog problem and the underwater rendering (refraction of whatever's on the surface). This is for CG though, not GLSL as people seem to be interested in. I'll backtrack my changes and post them here if anyone wants them. Be warned though - it's a soup of hacks :)
#248
08/04/2006 (6:53 am)
That would be cool Trond! I would really like to see what you have done.

Thanks!
#249
08/04/2006 (7:58 am)
I have absolutely no idea what sideeffects this may have, but so far, it seems to work ok. I'll start with listing the new shaders, which have been altered to handle fog.

In the vertex shader, the distance to the current vertex is being calculated, and a fogfactor for the current vertex is passed to the fragment shader, tagged along the variable formerly known as MPosition.
struct appdata {
	float4 Position		: POSITION;
	float4 UV0		: TEXCOORD0;
	float4 UV1	    	: TEXCOORD1;
	float4 UV2	    	: TEXCOORD2;
	float4 Normal		: NORMAL;
};

struct vertexOutput {
	float4 HPosition		: POSITION;
	float4 TexCoord0		: TEXCOORD0;	
	float4 TexCoord1		: TEXCOORD1;
	float4 Position			: TEXCOORD2;
	float4 DepthCoord		: TEXCOORD3;	
	float3 EyeVec			: TEXCOORD4;	
	float4 PositionAndFog	: TEXCOORD5;
};

vertexOutput main(appdata IN,
    uniform float4x4 ModelViewProj,
    uniform float4x4 ModelViewIT,
    uniform float4x4 ModelView,
	uniform float2	BaseDrift,
	uniform float	SurfaceParallax,
	uniform float3	EyePos,
	uniform float	FogStart,
	uniform float	FogEnd
)
{
	vertexOutput OUT;
	    
	OUT.TexCoord0 = IN.UV0 + float4(BaseDrift, 0, 0);
	OUT.TexCoord1 = IN.UV0 + float4(BaseDrift*SurfaceParallax, 0, 0);
	OUT.HPosition = mul(ModelViewProj, IN.Position);
	OUT.Position = mul(ModelViewProj, IN.Position);	
	OUT.DepthCoord = IN.UV2;
	
	OUT.EyeVec = EyePos;
	OUT.PositionAndFog.xyz = IN.Position.xyz;
	
	float3 pos = mul(ModelView, IN.Position).xyz;
	float fogDist = length(pos);
	float fogFactor = (FogEnd - fogDist)/(FogEnd - FogStart);

	OUT.PositionAndFog.w = fogFactor;	
		
    return OUT;
}


The fragment shader simply interpolates between the reflection result and the fog color based on the fog factor passed from the vertex shader.
struct vertexOutput {
    float4 HPosition		: POSITION;
    float4 TexCoord0    	: TEXCOORD0;	
	float4 TexCoord1    	: TEXCOORD1;
    float4 Position     	: TEXCOORD2;
	float4 DepthCoord   	: TEXCOORD3;	
	float3 EyeVec       	: TEXCOORD4;	
	float4 PositionAndFog 	: TEXCOORD5;
};

float fresnel(float NdotV, float bias, float power)
{
   return bias + (1.0-bias)*pow(1.0 - max(NdotV, 0), power);
}

float4 main(vertexOutput IN,
			uniform sampler2D waterTexture : TEXUNIT0,
            uniform sampler2D bumpTexture : TEXUNIT1,
			uniform sampler2D depthTexture : TEXUNIT2,
			uniform float2 TextureRatio,
			uniform float3 waterColor,
			uniform float3 fogColor): COLOR
{

	float2 refCoord = float2( ( IN.Position.x) / (IN.Position.w), 
                               (IN.Position.y) / (IN.Position.w) );
	refCoord = saturate( (refCoord + 1 ) * 0.5 );
	refCoord *= TextureRatio;	
	
	float3 normal0 = tex2D(bumpTexture, IN.TexCoord0.xy).rgb*2.0 - 1.0;
	float3 normal1 = tex2D(bumpTexture, IN.TexCoord1.xy*2).rgb*2.0 - 1.0;
	float3 normal = (normal0 + normal1);
	normal *= 0.1;

	float pix = 1.0/512.0;

	refCoord += normal.xy;
	refCoord.x = min(max(pix, refCoord.x), TextureRatio.x-pix);    
	refCoord.y = min(max(pix, refCoord.y), TextureRatio.y-pix);    
	
	float3 reflected = tex2D(waterTexture, refCoord).rgb;

	float depthAlpha = tex2D(depthTexture, IN.DepthCoord.xy).a;

	//Apply fresnel
	float3 eyeVec = normalize( IN.EyeVec.xyz - IN.PositionAndFog.xyz );
	normal = normalize(normal);
    	float fresnel = dot(eyeVec, normal);
	float bias = 0.12;
	float power = 6.0;
	
	fresnel = bias + (1.0-bias)*pow(1.0 - max(fresnel, 0), power);


	reflected = lerp(waterColor, reflected, fresnel);
	fresnel = max(0, fresnel-0.5)*2;
	
	float4 result = float4(reflected, depthAlpha+fresnel);
	
	float4 tmpresult = float4(result.x, result.y, result.z, 1);
	result.xyz = lerp(fogColor, tmpresult.xyz, clamp(IN.PositionAndFog.w, 0, 1));
	
    return result;
}


In the c++ code, the following changes were done:

sceneGraph.h (added variable to detect if we're over or under water)
class SceneGraph

--SNIP--

public:
   static bool useSpecial;
   bool mReflectPass;
   bool mCameraSubmerged;//CG Water: Hack to change water surface rendering method based on camera being over or under water.


fluidRender.cc (setting the parameters and setting over/under water flag)
void fluid::Render

//First test made in function!
//CG Water: Hack-of-the-day - to prevent the water surface from being rendered into the reflection texture...
if (gClientSceneGraph->mReflectPass)
	return;

--SNIP--

// Based on the view frustrum, accumulate the list of triangles that
// comprise the fluid surface for this render pass.
RunQuadTree( EyeSubmerged );

//CG Water: Hack to detect if camera is under water or not
gClientSceneGraph->mCameraSubmerged = EyeSubmerged;

CalcVertSpecular();

--SNIP--

// Pass the eye position
cgGLSetParameter3f( cgGetNamedParameter(cgVertexProgram, "EyePos"), m_Eye.X, m_Eye.Y, m_Eye.Z);                
			
// Pass the drift values to the shader
cgGLSetParameter2f( cgGetNamedParameter(cgVertexProgram, "BaseDrift"), BaseDriftX, BaseDriftY);

// Pass the surface parallax to the shader
cgGLSetParameter1f( cgGetNamedParameter(cgVertexProgram, "SurfaceParallax"), m_SurfaceParallax);	

//CG Water: Pass fog start and end distances, as well as fog and water color
//TODO: Add to water and skybox datablocks!
if (EyeSubmerged)
{
	cgGLSetParameter1f( cgGetNamedParameter(cgVertexProgram, "FogStart"), 200.0f);
	cgGLSetParameter1f( cgGetNamedParameter(cgVertexProgram, "FogEnd"), 300.0f);
	cgGLSetParameter3f( cgGetNamedParameter(cgFragProgram, "waterColor"), 0.07f, 0.30f, 0.07f);
	cgGLSetParameter3f( cgGetNamedParameter(cgFragProgram, "fogColor"), 0.82f, 0.828f, 0.844f);	
}
else
{
	cgGLSetParameter1f( cgGetNamedParameter(cgVertexProgram, "FogStart"), 500.0f);
	cgGLSetParameter1f( cgGetNamedParameter(cgVertexProgram, "FogEnd"), 800.0f);
	cgGLSetParameter3f( cgGetNamedParameter(cgFragProgram, "waterColor"), 0.12f, 0.22f, 0.25f);
	cgGLSetParameter3f( cgGetNamedParameter(cgFragProgram, "fogColor"), 0.82f, 0.828f, 0.844f);
}

sceneGraph.cc (set flipcull to false if we're under water, since we want regular culling for refraction)
//CG Water: We don't want to flip culling if we're doing a refraction pass, which is the
//case when we're submerged.
//TODO: Should have a separate state - mRefractionPass!!
pBaseState->mFlipCull = mReflectPass && !mCameraSubmerged; //<--- this was 'pBaseState->mFlipCull = mReflectPass;'

sky.cc (the krimzon-skybox-fix as well as a special case for under-water rendering)
void Sky::render

--SNIP--

if(gClientSceneGraph)
{
	F32 currentVisDis = gClientSceneGraph->getVisibleDistanceMod();
	if(mLastVisDisMod != currentVisDis)
	{
		calcPoints();
		for(S32 i = 0; i < MAX_NUM_LAYERS; ++i)
			mCloudLayer[i].setPoints();

		mLastVisDisMod = currentVisDis;
	}

	//CG Water:  krimzon - PRLD_WATER_SHADER
	if (gClientSceneGraph->mReflectPass)
	{
		glDisable(GL_CLIP_PLANE0);
	}
	// -krimzon
}

--SNIP--

/*
if(depthInFog > 0.0f)
      calcAlphas_Heights(camPos.z, banHeights, alphaBan, depthInFog);
   else // Not in fog so setup default values
   {
      alphaBan[0] = 0.0f;
      alphaBan[1] = 0.0f;
      banHeights[0] = HORIZON;
      banHeights[1] = banHeights[0] + OFFSET_HEIGHT;
   }*/

//CG Water: Added test for reflectpass, since we only want to clip in refraction pass.
   //TODO: I guess this can be done together with the krimzon-fix above.
   // Calculats alpha values and ban heights
   if(depthInFog > 0.0f && gClientSceneGraph && !gClientSceneGraph->mReflectPass)
      calcAlphas_Heights(camPos.z, banHeights, alphaBan, depthInFog);
   else // Not in fog so setup default values
   {
      alphaBan[0] = 0.0f;
      alphaBan[1] = 0.0f;
      banHeights[0] = HORIZON;
      banHeights[1] = banHeights[0] + OFFSET_HEIGHT;
   }

--SNIP--

if(mStormFogOn && mRealFog)
	updateRealFog();

//CG Water:  krimzon - PRLD_WATER_SHADER
if (gClientSceneGraph)
{
	if (gClientSceneGraph->mReflectPass)
		glEnable(GL_CLIP_PLANE0);
}
// -krimzon

PROFILE_END();

gameTSCtrl.cc (Only flipping camera around water plane if we're above water surface, if not, we just want refraction)
bool GameTSCtrl::processCameraQuery

--SNIP--

//CG Water: Only reflect around water plane if we're not submerged - if we're submerged, we'll just
//refract whatever is on the surface.
//FIXME: I guess it would be better to check the pBlock surfaceheight against the gClientSceneGraph
//		cameraposition...?
if (!gClientSceneGraph->mCameraSubmerged) //<--- This line is added so we'll only reflect if we're over water.
	camq->cameraMatrix = getCameraReflection(camq->cameraMatrix, plane);

terrRender.cc (Hack to render terrain above water when the camera is below)
void TerrainRender::renderBlock

--SNIP--

//CG Water: Use CCW faces as frontfaces when camera is submerged AND we're doing a refraction pass.
//Refraction pass = camera underwater and reflection pass :p
if (gClientSceneGraph->mCameraSubmerged
	&& gClientSceneGraph->mReflectPass)
	glFrontFace(GL_CCW);
else
	glFrontFace(GL_CW); //<-- This was the only line in the file before

I think that should be all. If it doesn't work, let me know and I'll see if I can figure out what I have forgotten. And yes, this definately would need a cleanup :)

Edit: It should end up looking something like this:
http://img272.imageshack.us/img272/714/cgwateraboveio3.th.png
http://img168.imageshack.us/img168/2352/cgwaterbelowqb4.th.png

Thanks to ImageShack for Free Image Hosting
#250
08/05/2006 (7:44 am)
Really awesome, works very well on tge1.4.
I just added Trond Abusdal codes and works great.
thank you everyone! :)
#251
08/05/2006 (10:17 pm)
@Trond Abusdal
Good stuff, works like a charm.
#252
08/06/2006 (12:01 pm)
We're having some problems getting the basic parts of this to work... we're using .net pro 2k3 the latest download of CG from NVidia (not the beta) and have tried merging the files, replacing the files to the basic SDK install (completely clean) and edited the file in cg to DGL etc... and are still getting the 100+ errors starting with...

D:\VC_NET\Vc7\PlatformSDK\Include\gl\GL.h(1152): error C2144: syntax error : 'void' should be preceded by ';'
D:\VC_NET\Vc7\PlatformSDK\Include\gl\GL.h(1152): error C2501: 'WINGDIAPI' : missing storage-class or type specifiers
D:\VC_NET\Vc7\PlatformSDK\Include\gl\GL.h(1152): error C2146: syntax error : missing ';' before identifier 'glAccum'

etc...

any ideas?
#253
08/06/2006 (1:50 pm)
@Dion Burgoyne
Try this Resource. I just merged the CG Water + DRL together, TGE 1.4 Ready. Just drag and drop in a clean build, and it should work.

www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=10591

Then add Trond Abusdal Bug fixes.
#254
08/06/2006 (8:01 pm)
I don't actually have access to that forum.... can you send it to me directly? dion at simpleblack.com
#255
08/07/2006 (6:15 am)
Oh Sorry, that code requires you to have TLK, i really recommend getting it.
#256
08/07/2006 (4:09 pm)
Ok, did that and I'm getting exactly the same errors

D:\VC_NET\Vc7\PlatformSDK\Include\gl\GL.h(1152): error C2144: syntax error : 'void' should be preceded by ';'
D:\VC_NET\Vc7\PlatformSDK\Include\gl\GL.h(1152): error C2501: 'WINGDIAPI' : missing storage-class or type specifiers
D:\VC_NET\Vc7\PlatformSDK\Include\gl\GL.h(1152): error C2146: syntax error : missing ';' before identifier 'glAccum'
D:\VC_NET\Vc7\PlatformSDK\Include\gl\GL.h(1152): error C2182: 'APIENTRY' : illegal use of type 'void'


has to be something with my setup... or with CG
#257
08/07/2006 (4:22 pm)
@Dion Burgoyne
did you include the cg "include" and "lib" files in your VC Directories?
#258
08/07/2006 (4:40 pm)
@T
Yep, VC++ Directories -> Include files -> C:\Program Files\NVIDIA Corporation\Cg\include
and Library files C:\Program Files\NVIDIA Corporation\Cg\lib

it's not complaining about missing files, it's just messing up when it hits VC's gl.h after trying to compile terrain render...
#259
08/07/2006 (7:17 pm)
OK, I figured it out... in cgGL.h there is more than one reference to gl.h about 20 lines down from the original replacement you need to change it to read

#ifdef __APPLE__
#include
#else
/*#include */
#include
#endif
#260
08/11/2006 (1:08 am)
Wow - excelent work everyone! Manoel, Alex, Alan, etc...

One question: My final water is shifted about 2 inches to the side, and there is a stretchy anomaly on the right 2 inch edge of the viewer. I also would like to know if I can increase the resolution of the reflection - as I see blocky edges.

Heres an image: http://www.trulyawesomegames.com/cgwater.jpg

Any ideas?