如何在C++、OpenGL及GLM环境下沿物体边界向内挤出2D顶点/向量以赋予物体厚度?
如何用C++、OpenGL和GLM给顶点沿物体边界向内挤出赋予厚度?
我拥有一组顶点/向量,需要将它们沿物体边界向内挤出,以此为物体赋予厚度。举例来说:我需要将类似这样的图形转换为类似这样的图形,请问该如何实现?(我正在使用C++、OpenGL及GLM)——更新——感谢@Futurologist的解答,我已成功解决该问题,效果非常好!
刚好之前做过类似的需求!用C++、GLM和OpenGL实现顶点向内挤出赋予厚度,核心是计算每个顶点的内法线方向,然后沿这个方向偏移顶点,同时处理相邻顶点的平滑衔接,下面是具体的实现思路和代码示例:
核心思路
对于简单多边形(无自相交)的顶点挤出,我们需要为每个顶点找到指向物体内部的偏移方向,这个方向是相邻两条边的内法线的归一化平均值,这样能保证挤出后的顶点不会出现错位或缺口。
具体步骤
1. 判断多边形顶点顺序(确定内法线方向)
首先计算多边形的有向面积:如果面积为正,说明顶点是逆时针排列;如果为负则是顺时针排列。这个结果会决定我们如何计算每条边的内法线。
2. 计算每条边的内法线
对于每条边的向量(比如从顶点v_prev到v_curr的向量edge),我们需要生成指向多边形内部的法线:
- 若顶点是逆时针排列,将边向量顺时针旋转90度得到内法线(向量
(edge.y, -edge.x)) - 若顶点是顺时针排列,将边向量逆时针旋转90度得到内法线(向量
(-edge.y, edge.x))
3. 计算顶点的挤出方向
每个顶点的挤出方向是相邻两条边的内法线的归一化平均值,这样能让挤出后的顶点平滑衔接,避免尖角处出现断裂。
4. 沿挤出方向偏移顶点
用原始顶点位置加上「挤出方向 × 厚度值」,得到挤出后的新顶点。
C++ + GLM 代码实现
#include <glm/glm.hpp> #include <vector> #include <cmath> // 计算多边形的有向面积,用于判断顶点排列顺序 float calculatePolygonArea(const std::vector<glm::vec2>& vertices) { float area = 0.0f; const int vertexCount = vertices.size(); for (int i = 0; i < vertexCount; ++i) { const int nextIdx = (i + 1) % vertexCount; area += vertices[i].x * vertices[nextIdx].y - vertices[nextIdx].x * vertices[i].y; } return area * 0.5f; } // 将2D多边形顶点向内挤出指定厚度 std::vector<glm::vec2> extrudeVerticesInward(const std::vector<glm::vec2>& originalVertices, float thickness) { std::vector<glm::vec2> extrudedVertices; const int vertexCount = originalVertices.size(); if (vertexCount < 3) return extrudedVertices; // 至少需要三角形才能挤出厚度 const float polygonArea = calculatePolygonArea(originalVertices); const bool isCounterClockwise = polygonArea > 0.0f; for (int i = 0; i < vertexCount; ++i) { const int prevIdx = (i - 1 + vertexCount) % vertexCount; const int currIdx = i; const int nextIdx = (i + 1) % vertexCount; const glm::vec2& prevVertex = originalVertices[prevIdx]; const glm::vec2& currVertex = originalVertices[currIdx]; const glm::vec2& nextVertex = originalVertices[nextIdx]; // 计算前后两条边的向量 const glm::vec2 edgePrev = currVertex - prevVertex; const glm::vec2 edgeNext = nextVertex - currVertex; // 根据顶点排列顺序计算内法线 glm::vec2 normalPrev, normalNext; if (isCounterClockwise) { // 逆时针排列,内法线为边向量顺时针旋转90度 normalPrev = glm::vec2(edgePrev.y, -edgePrev.x); normalNext = glm::vec2(edgeNext.y, -edgeNext.x); } else { // 顺时针排列,内法线为边向量逆时针旋转90度 normalPrev = glm::vec2(-edgePrev.y, edgePrev.x); normalNext = glm::vec2(-edgeNext.y, edgeNext.x); } // 归一化法线,确保方向一致 normalPrev = glm::normalize(normalPrev); normalNext = glm::normalize(normalNext); // 计算当前顶点的挤出方向:两个法线的平均后归一化 const glm::vec2 extrusionDir = glm::normalize(normalPrev + normalNext); // 生成挤出后的顶点 const glm::vec2 extrudedVertex = currVertex + extrusionDir * thickness; extrudedVertices.push_back(extrudedVertex); } return extrudedVertices; }
额外注意事项
- 上述代码针对2D多边形,如果是3D物体的表面挤出,需要先确定每个面的法线,然后针对每个顶点计算相邻面法线的平均方向,再沿指向物体内部的方向偏移。
- 只适用于简单多边形(无自相交),复杂多边形需要额外的拓扑处理。
- 挤出厚度
thickness需要根据你的坐标尺度调整,避免挤出后顶点重叠。 - 如果要生成带厚度的3D网格,还需要将原始顶点和挤出后的顶点连接成面(比如每个原始边对应一个矩形面),再加上原始面和挤出后的面,形成闭合的3D模型。
内容的提问来源于stack exchange,提问作者UserNotFound
相关产品推荐
相关产品推荐

