Jump to content
  • Advertisement

DX11 Ocean rendering - FFT Heightmap - Finite diff normals are distorted

This topic is 376 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


As part of my terrain project, I'm trying to render ocean water. I have a nice FFT Compute shader implementation which outputs a nice 512x512 heightmap (It can also output a Gradient map but I disabled it as there are issues with it). The FFT code is from the Nvidia FFT ocean sample for DX11.

Now, here is the weird thing, I have 2 different methods that render the water grid, both using the same FFT Heightmap SRV (SRVs are members of a dedicated Resource Manager class), and both are rendering the FFT Heightmap same way exactly. Although the grids are different, eventually I made the FFT map tile in a way where the scales are almost 1:1. The rendering itself is pretty much straight forward (Using DX11 Tessellation pipeline):

1. In domain shader - Sample the Heightmap in order to displace the vertices

2. In pixel shader - Finite diff to get the normals - Sample the heightmap 4 times and calculate the normals as usual

Now here is the weird thing:

Method 1 - Normals look good after Finite diff operation - Unfortunately I can't use this method as it has some other issues.

Method 2 - Normals are coming out distorted in a way that I can't explain - More than that, if in the Domain shader I give up the displacement on the horizontal axis (XZ) and leave only the vertical displacement on Y axis, the normals are fine. With full displacement (XZ included) it feels like the normals aren't compensating for the XZ movement of the displacement.

I tried to play with anything I could think of, but normals look bad no matter what. And I really don't want to give up the XZ displacement as with vertical displacement only, the FFT looks kinda crippled. I tried also to use ddx_fine and ddy_fine, and it seems like the normals looking more accurate (i.e taking the XZ movement into account), but the quality was very low, so not usable. But the fact that the natural derivatives functions showed the XZ movement more accurately does give me hope that there is a better way to do it (?)

So, Is there a better way to calculate the normals more accurately?

Here is the difference:

Method 1 normals - Nice and crispy


Method 2 normals - Distorted


Also here is the Method 2 displacement in wireframe, and it's looking good as can be seen:


I'm also attaching here the relevant DS and PS code that makes the displacement and normals in method 2 (Method 1 code is same, just has some more stuff like Perlin noise blended in the distance, but the FFT related stuff is same exactly):

DS displacement code

	// bilerp the position
float3 worldPos = Bilerp(terrainQuad[0].vPosition, terrainQuad[1].vPosition, terrainQuad[2].vPosition, terrainQuad[3].vPosition, UV);
	float3 displacement = 0;
	displacement = SampleHeightForVS(gFFTHeightMap, Sampler16Aniso, worldPos.xz);
displacement.z *= -1; // Flip Z back because the tex coordinates use a flipped Z, if not flipping the FFT look kinda upside down
worldPos += displacement * FFT_DS_SCALE_FACTOR;
	return worldPos;

PS finite diff:

	float3 CalcNormalForOceanHeightMap(float2 uv)
    float2 one_texel = float2(1.0f / 512.0f, 1.0f / 512.0f);
	    float2 leftTex;
    float2 rightTex;
    float2 bottomTex;
    float2 topTex;
    float leftY;
    float rightY;
    float bottomY;
    float topY;
    float normFactor = 1.0 / 512.0;
	    leftTex   = uv + float2(-one_texel.x, 0.0f);
    rightTex  = uv + float2(one_texel.x, 0.0f);
    bottomTex = uv + float2(0.0f, one_texel.y);
    topTex    = uv + float2(0.0f, -one_texel.y);
	    leftY   = gFFTHeightMap.SampleLevel(Sampler16Aniso, leftTex, 0 ).z * normFactor;
    rightY  = gFFTHeightMap.SampleLevel(Sampler16Aniso, rightTex, 0 ).z * normFactor;
    bottomY = gFFTHeightMap.SampleLevel(Sampler16Aniso, bottomTex, 0 ).z * normFactor;
    topY    = gFFTHeightMap.SampleLevel(Sampler16Aniso, topTex, 0 ).z * normFactor;
	    float3 normal;
    normal.x = (leftY - rightY);
    normal.z = (bottomY - topY);
    normal.y = 1.0f / 64.0;
	    return normalize(normal);

Any help would be welcome, thanx!



Edited by yonisi

Share this post

Link to post
Share on other sites

Issue resolved.

My error was the order that I took the worldPos.xz which is used to fetch the texcoords. The original order was:

	Bilerp worldPos
	Displace worldPos by FFT
	Assign (the already displaced) worldPos.xz to the DS output.worldPos.xz (Which is used as texcoord in the PS)

The correct order is:

	Bilerp worldPos
	Assign (BEFORE displacement) worldPos.xz to the DS output.worldPos.xz (Which is used as texcoord in the PS)
	Displace worldPos by FFT

Almost drove myself crazy around this :)

Edited by yonisi

Share this post

Link to post
Share on other sites

  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!