• Advertisement
Sign in to follow this  

Normal artifacting exposes underlying topology

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

I have a heightmap for which I've computed normals. When I simply output normals as color, I get this:

3wk4AXW.png

 

So basically the interpolation is not smooth and is showing up with artifacts that reflect the underlying topology. I don't understand why I'm getting this or what to do about it. Normalizing per fragment makes no difference. Is the actual normal computation wrong and showing up as this artifact, or is there something else going on here?

Share this post


Link to post
Share on other sites
Advertisement

That's actually expected, due to the way it's triangulated - there's probably nothing wrong with your calculations. If you generate your triangles in the opposite orientation, you'll get artifacts facing the other direction. These artifacts will be less noticeable the smaller you make your triangles, or the more textured you make your terrain.

 

You can also reduce it quite a bit if you just orient each triangle depending on the direction the slope faces (I suspect you'll notice that on slopes that are rotated 90 degrees from the one you pictured above, the artifact is not so noticeable). If the edges that splits each quad into two triangles is aligned so that it goes between the two vertices with the least height difference between them, then things will look better.

 

I explain this a bit here, under "Additional optimizations":

http://mtnphil.wordpress.com/2011/09/22/terrain-engine/

 

You could probably also get something that looks better if you instead sampled the terrain normals in the pixel shader instead of the vertex shader, since then you'd get values interpolated between 4 normals instead of 3.

Edited by phil_t

Share this post


Link to post
Share on other sites

What you are seeing is normal, no pun intended.  I'm surprised that you are not seeing at least some improvement by normalizing the normals in the fragment processor. 

 

You can either increase the detail of the model or you can post your shader code.   There are several people around who can help you configure your shaders to perform better, both visually and computationally.

 

Also, normal mapping will resolve this issue.  For dynamic height mapping you'd have to use a tangent space normal map. (Unless someone came up with something clever that I'm not aware of).  Most people only use tangent maps anyways.

 

There is an example in the cgToolkit from nVidia that shows how to use a normalization cube map to align the tangent space normals to your models' geometry.

https://developer.nvidia.com/cg-toolkit

 

In the installed directory look for:  /cg/examples/OpenGL/basic/23_bump_map_floor.  It will easily port to HLSL or GLSL with no issues.

Share this post


Link to post
Share on other sites


Also, normal mapping will resolve this issue

 

How so? If that were true you could just simulate a normal map where everything is (0, 0, 1) and be done with it :-)

Share this post


Link to post
Share on other sites

Dang. I was really hoping you guys wouldn't say that. I've seen the artifact before and vaguely remembered hearing it was expected, but Friday afternoon was not kind to me. I guess I'll hope that normal maps add enough entropy to cover for it.


You could probably also get something that looks better if you instead sampled the terrain normals in the pixel shader instead of the vertex shader, since then you'd get values interpolated between 4 normals instead of 3.

Hmm. Suppose instead of running the normals through the geometry, I uploaded to a texture and then sampled them per fragment? That would create a bilinear interpolation across all four corners that's independent of tessellation and might just get me out of trouble. Edited by Promit

Share this post


Link to post
Share on other sites

Like others have said, this is to be expected. If you find that it's a real problem in practice, though, you might be able to work avoid the "problem" by interpolating your normals non-linearly.

 


I'm surprised that you are not seeing at least some improvement by normalizing the normals in the fragment processor.

 

I disagree.

 

Here's (I hope) a useful analogy: imagine that, instead of "topology," you are working with only a low-resolution normal map, where each pixel corresponds to a "vertex." The old, fixed-function Gouraud shading is analogous to computing the lighting at the resolution of the original normal map, then scaling the whole image up to the target size with linear interpolation. Per-pixel (Phong) shading would involve scaling the normal map to the target size and then computing the lighting.

 

Note that if all we're doing is rendering the normal map (ignoring lighting), these two processes won't do anything different, so there's no advantage to doing it "per-pixel". The only way you'll get a result that doesn't exhibit the "topology" (which is really just like the pixelation of a scaled up image) is to use an interpolation algorithm that doesn't exhibit artefacts that you find unpleasant.

Ultimately, you're still interpolating, generating data where there is none; you just have to find a way to generate data that you like.

Share this post


Link to post
Share on other sites

interpolating per vertex colors across triangles is actually called gouraud shading and if you look it up on google images, you will find that it's the way it is. The reason is that you interpolate linearly what isn't linear. at the center of a quadratic surface, you actually take the average of just two corners. The solution back then was to interpolate linearly what really is linear (e.g. normal, light vector, eye vector) normalize those and then per pixel do the shading (phong) and that looks actually correct (not Physically based, but had no topology artifacts).

Share this post


Link to post
Share on other sites
How so? If that were true you could just simulate a normal map where everything is (0, 0, 1) and be done with it :-)

 

I suppose you could do this, instead of loading 1 out of those 2 normal maps that are used in that example, personally I think this idea of yours has merit, this way you can save on texture bandwidth.  But this is besides the point, whether you use a straight blue texture or just define all the unprocessed normals in the fragment processor as all (0,0,1).  is irrelevant to what the shader is ultimately doing.  

The nVidia sample is using a normalization cube map, it's this map that will be helping to adjust the light for the contours of the model.  This cube map is a world space normal map, it's not (0, 0, 1) like the tangent map.  But again, you are absolutely right, the tangent map is useless if there is no intention to add extra detail between the vertices of a low-poly model.  It's just that this code example is set up this way.  Your idea will optimize that shader for cases like this where the bump detail is not needed.  

Share this post


Link to post
Share on other sites
Here's (I hope) a useful analogy: imagine that, instead of "topology," you are working with only a low-resolution normal map, where each pixel corresponds to a "vertex." The old, fixed-function Gouraud shading is analogous to computing the lighting at the resolution of the original normal map, then scaling the whole image up to the target size with linear interpolation. Per-pixel (Phong) shading would involve scaling the normal map to the target size and then computing the lighting.

 

I'm not surprised that you disagree.  If a person were to shrink a normal map down to almost nothing and then resize it back up again, that would introduce such unsightly artifacts as to make the texture completely hideous and unuseable.  I would never consider doing this. There would be no point to using them at all.

 

Why in the world would you consider scaling down a normal map only to scale it back up with linear interpolation, or any interpolation for that matter?  This made me think of an analogy:  You spent the whole weekend polishing your car only to finish it up by slinging mud at the car and rubbing the mud into the paint and gouging it all up.

 

The proper way to generate a normal map is to estimate roughly how many pixels the model will take up on the screen, and then set your 3D modeling program to generate the normal map at whatever value will fill up that much screen space, I would say, to be safe, generate one power-of-2 size bigger just to be safe.  Only shrink it down when you're sure it's ready to go.  Don't try to save space now and then try to up-size it later.  If you end up in this situation then use the 3D modeling software to re-generate the larger size.

 

If you want to compress your normal maps then make sure that you are using a loss-less format or you will damage it.  Visually, it will literally look like you took sandpaper to your finely polished model.

Normals that are compressed to color space in an RGB texture have already been damaged by this process to begin with.  Only floating point textures can fix this completely. 

 

Personally I cringe at the idea of using a normal map that is smaller than the screen area that the model takes up.  They can be mipmapped fine without much corruption if you get the filter settings right.  They cannot be sampled up in size without seriously damaging them.  Unless they are blurred of course.  Low frequency noise can be scaled up without too much apparent aliasing creeping in. 

Share this post


Link to post
Share on other sites

removed: didn't seem to be appreciated...  and I obviously did not read the original post properly

Edited by marcClintDion

Share this post


Link to post
Share on other sites


Hmm. Suppose instead of running the normals through the geometry, I uploaded to a texture and then sampled them per fragment? That would create a bilinear interpolation across all four corners that's independent of tessellation and might just get me out of trouble.


Yeah, this works quite well. It has an advantage that you no longer need to have vertex normals / tangents / binormals passed through the vertex pipe reducing bandwidth in the vertex shader too. The texture coordinate can be derived from the position, and as you pointed out - its effectively bi-linear filtered across the quad, as opposed to the interpolation that happens across the triangle.

 

Another alternative, which i have tried before is to triangulate the quads based on their slope direction. Always cut the quad across the diagonal that's closest to being vertically flat.

Share this post


Link to post
Share on other sites

 

 No, I'm wrong, I should have put that more tactfully.  

 

You wrote that you tried normalizing in the fragment shader and that this had no effect.

 

Could it be that you only normalized one of the two terms that are used in the diffuse lighting calculation.  As follows, 

 

//=====================================================================================================

  float3 N = normalize(normal);                   // It seems to me that if you do not normalize both of these in the fragment shader 
  float3 L = normalize(lightPosition - P);     // then you will not see any benefit,  it will still look like the image you posted.
 
  float diffuseLight = max(dot(L, N), 0.0);
//=====================================================================================================

 

 

The image that Promit posted represents, in his own words, the normals as colors. There are no lighting calculations being performed at all. Thus, the result will represent linear interpolation whether it is computed in the pixel shader, in the vertex shader, or in the fixed-function pipeline (unless specific steps are taken to use another type of interpolation).
 

That's why everyone is saying that the image represents the expected result.

Share this post


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

  • Advertisement