Trying to implement water effect

Started by
10 comments, last by hartmannr76 13 years, 5 months ago
Greetings,
I have been attempting to replicate a water effect as implemented in this sample: http://www.humus.name/3D/Water.zip

I have been working on this for a while, but it just doesn't seem to come out right. There are a number of hard-coded constants in the shaders that have no explanation so I have used them directly, but I don't know if that was a good idea or not.

Here is a small (2.7MB) video of what I get.
http://www.mediafire.com/?3ss4ku3gfjuuajg

I'm hoping that someone might give me a clue as to why:
* the cubemap sampling doesn't work (I'm using the same cubemap image from the sample)
* the water gets that weird oscillating shimmer effect over the whole surface
* I can't see my background image at all

I have included details below and would greatly appreciate some input. Thank you for your time.
Murrgon



Original sample setup:
* render a skybox
* render the water on top of the skybox
* water surface is on the X-Z plane

My Setup:
* render a background image (it's a bunch of jelly beans)
* render the water on top of the background
* water surface is on the X-Y plane

Notes:
* My camera is static, it doesn't move
* it's looking at 0, 0, 0 from 0, 0, -HalfViewWidth
* field of view angle is 90 degrees



My version of the waterphysics shader:

// Mouse image sampler and texture
texture txtMouse;
sampler2D g_Mouse : register(s0) = sampler_state
{
Texture = <txtMouse>;
};

// Old height map
texture txtOldMap;
sampler2D g_OldMap : register(s1) = sampler_state
{
Texture = <txtOldMap>;
AddressU = Clamp;
AddressV = Clamp;
MinFilter = Point;
MagFilter = Linear;
MipFilter = Linear;
};


float g_fXPixelSize = 1 / 1024; // Calculate the size of one pixel on X axis
float g_fYPixelSize = 1 / 768; // Calculate the size of one pixel on Y axis
float g_fHeightOffset = 0.5f;


float4 PropagateWater(float2 vTex : TEXCOORD0) : COLOR0
{
float4 vHeight = tex2D(g_OldMap, vTex);

float fTopLeft =
tex2D(g_OldMap, float2(vTex.x - g_fXPixelSize, vTex.y - g_fYPixelSize)).x;
float fBottomLeft =
tex2D(g_OldMap, float2(vTex.x - g_fXPixelSize, vTex.y + g_fYPixelSize)).x;
float fTopRight =
tex2D(g_OldMap, float2(vTex.x + g_fXPixelSize, vTex.y - g_fYPixelSize)).x;
float fBottomRight =
tex2D(g_OldMap, float2(vTex.x + g_fXPixelSize, vTex.y + g_fYPixelSize)).x;

float fResult = (fTopLeft + fBottomLeft + fTopRight + fBottomRight) -
(vHeight.x * 4);

vHeight -= 0.5;

vHeight.y += 0.1 * fResult;
vHeight.x += 0.6 * vHeight.y;

// Damping
vHeight *= 0.985;

// sobelX
vHeight.z = 0.25 * (fTopLeft + fBottomLeft - fTopRight - fBottomRight);
// sobelY
vHeight.w = 0.25 * (fTopLeft - fBottomLeft + fTopRight - fBottomRight);

// Add the mouse interaction here
float3 vMouse = tex2D(g_Mouse, vTex).xyz;
vHeight.x += g_fHeightOffset * ((vMouse.x + vMouse.y + vMouse.z) / 3);

vHeight += 0.5;

return vHeight;
}



Here is my version of water.shd:

// Contents of the new height map
texture txtHeightMap;
sampler2D g_HeightMap : register(s0) = sampler_state
{
Texture = <txtHeightMap>;
AddressU = Clamp;
AddressV = Clamp;
MinFilter = Point;
MagFilter = Linear;
MipFilter = Linear;
};

// Contents of the environment map
texture txtEnvMap;
samplerCUBE g_EnvMap : register(s1) = sampler_state
{
Texture = <txtEnvMap>;
AddressU = Clamp;
AddressV = Clamp;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
};


float4 WaterHD(
float2 vTex : TEXCOORD0,
float3 vView: TEXCOORD
) : COLOR0
{
float4 vWaterColor = float4(0.3, 0.4, 1.0, 0.0);

float4 vHeight = tex2D(g_HeightMap, vTex) - 0.5;
float sobelX = vHeight.z;
float sobelY = vHeight.w;

// In water.shd, the sobelY ends up being assigned to vNormal.z, but I'm
// working on the XY plane, not the XZ plane so I switched it.
float3 vNormal = float3(sobelX, sobelY, 0.04);

vNormal = normalize(vNormal);
vView = normalize(vView);

float3 vReflectionDir = reflect(vView, vNormal);
float4 vReflectionClr = texCUBE(g_EnvMap, vReflectionDir) * 0.9;

float3 vRefractionDir = refract(vView, vNormal, 0.85);
float4 vRefractionClr = texCUBE(g_EnvMap, vRefractionDir) * vWaterColor;

return lerp(vReflectionClr, vRefractionClr, -dot(vView, vNormal));
}
The most exciting phrase to hear in science, the one that heralds new discoveries, is not "Eureka!" (I found it!) but "That''s funny ..."
Advertisement
been there

the solution is straightforward

you have to create some tweakable parameters
and then tweak them till it looks right

a debug view showing all the textures and normals helps

springfields only work with a fixed time-step
and water shaders are very sensitive

I posted a working springfield shader here:

http://www.gamedev.net/community/forums/topic.asp?topic_id=586959

which is probably almost identical to humus

but I am still tweaking my water now ...

this is the best I managed so far: http://www.youtube.com/FloodleObb
Are you doing this in XNA 4.0 or 3.1? I'm glad to see you've posted this here because I wasnt sure if I was the only on looking haha... I've been following Riemers tutorials and am stuck when it comes the the water because the ClipPlane functionality has been removed from the 4.0 (which is what I'm using) framework, and have been unable to find a workaround for it... so if anyone knows one can you please post it??

Also, I've been reading into the post processing water effect on this article
http://www.gamedev.net/reference/programming/features/ppWaterRender/
and have been unable to figure it out and put it into shader language... I'm relatively new to graphic programming and know very little about programming shaders but would like to know more...

I've been trying to figure out these solutions for days and have been posting on every forum except this one, and have gotten no replies... If you find anything or if anyone knows how to solve this... please post a solution!
use the hlsl clip() function

it clips the pixel if the parameter is negative

so if your water plane is at posWorld.y = 0
this will clip everything below water: clip(posWorld.y);

and this everything above water: clip(-posWorld.y);

when working with XNA it is a good idea to implement a WinForm control panel
in my games I register variables like this:

[ControlPanelItem]
float Damping; // declare variable with ControlPanelItem attribute

ControlPanel.Scan(this); // scan the object for properties or fields with ControlPanelItem attributes

http://img585.imageshack.us/img585/69/controlpanela.png

now I can tweak these parameters by dragging the mouse in the textbox (like 3ds max)

this will save you a LOT of time in the long run ... you can register your springfield parameters (damping, force factor, etc.) and just tweak

if I press CTRL-C on the form it fills the clipboard with csharp declarations for the current settings:

float Fscale = 0.1f;
float TileSize = 5.0f;

so I can just paste it in when it looks right
Thank you for explaining how that statement works!

So I were to get the image below the water plane(so i cn refract that image)... I would not change the renderelement but would check the y distance of the vertexs from the terrain to the water plane and clip all the vertexs above the plane?

If I'm not understanding this correctly, please correct me.

Otherwise, good idea with the forms during the debugging... i didnt think of that but can see how that would come in handy
clip the PIXELS above the water plane
(or below if the camera is underwater)

this is how I do it:

- render refraction map (render pixels below water plane)
- render reflection map (render pixels above water plane but y *= -1 (at the end of the vs))
- render real geometry (render pixels above water plane)
- render water (just a plane sampling refraction, reflection, normal map and springfield and using fresnel term)
- render skybox

it is very easy - but tweaking the colors, fresnel, normal strengths, etc. can take a LONG time
I thank you for your replies, but I still don't have this working yet. And no, I'm not using XNA, just plain DirectX and C++.

I have resorted to simply rendering the height/velocity surface directly to watch how it changes.

As I mentioned I have been working from Humus' sample, but I also tried to use your water propagation shader, skytiger, both from the post you linked and from this earlier one as well:
http://forums.create.msdn.com/forums/p/20551/109982.aspx

With the code you posted in the other gamedev.net link, I ended up with a green (velocity channel) smearing algorithm. With the code I found at the XNA forum, I got a red (height channel) smear towards the top left (same direction for the velocity smear).

All of the methods given are relatively similar, in their code, but there are subtle differences and many things are taken for granted. For instance in the code posted here:
http://www.gamedev.net/community/forums/topic.asp?topic_id=586959
up at the top you have:
float fi; // about 0.410
float vi; // about 1.000
float damp; // about 0.999

I'm assuming 'damp' is the damping factor and the code would indicate that. But the best I could come up with for 'fi' and 'vi' is 'force influencer' and 'velocity influencer' which don't really have a good explanation as to what they should be accomplishing.

The other samples I have found have similar numbers in them, either hard coded or used as shader constants with no explanation as to their use or even range of values. I dislike making assumptions about code.

Also, the Humus code uses a four-corners sampling instead of the up/down/left/right sampling that is used in the other examples. I'm not sure if this makes a big difference.

I have been attempting to use PIX to help debug this, but PIX doesn't seem to be behaving properly. I'm on Win7 64bit and if I run the 64 bit version of pix, no matter what I do, all constants in the shader debugger end up being zero. If I run the 32bit version, the constants show up properly but I get weird texture samples from tex2D() where my values are (22430.0000, 3483.0000) for x,y from the height field. It was my understanding that it should give me values between 0 and 1? I'm using the June2010 version.

Murrgon
P.S. gamedev.net really needs a preview button on their forums.
The most exciting phrase to hear in science, the one that heralds new discoveries, is not "Eureka!" (I found it!) but "That''s funny ..."
I'm trying to replace the clip plane functionality in XNA with this code I found on the XNA site...
struct CVertexToPixel{	float4 Position     : POSITION;	float4 Color        : COLOR0;	float2 TextureCoords: TEXCOORD1;};struct CPixelToFrame{	float4 Color : COLOR0;};void Clipvs(inout float4 position : POSITION0, out float4 clipDistances : TEXCOORD0){	clipDistances.x = 0;     clipDistances.y = dot(position.y, ClipPlane0.y);     clipDistances.z = 0;     clipDistances.w = 0;    	position = mul(mul(mul(position, xWorld), xView), xProjection);}  float4 Clipps(float4 clipDistances : TEXCOORD0) : COLOR0 {  	clip(clipDistances);    return float4(0,0,0,0); // TODO: whatever other shading logic you want }  technique Clip{ 	pass Pass0    {         VertexShader = compile vs_2_0 Clipvs();         PixelShader = compile ps_2_0 Clipps();     } } 


But I'm getting this effect: http://imagebin.org/123358

I just dont understand why this isnt clipping properly..
OK, after many MANY hours of trying to get a clip plane to work, I finally have a clip plane that matches the height I need it to be at, and it actually clips... now my problem is that it clips the wrong side, and I can't get it to flip... does anyone have any ideas?

P.S. Using XNA 4.0

Thanks!
Quote:Original post by Murrgon
I thank you for your replies, but I still don't have this working yet. And no, I'm not using XNA, just plain DirectX and C++.


- springfields sometimes "explode" due to a divergent oscillation
- keep your damping factor quite low (0.980 is more stable than 0.999)
- you need a way to reset the springfield (set height and velocity back to 0) without restarting - this is so you can experiment with the parameters till it works for you
- you need a way to interactively tweak those parameters

also there is no harm in clamping the 3 values

force = clamp(force, -1, 1);
velocity = clamp(velocity, -1, 1);
position = clamp(position, -1, 1);

until you get it under control

instead of PIX pull the data out the texture in your program and dump sections of it as a grid of numbers

fi and vi are just replacements for physical constants such as: mass, elapsed time, spring stiffness

as they are just constants multiplied together it makes sense to replace them with a single tweakable parameter

as fi increases the waves propagate faster (and the springfield gets more sensitive and unstable)

as vi decreases the water gets "thicker" and moves slower (more like treacle)

I pulled my hair out over this first time round, but it is simple - AS LONG AS you can tweak the parameters (and reset the springfield) in realtime

This topic is closed to new replies.

Advertisement