Hi,
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!
↧