• Advertisement
Sign in to follow this  

Creating waves on a water surface (HLSL)

This topic is 4270 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey, I got a water reflection that stretches over a big quad made by two triangles. What I want to do now is distort the pixels on the water to make it look like the water is moving (waves). How can I do this using a vertexshader/pixelshader (HLSL)? My vertexshader looks like this:
vertexOutput VS_TransformAndTexture(vertexInput IN) 
{
   vertexOutput OUT;

   // This does projective texturing

   OUT.pos = mul(float4(IN.position.xyz , 1.0), worldViewProj);

   OUT.project.x = 0.5 * (OUT.pos.w + OUT.pos.x);
   OUT.project.y = 0.5 * (OUT.pos.w - OUT.pos.y);
   OUT.project.z = OUT.pos.w;
	
   OUT.color = IN.diffuseColor;

   return OUT;
}
... and my pixelshader looks like this:
float4 PS_Textured( vertexOutput IN): COLOR
{
   float2 projcoord = (IN.project.xy / IN.project.z);
   float4 reflect = tex2D(TextureSampler, projcoord);

   return reflect;
}
The result I would like to make it look something like this: Water sample Thanks in advance! [Edited by - kobingo on May 9, 2006 12:29:14 PM]

Share this post


Link to post
Share on other sites
Advertisement
Try passing a du_dv map of the wave into the shader.

use the red and green to offset the uv of the water reflection by a certailn amount. you can also incorporate the fresnel term if you want realistic reflection/refraction.

Share this post


Link to post
Share on other sites
Yeah, use a per-pixel distortion on the reflection coords from a normal or dudv map. The key is to distort the texture coordinates after projecting, so that you avoid some ugly glitches.

Share this post


Link to post
Share on other sites
It's not HLSL, but maybe this would help.

Also check out the site's forum, for it has a couple of additions to the tutorial.
Hope that helps.

Greets

Share this post


Link to post
Share on other sites
Here's how I'm doing it in one of my water shaders:

float3 combinedNormal;
combinedNormal = tex2D(animatedNormalMapSampler, IN.normalMapCoords1.xy);

//convert the normal from 0 to 1 into -1 to 1
combinedNormal = normalize(2 * combinedNormal - 1);

//IN.reflectCoords.z is the distance from the viewer to the pixel
float distScaledDistortion = IN.reflectCoords.z;

//setup a distortion fadeout with distance
distScaledDistortion = clamp(3 / (distScaledDistortion + 2), .3, 1);

//scale the normal by this distortion and a user-supplied constant - reflectDistortion
float3 scaledNormal = normalize(combinedNormal * float3(reflectDistortion * distScaledDistortion, 1, reflectDistortion * distScaledDistortion));

//project the coordinates and offset them by the normal
//note that up in this normal map is y not z
IN.reflectCoords.xy = IN.reflectCoords.xy / IN.reflectCoords.w + scaledNormal.xz;

float4 reflectiveColor = tex2D(reflectionSampler, IN.reflectCoords);



Hope that helps.

Share this post


Link to post
Share on other sites
It's really not that complicated.

Your quad should have some texture coordinates for the du-dv map.

1. Reflection

in the shader, offest the texture coordinates of the projected reflection by *2.0-1.0 of the du_dv map.

that will put wave on the reflection on the water.

2. Refraction

do an offscreent render to toexture from the camera pov.

pass the refraction texture to the matrix -

use a fresnel term to calculate a blend from the camera pov with the the du_dv map to mix the reflection and refraction texutes.

et voila.

Image Hosted by ImageShack.us



Share this post


Link to post
Share on other sites
also, don't use the above z read out of te refraction texture. you will be calculating the offset with air having the same coefficient as water.

create your own depth texture for the that, by using the pixels depth below the water plane level.

Share this post


Link to post
Share on other sites
jamesw, I tried this code you showed me, and now my pixelshader looks like this:

float2 projcoord = (IN.project.xy / IN.project.z);
float4 bump = tex2D(NormalSampler, projcoord);
float4 reflect = tex2D(TextureSampler, projcoord);

float3 combinedNormal;
combinedNormal = tex2D(NormalSampler, IN.project.xy);

//convert the normal from 0 to 1 into -1 to 1
combinedNormal = normalize(2 * combinedNormal - 1);

//IN.reflectCoords.z is the distance from the viewer to the pixel
float distScaledDistortion = IN.reflectCoords.z;

//setup a distortion fadeout with distance
distScaledDistortion = clamp(3 / (distScaledDistortion + 2), .3, 1);

//scale the normal by this distortion and a user-supplied constant - reflectDistortion
float3 scaledNormal = normalize(combinedNormal * float3(reflectDistortion * distScaledDistortion, 1, reflectDistortion * distScaledDistortion));

//project the coordinates and offset them by the normal
//note that up in this normal map is y not z
IN.project.xy = IN.project.xy / IN.project.w + scaledNormal.xz;

float4 reflectiveColor = tex2D(TextureSampler, IN.project);

return reflectiveColor;

But the problem is, not only does it distort the reflections, it also changes the uv coordinates of the reflections so much that it doesn't look right anymore... is there something wrong with my shader code?

Share this post


Link to post
Share on other sites
First are you sure you're projecting the reflective texture coordinates right? In other words, does it look like a mirror when you're not distorting the coords?

Make sure you set the scale constants appropriately. I'm using

float reflectDistortion = .03;

but it really depends on your normal map.

It looks like you are using projective coordinates to lookup into the normal map, this isn't what you want. The normal map should be applied just like a normal texture.

Share this post


Link to post
Share on other sites
Yes, my reflection is perfect when I don't distort it.

I changed the line from:
combinedNormal = tex2D(NormalSampler, IN.project.xy);

to:
combinedNormal = tex2D(NormalSampler, IN.texCoordDiffuse.xy);

but it was still the same problem...

Weird, but the whole reflected surfaces moves "up" when I do this. If I change the line:
IN.project.xy = IN.project.xy / IN.project.w <plus> scaledNormal.xz;

to:
IN.project.xy = IN.project.xy / IN.project.w - scaledNormal.xz;

(subtract instead of add) then the whole surface moves "down".

Also my float reflectDistortion is .03

Some error with scaledNormal?

Share this post


Link to post
Share on other sites
Could you post a screenshot?

Also make sure that your normal map is using x and z as the horizontal components, otherwise you will need to change the shader.

Share this post


Link to post
Share on other sites
Sure, here is a screenshot: Water 1
(you can see that the reflection offset is wrong)

Here is screenshot of only reflection (no distortion): Water 2
(the reflection offset is correct)

What do you mean by I must use the x and z horizontal components, and if I dont, what do I need to change in my shader?

Share this post


Link to post
Share on other sites
Quote:
What do you mean by I must use the x and z horizontal components, and if I dont, what do I need to change in my shader?


What I mean is the normal maps that I use with that shader have the normals stored as y axis going up and the x and z horizontal. So a vector straight up would be stored as (127, 255, 127). Most normal maps are stored with z up and x and y horizontal, so up would be (127, 127, 255). Basically if your normal map is predominately blue then you need to change how you treat the normal fetched from the normal map. So instead of

    float3 scaledNormal = normalize(combinedNormal * float3(reflectDistortion * distScaledDistortion, 1, reflectDistortion * distScaledDistortion));
IN.project.xy = IN.project.xy / IN.project.w + scaledNormal.xz;


you would put


    float3 scaledNormal = normalize(combinedNormal * float3(reflectDistortion * distScaledDistortion, reflectDistortion * distScaledDistortion, 1));
IN.project.xy = IN.project.xy / IN.project.w + scaledNormal.xy;

Share this post


Link to post
Share on other sites
Imagine your water surface has normals pointing upward from every pixel. Normals can be
calculated by the following formula

N = -dz/dx(i) - dz/dy(j) + k

Later normalize it.

However, most of the times this is precalculated in the normal map or can be easily calculated in the vertex-shader and interpolate along the whole tri. For that however, u need more tesellation, only two tris will look ugly.

For the moment you can using simple stuffs, as follows, as your wave model

z = A * sin(k*x) * cos(l*y);

Where A, k and l are constants.

Now imagine that whatever reflection texture you want to put on the water surface, is
lifted JUST A LIL ABOVE the surface. Note the phrase "JUST A LIL." Now for a texture coordinate s,t, make it 3d, (s,t,0). Now with the normal N , just project (s,t,0) to
that lifted surface along N.. i am using R ( or Z ) to be upward here. Which means

(s',t',r') = (s,t,0) + lambda * N;

Now use (s',t') to index into your texture. "lambda" is a very small number, u can't afford to make it large because when u took the snapshot of the reflected world (by RTT), u already lost one dimension, and hence u can't make lambda large...it will only make things look ugly.

Since we are only worried about s' and r' so the above equation actually boils down to

s' = s + lambda * N.x
r' = r + lambda * N.y

After doing all these.. u will still run into problems with texture sortof "waving" on the edges of the screen, depending on the orientation of your water with respect to camera. This is something which is VERY difficult to avoid to be honest. There are some hacks to get around this... which i will tell u once u get the basic thing right.

Btw, by just looking at one of ur screenshot.... DID YOU USE the same aspect-ratio when u did the RTT as when doing normal rendering to screen. IF you didn't then DO IT. You must use the SAME aspect-ratio when doing RTT. Usually beginners think that they should use an aspect ratio of 1 when using a RTT on a square texture (say, 256x256) but that is not correct.

Regards
Z

Share this post


Link to post
Share on other sites
Thanks, but I'm sorry I don't really understand what you are trying to explain, I think I already got the basics to work (reflection and distortion of water). The only problem right now is that there is a small gap between my terrain and the reflection in the water. Here is a new screenshot that shows what I mean

This is the pixelshader code I'm using:

float4 reflect = tex2D(TextureSampler, projcoord);

float3 combinedNormal;
combinedNormal = tex2D(NormalSampler, IN.texCoordDiffuse.xy);

float distScaledDistortion = IN.project.z;
distScaledDistortion = clamp(3 / (distScaledDistortion + 2), .3, 1);

float3 scaledNormal = normalize(combinedNormal * float3(reflectDistortion * distScaledDistortion, reflectDistortion * distScaledDistortion, 1));

IN.project.xy = IN.project.xy / IN.project.z - scaledNormal.xy;

float4 reflectiveColor = tex2D(TextureSampler, IN.project);
reflectiveColor = reflectiveColor * IN.color;

return reflectiveColor;


Also, I'm already using the same aspect-ratio in my RTT... I have tried to get this to work for a week now, starting to get really frustated :-/

Share this post


Link to post
Share on other sites
All the math looks right and with no distortion the reflection is right, so it seems like the problem lies in the format of your normal map. Make sure that it's an 8 bit or more per channel rgb format, no color-based compression, and the color value (127, 127, 255) is a unit vector straight up. Maybe you could post a shot of it.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement