Recalculating terrain normals

Started by
14 comments, last by kauna 9 years, 10 months ago

Hi everyone,

I'm trying to get a terrain shader working and I am having problems re-calculating the normals. Here's my shader at the moment:

https://www.dropbox.com/s/ykowd08ga9abzaj/LandscapeShaders.fx

I'm using a technique I discovered in a paper to determine the normal to the terrain using the heightmap data that sets the height of each vertex in a 2D plane. The problem is that the shading is still totally flat and the normals still point directly up, as they do in the original plane. Can anyone see why? Should I be doing the normal calculation in the pixel shader (which I also tried, doesn't work either).

Thanks.

Here's how the terrain looks:

[attachment=21893:tershade.jpg]

Advertisement

Have you tried to make your pixel shader to output the normal instead of the color? This way it is easy to check whether the normals are point at different directions.

Cheers!

[edit] is your f_LightPower bigger than 0 ? Is your v3_LightDir other than 0,0,0 vector? Those 2 could produce a zero result for lighting. Also, is your ambient less than white?

Yeah, I tried outputting the normal and it gives me a totally green colour across the terrain, i.e. (0, 1, 0), just like on the original plane. Yes, I checked the ambients and light power too. I'll have another mess around with it!

Have you tried linear filtering for your terrain sampler?

You are using point filtering and if the texture coordinates sample the same location of the texture (because of point filtering), this will result n.z and n.x of having a zero value.

Cheers!

Have you tried to use the normal without multiplying it by the world matrix? Perhaps your world matrix isn't properly being set, which could lead to issues. Also, why have you chosen to use tex2Dlod for sampling the texture? It should still work fine, but I was just wondering why you would go that way...

I'm by no means an expert. I don't think you need to calculate normals in the shader unless you're terrain is moving every frame, as opposed to the camera moving every frame. Below is the code I use to get the terrain normals when I initialize new terrain, in C#, which seems to work. It's not my code, You could probably find a better algorithm for generating the normals, since it doesn't run every frame speed is not a huge factor.


  private static Vector3 SimpleCrossFilter(int x, int y, ref float[] heightfield,
    float normalStrength, int width, int length)
        {
            // Create four positions around the specified position
            Point[] pos = new Point[]
            {
                    new Point(x - 1, y), // left
                    new Point(x + 1, y), // right
                    new Point(x, y - 1), // higher
                    new Point(x, y + 1), // lower
            };

            // Get the heightvalues at the four positions we just created
            float[] heights = new float[4];
            for (byte i = 0; i < 4; i++)
            {
                // Check if we can access the array with the current coordinates
                if (pos[i].X >= 0 && pos[i].X < width &&
                    pos[i].Y >= 0 && pos[i].Y < length)
                {
                    int j = pos[i].X + pos[i].Y * width;
                    heights[i] = heightfield[j];
                }
                else
                {
                    // If not, then set value to zero.
                    heights[i] = 0;
                }
            }

            // Perform simple cross filter.
            float dx = heights[0] - heights[1];
            float dz = heights[2] - heights[3];
            float hy = 1.0f / normalStrength;

            // Create and normalize the final normal
            Vector3 normal = new Vector3(dx, hy, dz);
            normal.Normalize();

            return normal;
        }


Also, why have you chosen to use tex2Dlod for sampling the texture?

As far as I know, tex2Dlod is the right function for texture sampling in a vertex shader, there are restrictions in which sampling functions are supported.


I don't think you need to calculate normals in the shader unless you're terrain is moving every frame,

You are right that normal recalculation isn't necessary in the shader, but on the other hand, it allows you to use GPU to calculate something that typically you'd need to do on CPU so, this way you'll reduce CPU load / storage load and also you'll need to send less data to the GPU. The normal calculating function presented here was used in certain Battlefields games, so I guess it has some advantage too. Of course, you'll need to have the height map available for the normal calculations.

Cheers!

You are right that normal recalculation isn't necessary in the shader, but on the other hand, it allows you to use GPU to calculate something that typically you'd need to do on CPU so, this way you'll reduce CPU load / storage load and also you'll need to send less data to the GPU. The normal calculating function presented here was used in certain Battlefields games, so I guess it has some advantage too. Of course, you'll need to have the height map available for the normal calculations.

But you would make it only once on the CPU, wouldn't you? As opposed to every frame on GPU. (If the terrain is not changing somehow during time.)

You need to send less data to the GPU, that's right. But that would probably require some profiling, whether it's better to be sending less data and calculating some on the GPU or to be sending more data and spare some GPU time.

Well, as I told, the method has been used in a AAA level game, so I guess that they did the profiling. Of course hardware changes and everything changes, so you can always redo the profiling.

With CPU calculated normals, you'll still do texture read in the shader and probably some unpacking depending on the texture format. With the GPU version you'll do 4 texture reads, but probably they won't be much slower. The amount of ALUs in the GPU version is so small that it won't change much.

So, probably calculating the normals on the GPU won't be any slower than the CPU version.

Cheers!

Another option is to store the pre-calculated normals in the heightmap.

This topic is closed to new replies.

Advertisement