GPU Gems Gerstner waves normals

This has probably been asked 100 times but I have searched the forums and not found an answer that works.

I have been following this article on the creation of waves

It works great with world position offset and tesselation except the normals are only accurate for a steepness value of 0. It is probably something I have missed but I am also wondering if the article might be the problem.

Has anybody figured out how to create normals that work with all steepness values?

Works fine for me, you may have bungled the formula a bit.
You can also try using ddx ddy, if you water has no normal for small/large waves it performs quite well with just the formula.

Btw, the math is also within the ocean community project. It’s just well hidden, you have to poke around the material quite a bit. The formula in there might have been slightly altered compared to the article though.
Mostly because they use transparent water and therefore they have to work around the transparency blackface priority issues.

DDX DDY is essentially flat shading, so not suitable, however I am using it to compare to the calculated normals and the difference is quite obvious, and the same result for the community ocean I’m afraid. The normals diverge from correct with increasing steepness.

This is the difference between DDX/DDY and calculated normals at 0 steepness. My implementation is on the left and the community ocean project on the right. (The reason it is not black is because of the faceted shading vs the smooth generated normals, otherwise my implementation would be a perfect result at this setting)

This is with maximum steepness. As you can see a considerable divergence for both.

This is what I’m using to check, I would appreciate it if you could do the same and confirm for me that I’m wrong?

hey @DPAContent1000 you can calculate your normal from pixel position by looking at the neighbors in a very small threshold, you can actually take a look at the nodes: NormalFromHeightmap or NormalFromFunction, this last one you can actually use, a bit messy but it works, they both are using the same technique.

Also you can check this paper where the author is describing step by step how to do that in glsl, Results in Reverse: Calculate Normals In Shader, not only to Gerstner waves you can use the neightbors for every WPO function

While I have used this method many times before with textures, I do not think this would be suitable for the waves as it would require calculating each wave 3 times. This would be wasteful especially once you start stacking the waves. 4 layers of waves would become 12 for example.

This method also suffers from being completely arbitrary and so would be difficult to match the true geometric normal.

I’ll check in a second, but a decent implementation that doesn’t feel too repetitive is at least 2 clusters of 4 waves…

The main thing is, are you layering other normals on top?
I am, so my results would differ anyway (custom textures).
As a fallback you still usually have the small and large wave textures. And on top of it, if tessellation is enabled you have the gerstner math that adds to the displacement/normal.

The separate math for calculating the normals, and adding the normals on top, really does make a visual difference, especially when you set the proper IOR and Specularity along with the scene captures to generate reflections.

Changing textures to real water scans was also really helpful in my case. But getting the shader to be what I wanted took an awful long time - and i’m still not 100% happy.

I am not yet layering other normals on top, my comparisons are just based on plain displaced geometry otherwise they would not be accurate, I wanted to get that bit right before moving on.

I agree getting these normals right is vital, the results are too ugly without them.

how did you implement the function for the rest? You have Direction and Lambda?
If you do the non math UE4 version is

Direction Mask R/ Mask G

Lambda * Amplitude (call it C)

then C * Cosine of function into the Multiply (commutative, pin doesn’t matter) into the 2 masks.

Multiply Sine from the function into the C - this is the Z of the float3

Now you have 2 types of pathways you can subtract or sum.
the 2 multiplies from the mask go into a subtract node with 0 as the first value and the multiply in the input.

Then you make your Float 3, R is X, G is Y, and the sine*c 1-x is Z.

Out of the same pins you can make the summing (green hue)
make the float out of the multiplies as they are (no 0 - and no 1-x)

Most of the stuff is actually walked step by step here
There might be some inaccuracies within that, I ended up re-working the math at least once.

I’ve have tried several, all of them diverge with steepness, that video is the same.

In all my searching on this subjet I have seen no proof that these normals actually work. Perhaps people just implement the functions, assume their normals are correct and never check them. Until somebody shows me that article is correct I am going to assume it isn’t and waste no more time on it.

the GPU gems article? that’s 100% correct since it was published. they’d have redacted it by now if that weren’t the case. As I mentioned, re-work the math yourself at least once. And test on the base wave with no cluster.

Would they though? It’s missing pi symbols and that hasn’t been fixed.

I have reworked the maths numerous times, I’ve tried the tangent and bi-normal method, I’ve taken the position of the summations literally and calculated normals separately, nothing has worked. I have looked at other implementations and they have the same errors, so really I think the article is wrong.

I’d love for somebody to prove me wrong but until then I’m giving up on it. I’ve wasted a couple of days work already.

If you have doubts about correctness, you derive normal formula yourself.

well if you think the article is incorrect you can always ask to ryan brucks: he got it working with that same article. beautifully I might add

It’s true.
I’ve had the same problem for years.
Still haven’t found any solution.