0

I'm implementing a system to generate terrains using perlin noise. This is how I generate the vertices:

int arrayIdx = 0;

    for(float x = offset.x - CHUNK_WIDTH / 2.0f; x < float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f; x += TRIANGLE_WIDTH) {
        for(float y = offset.y - CHUNK_WIDTH / 2.0f; y < float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f; y += TRIANGLE_WIDTH) {
            float height0 = noise->octaveNoise(x + 0.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH),
            height1 = noise->octaveNoise(x + 1.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH),
            height2 = noise->octaveNoise(x + 0.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH),
            height3 = noise->octaveNoise(x + 1.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);

            mapVertices[arrayIdx + 0] = glm::vec3(x + 0.0f * TRIANGLE_WIDTH, height0, y + 0.0f * TRIANGLE_WIDTH);
            mapVertices[arrayIdx + 1] = glm::vec3(x + 1.0f * TRIANGLE_WIDTH, height1, y + 0.0f * TRIANGLE_WIDTH);
            mapVertices[arrayIdx + 2] = glm::vec3(x + 0.0f * TRIANGLE_WIDTH, height2, y + 1.0f * TRIANGLE_WIDTH);
            mapVertices[arrayIdx + 3] = glm::vec3(x + 1.0f * TRIANGLE_WIDTH, height3, y + 1.0f * TRIANGLE_WIDTH);
            mapVertices[arrayIdx + 4] = glm::vec3(x + 1.0f * TRIANGLE_WIDTH, height1, y + 0.0f * TRIANGLE_WIDTH);
            mapVertices[arrayIdx + 5] = glm::vec3(x + 0.0f * TRIANGLE_WIDTH, height2, y + 1.0f * TRIANGLE_WIDTH);

            mapUVs[arrayIdx + 0] = glm::vec2(x + 0.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH);
            mapUVs[arrayIdx + 1] = glm::vec2(x + 1.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH);
            mapUVs[arrayIdx + 2] = glm::vec2(x + 0.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);
            mapUVs[arrayIdx + 3] = glm::vec2(x + 1.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);
            mapUVs[arrayIdx + 4] = glm::vec2(x + 1.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH);
            mapUVs[arrayIdx + 5] = glm::vec2(x + 0.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);



            glm::vec3 normal0 = -1.0f * glm::triangleNormal(mapVertices[arrayIdx + 0], mapVertices[arrayIdx + 1], mapVertices[arrayIdx + 2]),
            normal1 = +1.0f * glm::triangleNormal(mapVertices[arrayIdx + 3], mapVertices[arrayIdx + 4], mapVertices[arrayIdx + 5]);

            mapNormals[arrayIdx + 0] = normal0;
            mapNormals[arrayIdx + 1] = (normal0 + normal1) / 2.0f;
            mapNormals[arrayIdx + 2] = (normal0 + normal1) / 2.0f;
            mapNormals[arrayIdx + 3] = normal1;
            mapNormals[arrayIdx + 4] = (normal0 + normal1) / 2.0f;
            mapNormals[arrayIdx + 5] = (normal0 + normal1) / 2.0f;


            arrayIdx += 6;
        }
    }

Not using lighting produces these quite smooth results,

enter image description here

The only thing left to do is to generate normals for the triangles, that will make the terrain look smooth. Just using glm::triangleNormal yields this result,

enter image description here

As you can see, lighting really destroys the illusion of a smooth surface.

I tried using an average value of normals on the colliding vertices of the triangles like this:

arrayIdx = 0;

    for(float x = offset.x - CHUNK_WIDTH / 2.0f; x < float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f; x += TRIANGLE_WIDTH) {
        for(float y = offset.y - CHUNK_WIDTH / 2.0f; y < float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f; y += TRIANGLE_WIDTH) {

            if((x == offset.x - CHUNK_WIDTH / 2.0f && y == offset.y - CHUNK_WIDTH / 2.0f) ||
               (x == float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH && y == offset.y - CHUNK_WIDTH / 2.0f) ||
               (x == offset.x - CHUNK_WIDTH / 2.0f && y ==  float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH) ||
               (x == float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH && y ==  float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH)) {
                //Special case
            }
            else if(x ==  float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH ||
                    y ==  float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH) {
                 //Special case
            }
            else {
                glm::vec3 averageNormals = (mapNormals[arrayIdx + 3 + 0] + //This triangle
                                            mapNormals[arrayIdx + 0 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6 + 6] + //Triangle after and this one
                                            mapNormals[arrayIdx + 2 + 6] + //Triangle in the right
                                            mapNormals[arrayIdx + 5 + 6] + //Triangle in the right
                                            mapNormals[arrayIdx + 1 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6] + //Triangle after this one
                                            mapNormals[arrayIdx + 4 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6])  //Triangle after this one
                                            / 6.0f;

                mapNormals[arrayIdx + 3 + 0] = averageNormals;
                mapNormals[arrayIdx + 2 + 6] = mapNormals[arrayIdx + 3 + 0];
                mapNormals[arrayIdx + 5 + 6] = mapNormals[arrayIdx + 3 + 0];
                mapNormals[arrayIdx + 1 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6] = mapNormals[arrayIdx + 3 + 0];
                mapNormals[arrayIdx + 4 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6] = mapNormals[arrayIdx + 3 + 0];
                mapNormals[arrayIdx + 0 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6 + 6] = mapNormals[arrayIdx + 3 + 0];
            }

            arrayIdx += 6;
        }
    }

which yielded this result,

enter image description here

but this doesn't look much better.

Using the normals as fragment color give this result:

enter image description here

Rendering the normals as lines yields this, this is before the optimization and with larger triangles, to reduce the number of lines:

enter image description here

This is with my optimization:

enter image description here

Somehow, two normals dont get set.

The blue lines here are the average normals, the green lines are the individual normals before optimizing, they look good:

enter image description here

This is with wireframe:

enter image description here

Maybe some normals aren't set to the average value?

How can I generate normals that are smooth?

user11914177
  • 885
  • 11
  • 33
  • 1
    Without the information on how you try `using an average value of normals on the colliding vertices` and how the shader looks like it is hard to tell what you do wrong. To get smooth normals the vertices that have the same position should have the same normal and then you shouldn't see hard borders. Are you sure the problem is the normals and not the texture that is not seamless? And for debugging it is often helpful using the value of the normals to color the triangles. – t.niese Feb 15 '20 at 17:44
  • You have some problem with the texture or the uv as well. Please remove the texture, so we can see the lighting better. – geza Feb 15 '20 at 18:27
  • @geza why do you think that I have a problem with the texture? – user11914177 Feb 15 '20 at 18:32
  • @t.niese I added more information. It can't be the texture not tiling, because the errors also appear on non texture borders. – user11914177 Feb 15 '20 at 18:36
  • Because there are seams. I don't really like this: `int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH))`. At least you should round here before converting to `int`. Or remove the floating point math altogether. You have some problems with your indices, because you have differing normals at vertices which are at the same position. – geza Feb 15 '20 at 19:03
  • I may have missed something obvious but, are the normals you average all normalized (or at least all the same length)? If not then the result will be weighted. – G.M. Feb 15 '20 at 19:44
  • @geza i don't really get what your problem is, the UVs are working fine, I tested it with a debug texture and it is working... – user11914177 Feb 15 '20 at 21:49
  • @G.M. this sadly doesn't seem to be the problem – user11914177 Feb 15 '20 at 21:54
  • see [How to achieve smooth tangent space normals?](https://stackoverflow.com/a/21930058/2521214) – Spektre Feb 16 '20 at 08:35
  • "Not using lighting produces these quite smooth results" I see seems very clearly due to the seems in the texture mapping already. I think you have to fix much more than just the normals to make this look smooth. – derhass Feb 16 '20 at 16:14
  • @derhass The texture is just a regular photo of a rock, of course there are seams! That's not even what I asked for... – user11914177 Feb 16 '20 at 16:17
  • I find it really hard to judge the smnoothness of the lighting when there are hard seems induced by the texture. These picutres would have made much more sense with a uniformly colored model. – derhass Feb 16 '20 at 16:23
  • @derhass Well, I already have a class for spheres with smooth normals. Using the exact same texture, I get a very smooth result... – user11914177 Feb 16 '20 at 16:59

2 Answers2

3

The way to compute the per-vertex normal is as follows: You have to consider each polygon that the vertex also belongs to, for instance:

Per-vertex normal computation

As you mentioned, the problem seems to be related to lighting. Having the proper per-vertex normal computed, you should be also using the correct shading technique, which in this case would be Phong or Gouraud shading instead of Flat shading, as it appears you are using.

André Caceres
  • 719
  • 4
  • 15
  • What is m in your formula? I basically thought of that method and tried to implement it (that is what you see above). I also am using OpenGL 3.3 with an own implementation of Phong lighting – user11914177 Feb 15 '20 at 21:56
  • *m* is the number of connected polygons. – André Caceres Feb 16 '20 at 00:16
  • This is exactly what I try to do, where is the problem in my code? – user11914177 Feb 16 '20 at 08:28
  • @user11914177 render your normals you try to average ... my bet is that at least one of them is incorrect (opposite direction or wrong size) – Spektre Feb 16 '20 at 08:36
  • @Spektre how should I render them? I already used them as fragment color (as seen above) – user11914177 Feb 16 '20 at 09:17
  • @Spektre I just printed them out, this is what it looks like: `-------------------------------------------- X = 0.121165 Y = 0.990511 Z = 0.064865 X = 0.189163 Y = 0.981821 Z = -0.015689 X = 0.187401 Y = 0.978661 Z = -0.084282 X = 0.187401 Y = 0.978661 Z = -0.084282 X = 0.196989 Y = 0.978824 Z = 0.055660 X = 0.196989 Y = 0.978824 Z = 0.055660 ----------------------------- X = 0.179851 Y = 0.981217 Z = -0.001345 --------------------------------------------` The first 6 are the calculated normals, the last one is the average. – user11914177 Feb 16 '20 at 09:21
  • @user11914177 you need to render them as vectors ... colors and prints are not good for quick visual validation see this https://i.stack.imgur.com/7qpxv.png you just render line from the `vertex` to `vertex + visual_size*normal` then you will quickly see (especially on wireframe) what is wrong ... – Spektre Feb 16 '20 at 11:10
3

I can see the images I requested...

Now look below the terrain if there are not some normals pointing downwords. If yes it means wrong order of multiplication in cross or not the same winding in the terrain geometry.

Then check if your normals are normalized (the same size) but from a quick look it look OK to me.

If you render your averaged normal (with different color) it should be enclosed by the other normals (in the middle of them)

Anyway this does not look right to me:

img

looks like one of the normal is going in reflected direction. Are you sure you are computing the normal from correct vertexes ?

Your geometry looks like triangulated QUAD grid so you just may be use wrong diagonal ...

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • I don't see any normals going down, I already multiply one normal with -1.0f to invert it. The normals on the edge of the map aren't calculated very good currently. I uploaded some new pictured with the average normals and a wireframe to my question. – user11914177 Feb 16 '20 at 14:29
  • @user11914177 well the only thing I can think of is you either assign computed or averaged normal to incorrect vertex or not using per vertex normal/lighting. My bet is the first option ... – Spektre Feb 17 '20 at 08:18
  • 1
    That exactly was the problem, I mixed up the x and z axis and therefore calculated the wrong normals... Thanks for your help – user11914177 Feb 17 '20 at 13:16