重新计算规则网格表面的法线 (C++/OpenGL)

Posted

技术标签:

【中文标题】重新计算规则网格表面的法线 (C++/OpenGL)【英文标题】:Recalculate normals of a regular grid surface (C++/OpenGL) 【发布时间】:2019-10-01 10:11:08 【问题描述】:

我正在尝试计算网格表面的法线。 地图为 29952 像素 x 19968 像素,每个单元格为 128 像素 x 128 像素。所以我有36895个顶点。

网格:

我的平面地图数组被发送到具有以下结构的着色器:

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,
    ......
 

我用函数计算 zNoise

float noise(float x, float y);

它可以工作(我将它添加到顶点着色器中的 y 和 z)

方法一

如果我使用有限差分法计算法线,我会得到一个不错的结果。 伪代码:

  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);

但是,如果我需要手动编辑地图,例如在编辑器上下文中,您使用工具设置 zNoise 以根据需要创建山脉,则此方法无济于事.

我得到了这个不错的结果(从小地图上看)(法线故意很暗):

方法二

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

所以我尝试使用相邻的三角形计算法线,但结果却大不相同(似乎某处有错误):

代码。

getVertex() 是一个函数,它接受 x 和 y 返回与该顶点关联的顶点信息。 VerticesPos 是一个一维数组,其中包含 每个顶点的位置,以便能够从 vertices(我在上面描述的那个,每个顶点有 10 个值)获取信息。我决定编辑顶点着色器中的 y 和 z 以保持 x 和 y 不变,并通过VerticesPos 将它们用于索引顶点。 (我希望它很清楚)。

glm::vec3 getVertex(int x, int y) 

    int j = VerticesPos[(int)(y/128 * 29952 / 128 + x/128)];

    float zNoise = vertices[j * 10 + 3] * 2;
    float x1 = vertices[j * 10];
    float y1 = vertices[j * 10 + 1] + zNoise;
    float z1 = vertices[j * 10 + 2] + zNoise;

    return glm::vec3(x1, y1, z1);

getAdjacentVertices() 是一个函数,它采用 vec2d(x 和 y 坐标)并返回 6 个相邻顶点,按顺时针方向排序

std::array<glm::vec3, 6> getAdjacentVertices(glm::vec2 pos) 
    std::array<glm::vec3, 6> output;
    output = 
        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;

最后一个完成这项工作的函数:

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);
    

不幸的是,我得到了这个bad result(从小地图上看到):

注意:使用这两种方法,建筑物在不同的位置但表面具有相同的种子

有人可以帮忙吗? :-)


编辑:

我添加了更多图片以帮助理解问题。 方法一:

方法二:

【问题讨论】:

我了解您的尝试,我可以按照您的代码进行操作。这对我来说似乎是正确的。问题可能出在getVertex 中吗? getVertex 似乎读取了与 2 维坐标相关联的高度并返回 3D 坐标。 (高度)查找是否有可能每行(行)移动 1? @Rabbid76 我编辑了帖子,添加了getVertex 函数。 你确定(int)(y/128 * 29952 / 128 + x/128)?如果纹理连续有 29952 个像素,那么它必须是(int)(y/128 * 29952 + x/128)?我错了吗? 是的,因为 29952 是地图宽度,但请记住,我们每 128px 有一个顶点,所以我们在 x 轴上有 234 个顶点,在 y 轴上有 156 个顶点。 y 轴上是否有 234 个图块或顶点? 【参考方案1】:

解决了!

我在计算 zNoise 的同时计算法线。 所以,有可能某些顶点不在正确的 Z 轴上。

我在所有 Z 之前解决了计算,然后是所有法线。 我只想放一些代码:

struct Triangle 
    glm::vec3 a, b, c;
;
std::array<Triangle, 6> getAdjacentTriangles(glm::ivec2 pos) 
    int gap = 128;
    std::array<Triangle, 6> triangles;  
    triangles[0] = 
        getVertex(pos.x, pos.y), getVertex(pos.x - gap, pos.y),getVertex(pos.x - gap, pos.y + gap),
    ;
    triangles[1] = 
        getVertex(pos.x, pos.y), getVertex(pos.x - gap, pos.y + gap), getVertex(pos.x, pos.y + gap)
    ;
    triangles[2] = 
        getVertex(pos.x, pos.y), getVertex(pos.x, pos.y + gap), getVertex(pos.x + gap, pos.y)
    ;
    triangles[3] = 
        getVertex(pos.x, pos.y), getVertex(pos.x + gap, pos.y), getVertex(pos.x + gap, pos.y - gap)
    ;
    triangles[4] = 
        getVertex(pos.x, pos.y), getVertex(pos.x + gap, pos.y - gap),getVertex(pos.x, pos.y - gap),
    ;
    triangles[5] = 
        getVertex(pos.x, pos.y), getVertex(pos.x, pos.y - gap), getVertex(pos.x - gap, pos.y)
    ;
    return triangles;

glm::vec3 calculateTriangleNormal(Triangle T) 
    glm::vec3 N = glm::cross(T.c - T.a, T.b - T.a);
    return N;

void mapgen::updateNormals() 
    for (int i = 0; i < nVertices * 10; i += 10) 
        float xCoord = mapgen::MapVertices()[i];
        float yCoord = mapgen::MapVertices()[i + 1];
        glm::ivec2 pos = glm::ivec2(int(xCoord), int(yCoord));  
        if (pos.x > 128 && pos.x < 29952 - 128 && pos.y > 128 && pos.y < 19968 - 128) 

            std::array<Triangle, 6> adjacentTriangles = getAdjacentTriangles(pos);
            glm::vec3 sum(0.f);

            for (int i = 0; i < 6; i++) 
                glm::vec3 normal = calculateTriangleNormal(adjacentTriangles[i]);
                sum += normal;
            
            glm::vec3 N = glm::normalize(sum);

            mapgen::MapVertices()[i + 6] = N.x;
            mapgen::MapVertices()[i + 7] = N.y;
            mapgen::MapVertices()[i + 8] = N.z;
        
        else 
            mapgen::MapVertices()[i + 6] = 0.f;
            mapgen::MapVertices()[i + 7] = 0.f;
            mapgen::MapVertices()[i + 8] = 1.f;
        
    

耶!

【讨论】:

以上是关于重新计算规则网格表面的法线 (C++/OpenGL)的主要内容,如果未能解决你的问题,请参考以下文章

如何在OpenGL中计算三角形网格的顶点法线?

计算机图形学(OPENGL):法线贴图

计算网格的顶点法线[重复]

✠OpenGL-10-增强表面细节

OpenGL/GLUT 圆锥表面法线

Opengl-法线贴图(用来细化表面的表现表现的凹凸)