GPU Normal Maps Artifacts on a Quadtree Terrain

Started by
7 comments, last by pbobzebuilder 10 years, 8 months ago

Hi,

I'm actually working on terrain rendering based on quadtree & GeoMip Terrain.

I've implemented normal maps generation by the GPU and everythings works fine except I'm getting artifacts near the ground.

I Get lines (cf screenshot) after the normal calculation wich is certainly a precision issue.

Heightmaps are 256^2, and shows no artifacts.

Normal Maps are 256^2 and artifacts shows at ground level (Quadtree depth is 16 and above.)

Both are R32G32B32A32_FLOAT textures.

If anyone has an idea or experienced those kind of artifacts, I'll be grateful tongue.png

Here is my normalMap HLSL code (with scale coming from CPU : f32 scale = 1<<pTreeNode->GetDepth(); );

float2 uv = input.tex0;
float3 sphere = heightMap.Sample(heightMapSamplerState, uv).xyz;
float hpix = PixelSize;
float normalStrength = 0.5;
float l = heightMap.Sample(heightMapSamplerState, uv + float2( -hpix, 0)).w;
float r = heightMap.Sample(heightMapSamplerState, uv + float2( hpix, 0)).w;
float t = heightMap.Sample(heightMapSamplerState, uv + float2( 0, hpix)).w;
float b = heightMap.Sample(heightMapSamplerState, uv + float2( 0, -hpix)).w;
float tanZ = (t-b);
float tanX = (l-r);
float tanY = 2.0 / (scale*normalStrength);
float3 normal = normalize(float3(tanX, tanY, tanZ)) * 0.5 + 0.5;
float ny = normal.y;
float3x3 mat = BuildRotationMatrix(sphere, float3(0.0f, 1.0f, 0.0f));
normal = mul(normal.xzy, mat); //FaceMatrix
return float4(normal, ny);
Advertisement

I think it's definitely a precision problem from the GPU to deal with too small values from the HeightMap.

If anyone have a clue for me smile.png

I would definitely try adding 0.5*PixelSize to your UV coordinates - sampling at texel edges tends to be problematic. I'd also play around with the size of hpix, at least halving and doubling are usually good candidates to fix sampling artifacts.

Just checked my UVs but those are correct.

I keep digging the Normal Calculation, but I think I was wrong and my real problem is my noise function reaching the limits of float.

Question is: How to generate good looking NormalMaps from HeightMaps generated on the GPU at ground level.

Been trying all the day to get around the problem without any success.

I think I'll bypass the precision issue by setting the maximum quadtree-depth of the normal map to 14.

I'll be happy to discuss with anyone having similar problems.

This is definitely a precision issue and likely has to do with this:

Here is my normalMap HLSL code (with scale coming from CPU : f32 scale = 1<<pTreeNode->GetDepth(); );

Why would your normals depend on the depth or a particular quad tree node? This is strange and suggest some type of dependence between the data in the height map and tree depth. You algorithm is a bit strange as well and seems to be doing things in a round about way. Generally normals are derived from a height map like so:

http://archive.gamedev.net/archive/reference/programming/features/normalheightfield/page3.html

My problem comes much from where the heightmap comes from. I create an heightmap for each leaf of my quadtree. As I go deeper in the quadtree subdivision, I continuously create heightmap based on miscellanous noise functions. At a given depth (about 14 subdivs) the input coordinates for the noise are too small for a 32 bits float (I'm doing it with the GPU). This precision issue leads to really bad artifacts in the normal map.

Nothing strange about the way normals are computed. There's multiple ways to compute normals. Here I choose a fast and simple shortcut. I Could have used something like sobel filter but it takes more computations and more sampling from the heightmap. Instead, I only do the difference between the top and above Heights for one axis and the difference between left and right Heights for another axis. Given a Y axis = 2.0 for a quadtree depth of 0 I'll have Y=1 for Depth=1; Y=0.5 for Depth=2, Y=0.25 for Depth=3 and so on... (here is why the 1<<pTreeNode->GetDepth() is for.)

Nothing strange about the way normals are computed. There's multiple ways to compute normals. Here I choose a fast and simple shortcut. I Could have used something like sobel filter but it takes more computations and more sampling from the heightmap. Instead, I only do the difference between the top and above Heights for one axis and the difference between left and right Heights for another axis. Given a Y axis = 2.0 for a quadtree depth of 0 I'll have Y=1 for Depth=1; Y=0.5 for Depth=2, Y=0.25 for Depth=3 and so on... (here is why the 1<<pTreeNode->GetDepth() is for.)

It's the rotation by a matrix that appears to be unique to each height map entry that's strange as well as the scale and bias that gets applied to the normal before the matrix multiplication. There is also the issue of 'normalStrength.' I understand that this is mostly a convenience thing but it opens the door for normals that are not truly perpendicular to the surface.

All that aside you are definitely pushing the precision boundaries of 32 bit floats. You could precompute:


float tanY = 2.0 / (scale*normalStrength);

with doubles and store the results in a table of 32 bit values and pass that to your shader. This will not solve the problem but it will buy you a few more subdivisions. Aside from that your only choice is to work in a different system of units or inject various scale factors and rearrange the order of computations to better make use of the range of precision available to 32 bit floats.

1 / (1 << 14) = 0.00006103515

That's a very small value. If your units are kilometers then you are resolving normals from a surface at a scale of 6 centimeters at 14 subdivisions; at 16 subdivisions you are at a scale of 1.5 centimeters. Maybe this is necessary but it seems extreme to me for terrain.

Exploiting floating point precision can be tricky especially on the GPU where your shader code can get optimized in a way that harms your precision. A lot of older GPUs are not truly IEEE compliant which can make matters worse. Looks around the web for how values are encoded in floats, this will give you a better idea of how to tweak computations to get the best results.

http://en.wikipedia.org/wiki/Floating_point

The section titled "Floating-point arithmetic operations" is particularly useful.

Thanks to take the time helping me :)

I haven't tried to precompute values by the CPU but it could reduce the artifacts if precomputed using doubles.... I'll give it a try :)

My terrain is actually a 1:1 scale planet. My normals are computed in object space which explain the matrix (Used to transform my normals from planar heightmap to sphere).

Coordinates passed to my noise function are between -1, 1 at quadtree-depth = 0. May be I could try another scale to limit f32 precision issues.

This topic is closed to new replies.

Advertisement