Terrain texture projection artifacts

I’m going to talk about some long-standing artifacts I’ve had in my terrain shader, and maybe solicit ideas on how to fix them. First, I’ll briefly explain how it is implemented.


My terrain is based on a height map, and I render it in 32 x 32 chunks.

Below is an example of the rendered terrain and the tessellated grid, where one quad corresponds to one texel in the heightmap.

You’ll note that I swap the orientation of the quads in some areas. I do this depending on the height difference of the four vertices that compose the quad. This really helps with removing grid-like artifacts around steep slopes, and let’s me achieve a pretty good look with low resolution. I’d like to keep this.

One disadvantage is that it means I have to prepare a custom index buffer at runtime as I load the chunks. There is a definite CPU overhead here (especially on the Xbox), and I’d like the keep that to a minimum.

Normals are currently calculated offline and put into the vertices at runtime (I could also calculate them in the vertex shader by sampling from 4 spots in the heightmap).

Terrain viewspace normals (w/o normal maps and with normal maps)

To avoid stretching on steep slopes, the terrain is textured using textures that are projected on the x, y and z axes. The vertices don’t contain texture coordinates – I calculate them from the vertices’ positions in the vertex shader.

In the pixel shader I calculate the appropriate blend weights for each of the x, y and z-axis texturing based on the interpolated normal. There are short “fade” zones between each of the axes. The HLSL looks like this:

float3 CalculateXYZBlendWeights(float3 normal)
 // Determine the blend weights for the 3 planar projections
 float xzNormal = length(normal.xz);
 float2 yVSxzBlendWeights = normalize(abs(float2(normal.y, xzNormal))); // y vs other
 float2 xzBlendWeights = abs(normalize(normal.xz)); // other vs each other
 // Tighten up the blending zones
 xzBlendWeights = (xzBlendWeights - 0.55) * 11;
 xzBlendWeights = normalize(max(xzBlendWeights, 0));
 yVSxzBlendWeights = (yVSxzBlendWeights - 0.65) * 13; // Much tighter
 yVSxzBlendWeights = normalize(max(yVSxzBlendWeights, 0));
 float3 blendWeights = float3(xzBlendWeights.x, 1, xzBlendWeights.y) * yVSxzBlendWeights.grg;
 // Force weights to sum to 1.0 (very important)
 blendWeights /= dot(blendWeights, 1);
 return blendWeights;

The problem

The texture projection system is designed to prevent texture stretching. What we want to avoid is to have a normal that indicates, for instance, that we should have the x-axis texture, while at the same time having no difference in the x or z world coordinates for triangle.  Unfortunately, due to the way the normals are calculated, this is sometimes exactly what we get. A diagram is probably the best way to explain this.



It looks like this:



The solution

What are the possible solutions?

I could increase the tessellation of the terrain, but this has many drawbacks. I would quadruple the size of my vertex buffers and the CPU workload involved in preparing them. I would no longer be able to calculate normals offline (unless I want to quadruple the size of my normal data). Also, this doesn’t eliminate the problem, it just improves it incrementally. I could go all out and try to detect the sharp edges where this occurs, and add a bunch more geometry. This doesn’t seem trivial at all though.

I could run some sort of smoothing pass over the terrain that moves vertices up and down so that there is some texture coordinate resolution for any axis which might be used for projection. That would eliminate some high-frequency details from my terrain though – and it doesn’t seem like a trivial algorithm to implement.

Alternately, I could somehow be more intelligent over how the normals are interpolated between vertices, passing some extra information from the vertex shader. I haven’t been able to figure out anything reasonable here though.

I think the smoothing pass is most promising way so far since it wouldn’t involve changing any of my runtime code.

Any other thoughts?


6 comments on “Terrain texture projection artifacts

  1. I think either smoothing or increasing resolution is the way to go.
    Your problem is on example of trying to pack too high-resolution data onto low-resolution container. In your case elevation data into vertex grid. And these things tend to cause artifacts that are hard to remove automatically.
    Two more ideas:
    1. Make your fade zones longer. As blending textures together makes them blurry you may try to add slight contrast enhancement in these zones.
    2. Use higher-resolution grid. Scrap the quad reorientation (or implement in geometry shader) – it is less useful for higher resolution grid. Also it should be possible to use single flat vertex buffer for all your terrain chunks and do all transformations in vertex shader with vertex texture, thus eliminating the bottleneck of preprocessing large FBO-s.

  2. I think the smoothing pass would definitely be the best bet. Just look for the sharp angles and tweak them as necessary. I can’t immediately see any other solution being robust enough to be worth the extra development time.

    But then again, I’m probably not qualified to give advice here.

  3. As always a very well explained post 🙂 I echo Lauris’ (2) A higher resolution grid, and scrapping the quad reorientation. By averaging four samples you can have your grid at twice the resolution of your heightmap, as you say, and the memory costs for the grid will be O(1) as opposed to the chunks O(n) I think?

  4. Hmm… I’ll try the double resolution grid. But in that case, how are you calculating your normals?

  5. […] last post on this is here, and explains how I’ve been trying to remove the texture projection artifacts I have due to […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Space Quest Historian

Adventure game blogs, Let's Plays, live streams, and more

Harebrained Schemes

Developer's blog for IceFall Games

kosmonaut's blog

3d GFX and more


Turn up the rez!

bitsquid: development blog

Developer's blog for IceFall Games

Game Development by Sean

Developer's blog for IceFall Games

Lost Garden

Developer's blog for IceFall Games


Developer's blog for IceFall Games

Casey's Blog

Developer's blog for IceFall Games


Developer's blog for IceFall Games

Rendering Evolution

Developer's blog for IceFall Games

Simon schreibt.

Developer's blog for IceFall Games

Dev & Techno-phage

Do Computers Dream of Electric Developper?

- Woolfe -

Developer's blog for IceFall Games

Ferrara Fabio

Game & Application Developer, 3D Animator, Composer.

Clone of Duty: Stonehenge

First Person Shooter coming soon to the XBOX 360

Low Tide Productions

Games and other artsy stuff...


Just another WordPress.com site

Sipty's Writing

Take a look inside the mind of a game developer.

Jonas Kyratzes

Writer, game designer, filmmaker.

%d bloggers like this: