Recalculate normals of a regular grid surface (C++/OpenGL)

Started by
9 comments, last by fabian7 4 years, 7 months ago

I'm trying to calculate normals of my grid surface. The map is 29952px x 19968px and each cell is 128px x 128px. So I have 36895 vertices.

hAYYT.png

My flat map array is sent to shaders with the following structure:


float vertices[368950] = {

  //  x     y     z    znoise   xTex  yTex  xNorm yNorm zNorm Type
    16384,16256,-16256,   0,    0.54, 0.45,   0,    0,   1,    1,
    16256,16384,-16384,   0,    0.54, 0.45,   0,    0,   1,    1,
    ......
} 

I calculate the zNoise with a function


float noise(float x, float y){};

And it works (I add it to y and z in the vertex shader).

Method 1

If i calculate normals using finite-difference method i obtain a nice result. Pseudo-Code:


vec3 off = vec3(1.0, 1.0, 0.0);
float hL = noise(P.xy - off.xz);
float hR = noise(P.xy + off.xz);
float hD = noise(P.xy - off.zy);
float hU = noise(P.xy + off.zy);
N.x = hL - hR;
N.y = hD - hU;
N.z = 2.0;
N = normalize(N);

But, in the case I need to edit the map manually, for example in a Editor context, where you set the zNoise with a tool to create mountains as you want, this method won't help.

I get this nice result (seen from minimap) (Normals are quite dark purposely)

Spoiler

016NP5V.jpg

Method 2


  |    |    |
--6----1----+-
  |\   |\   |      Y
  | \  | \  |      ^
  |  \ |  \ |      |
  |   \|   \|      |
--5----+----2--    +-----> X
  |\   |\   |
  | \  | \  |
  |  \ |  \ |
  |   \|   \|
--+----4----3--
  |    |    |

So i'm trying to calculate the normal using the adjacent triangles, but the result is very different (it seems that there's a bug somewhere):

Code:


std::array<glm::vec3, 6> getAdjacentVertices(glm::vec2 pos) {
    std::array<glm::vec3, 6> output;
    output = {
        // getVertex is a function that returns a glm::vec3 
        // with values x, y+znoise, z+znoise
        getVertex(pos.x, pos.y + 128), // up
        getVertex(pos.x + 128, pos.y), // right 
        getVertex(pos.x + 128, pos.y - 128), // down-right
        getVertex(pos.x, pos.y - 128), // down
        getVertex(pos.x - 128, pos.y), // left
        getVertex(pos.x - 128, pos.y + 128), // up-left

    };
    return output;
}

And the last function:


glm::vec3 mapgen::updatedNormals(glm::vec2 pos) {

    bool notBorderLineX = pos.x > 128 && pos.x < 29952 - 128;
    bool notBorderLineY = pos.y > 128 && pos.y < 19968 - 128;

    if (notBorderLineX && notBorderLineY) {

        glm::vec3 a = getVertex(pos.x, pos.y);

        std::array<glm::vec3, 6> adjVertices = getAdjacentVertices(pos);
        glm::vec3 sum(0.f);

        for (int i = 0; i < 6; i++) {
            int j;
            (i == 0) ? j = 5 : j = i - 1;

            glm::vec3 side1 = adjVertices[i] - a;
            glm::vec3 side2 = adjVertices[j] - a;

            sum += glm::cross(side1, side2);
        }
        return glm::normalize(sum);
    }

    else {
        return glm::vec3(0.3333f);
    }
}

I get this bad result (seen from minimap) unfortunately

Spoiler

BG3gPJG.jpg

Note: The buildings are in different positions but the surface has the same seed using the two methods.

Could anyone help? ? Thanks in advance.

Advertisement

Why do have to edit the map manually when using the difference method ?

And your pseudo code method 1 would calculate values for the same positions several times. If the noise function is not coherent there would be different outcomes each time.

And if i am not mistaken, method 2 does not look at the adjacent vertices but at those 128 positions away. Though i may have misunderstood something ...

 

 

 

 

 

sample the edited heightmap from the shader and calculate the normal exactly as you do with the noise normal.

1 hour ago, Green_Baron said:

Why do have to edit the map manually when using the difference method ?

And your pseudo code method 1 would calculate values for the same positions several times. If the noise function is not coherent there would be different outcomes each time.

And if i am not mistaken, method 2 does not look at the adjacent vertices but at those 128 positions away. Though i may have misunderstood something ...

 

 

 

 

 

Hey :) thanks for reading and posting.

Maybe I was not clear. The difference method works very well if the map is static: in fact, the noise function gives always the same value Z for a given (X,Y) (i save the seed).

The problem is: imagine that in the game you can edit the height with a tool (to create for example mountains). In that case, there is no function that approximates the normal with difference method. 

In the method 2 , the function getVertex() takes two parameters, x and y, that are world position, so doing +- 128 i take exactly the adjacent vertex (because the grid is 128px x 128px). Is it better explained now?

36 minutes ago, Krypt0n said:

sample the edited heightmap from the shader and calculate the normal exactly as you do with the noise normal.

This is a very good idea, if i've understood your answer?

You mean, do a render of the map on greyscale values, take the height value with ReadPixel() and use it to calculate the normal with Difference Method? This will be done just once (or just when you click using the height tool). 

31 minutes ago, fabian7 said:

Hey :) thanks for reading and posting.

Maybe I was not clear. The difference method works very well if the map is static: in fact, the noise function gives always the same value Z for a given (X,Y) (i save the seed).

The problem is: imagine that in the game you can edit the height with a tool (to create for example mountains). In that case, there is no function that approximates the normal with difference method. 

See @Krypt0n's answer ? Which is the normal (haha) way to to calculate normals from a heightmap. It does not solve the fact when something is edited/changed, that part must be renewed. But renewing e texture tile is probably more handy than part of a vertex buffer.

Quote

In the method 2 , the function getVertex() takes two parameters, x and y, that are world position, so doing +- 128 i take exactly the adjacent vertex (because the grid is 128px x 128px). Is it better explained now?

Yes. That part i didn't get. ? Thanks

Quote

This is a very good idea, if i've understood your answer?

You mean, do a render of the map on greyscale values, take the height value with ReadPixel() and use it to calculate the normal with Difference Method? This will be done just once (or just when you click using the height tool). 

Write the z values to a single channel texture (check if your graphics card supports such a huge texture) and sample the texture with texture() or textirelod(). But when values change the respective part of the texture must be renewed, so it would be an idea to portion the texture in tiles of a certain size. Not everything is displayed anyway at the same time. But any change to the texture will have effect immediately.

It'll look like this in the vertex shader (you probably don't need the position-to-texture ratio if that is 1:1 and the multiplier). Attention at the edges. The texture should have an overhang of 1.



// Intake is displaced position
vec3 computeNormalCentralDifference( vec3 pos ) {
	float leftHeight = texture( og_heightMap, vec2( pos.x - 1.0, pos.z ) * u_positionToTextureCoordinate).r * u_heightExaggeration;
    vec3 left = vec3( pos.x - 1.0, leftHeight, pos.z );
	float rightHeight = texture( og_heightMap, vec2( pos.x + 1.0, pos.z ) * u_positionToTextureCoordinate ).r * u_heightExaggeration;
    vec3 right = vec3( pos.x + 1.0, rightHeight, pos.z );
	float bottomHeight = texture( og_heightMap, vec2( pos.x, pos.z - 1.0 ) * u_positionToTextureCoordinate ).r * u_heightExaggeration;
    vec3 bottom = vec3( pos.x, bottomHeight, pos.z - 1.0 );
    float topHeight = texture( og_heightMap, vec2( pos.x, pos.z + 1.0 ) * u_positionToTextureCoordinate ).r * u_heightExaggeration;
    vec3 top = vec3( pos.x, topHeight, pos.z + 1.0 );    
    return cross( right - left, top - bottom );
}

 

32 minutes ago, Green_Baron said:

 

[...]

hhhhmm... I'm trying to understand ?

32 minutes ago, Green_Baron said:

(check if your graphics card supports such a huge texture)

Do you mean every pixel or every vertex? Because the grid is 234 x 156. The whole map is 29952 x 19968. But i guess every vertex, because we have data just for each vertex... ?

Moreover, this type of operations:


vec2( pos.x - 1.0, pos.z )

Do you mean take these z-values (the intensity)... ?

Immagine.png.87c1058581746659bbf45aaeb1fff9be.png

(Each quad aims to be a zoomed pixel of the texture)

Doing this by vertex, the result would be quite similar to Method 2, does it?

Thanks.

Ah, i think i see. So vertex 0/0 maps to texture position 0/0 and 234/156 to pos 1/1. PositionToTexture(*) is then ... errr ... a vec2(1/234, 1/156). Then the function should more or less (hopefully more more than less) work and, if your texture is sampled linearnearest, have a similar outcome as your method 2.

Edit: to map a given vertex x/y of the grid to a texel position in the texture.

no responsibility is taken for the correctness of this information

 

I add two other images, to help understanding the problem.

Method 1:

kmocn1l.png

Method 2:

oG25p0g.png

Not sure what you're trying to say.

The suggestion to use a texture for speedy updates works. I use it in my lod algorithm, with different normal calculation methods and scales and offsets for a regular gridmesh as well. Only the textures are tiled in handsome portions because my graphics card wants no 30k textures, and they take a long time to load and eat up GBs of memory ...

 

SOLVED!

I was calculating the normals at the same time I was computing the zNoise. So, it was possible that some vertices weren't at the right Z.

I solved calculating before all the Z, and then all the normals.

Thanks Green_Baron for helping!

This topic is closed to new replies.

Advertisement