OSG网格框选

文章讲述了在病害提取项目中,如何通过两种方法实现框选功能:一种是利用osg碰撞检测快速框选但可能产生网格漏洞,另一种是索引查找计算虽慢但无漏洞,同时讨论了其他库和工具如CloudCompare、Curve库以及ClipNode的应用。作者面临时间紧迫和性能优化的需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、需求来源

需求来自于病害提取,需要有个框选功能,用于框选病害区域,三维转二维图像,在二维图像进行标注病害最后在三维中体现出来,为满足胡妈要求,特此写这个文章,记录一下。

二、要求

要求不改变框选部分网格拓扑关系,纹理也需要在,相当于复制了网格的一部分。

三、方法1 osg碰撞

使用杨石兴老师方法,他给出了两个osg框选案例,这是杨老师的实现方式。修改之后就是通过记录鼠标的拖动的最大值和最小值,通过osg内置的碰撞检测来实现。通过区域内的密集碰撞,拿到碰撞的面片点,面片纹理坐标,面片纹理指针,最后组合成一个新的Geometry。

优点就是快,缺点也很明显了,有一些奇特的三角面片比较小,由于osg碰撞密度太稀疏,就会碰不到,就会会产生网格漏洞,其实可以将漏洞连接起来,因为纹理坐标是在的,所以按理说,连接起来以后不会出现纹理错乱的情况,但事实上,本人能力有限不会检查漏洞,漏洞算法又是几何学上的一个大问题。同时如果有两个相邻的三角面片都没有连接,那么我们怎么连接,连错了就是改变拓扑结构,谢t!CGAL貌似可以做网格布尔运算,但不带纹理,也会改变网格,opencascade应该可以,很现实的问题是我不会,我也不可能在短时间内去熟悉它,导师提个要求,就得学个新库,而且要5天内解决怎么可能。

方法二 索引查找

osg读入网格模型以后就有了纹理索引、图元索引等等,从网格索引中下手。首先框选能拿到选框的四个点,然后选框的中间点去碰撞,拿到中间碰撞得到的三角面片索引其中一个Index1,判断一下Index1在不在框内(三维坐标MVPW矩阵三维转屏幕点,在二维使用向量判断在不在框内),然后查找有哪些map中存在Index1,每组里的另外两个三角面片索引存上,再每组另外两个索引依次查在那个组里,在不在框内,用索引找索引,再用索引找索引,例如{1,2,3}{1,2,6}{2,5,8}查1,发现在1,2组里,另外四个索引是2,3,2,6为了减少计算(现在看的挺少的,真到了网格里成倍数的增加计算)使用std::set给去重就剩下了{2,3,6}在找2,发现是是5,8(查过的就不查了),在继续找3,发现已经没了,都检查完,挨个按照索引把顶点坐标,纹理坐标填进去,组成一个新的Geometry。

优点不会出现漏洞。缺点计算慢框选越大,计算的越慢,等着再优化一下,用一下QtConcurrent::mapped或者扔到GPU中去给它算,应该快很多了。

这个方法比遍历所有顶点快多了,遍历所有顶点又涉及到背面的问题,在选框内模型正面和背面,背面应该丢弃,又涉及到视口远近好像是叫拣选,麻烦。

参考过cloudCompare,发现人家人家深度集合于CCCore,看完以后沉默了,我这个就写一个一个事件,和一个查找索引的类,它那个代码太多了。不过还得多参考,人家那个快。

还有个Curve-库,blender里面使用了,可以带纹理的裁剪,好家伙下来一看,Linux,改了改cmakelist,又改了很多代码,真的麻烦,遂放弃。

还有个方法,就是仅碰撞四个点,然后拿到四个点的纹理坐标,组成个新的,像个平面,而且三维转二维也得投影不久省了一步,但事与愿违。小点的没问题。大点的纹理就乱了。乱了原因大概是纹理贴图在内插的时候就错了,一个三角面片内,内插没问题,一堆三件面片就给四个点,在内插纹理坐标就属于扯淡了。

 另外的方法就是使用clipNode,这个简单,就给个裁剪平面就完了,但ClipNode就是影响显示在屏幕上的模型而已,并没有对顶点做出实质改变。

快毕业了,在三维引擎开发,图形学路上慢慢前进。

<think>我们使用OpenGL将TIFF文件渲染为三维图像,主要思路是将TIFF文件中的高程数据(或灰度值)转换为地形高度,然后通过三角网格构建地形表面。以下是详细步骤: ### 1. 理解TIFF文件结构 TIFF(Tagged Image File Format)是一种灵活的栅格图像格式,常用于存储地理空间数据(如高程图)。如果TIFF文件包含高程数据,则每个像素的灰度值代表该点的高度值。 ### 2. 读取TIFF文件 使用库(如GDAL)读取TIFF文件,获取图像数据和地理信息(如分辨率、坐标范围等)。 #### 示例:使用GDAL读取TIFF ```python from osgeo import gdal # 打开TIFF文件 dataset = gdal.Open('terrain.tif', gdal.GA_ReadOnly) if dataset is None: raise Exception("无法打开TIFF文件") # 获取图像尺寸和波段 width = dataset.RasterXSize height = dataset.RasterYSize band = dataset.GetRasterBand(1) # 假设高程数据在第一个波段 # 读取高程数据为数组 elevation_data = band.ReadAsArray(0, 0, width, height) # 获取地理变换信息(用于坐标转换) geotransform = dataset.GetGeoTransform() # 左上角坐标 (x, y) 和像素大小 x_min = geotransform[0] y_max = geotransform[3] pixel_width = geotransform[1] pixel_height = geotransform[5] # 通常为负值(因为图像从上到下) # 关闭数据集 dataset = None ``` ### 3. 构建三维网格 将二维高程数组转换为三维顶点网格。每个像素的位置$(x, y)$对应一个顶点,其$z$坐标由高程值(可能缩放)决定。 #### 顶点坐标计算 - **水平坐标**:根据像素索引和地理变换计算: $$x = x_{\text{min}} + i \cdot \Delta x$$ $$y = y_{\text{max}} + j \cdot \Delta y$$ 其中$\Delta x$和$\Delta y$是像素宽高(注意$\Delta y$通常为负,因为图像从上到下存储)。 - **垂直坐标**:$z = \text{scale} \cdot \text{elevation}[i, j]$ #### 构建三角网格 每个网格单元(由相邻四个顶点构成)分解为两个三角形: - 三角形1:$(i,j)$, $(i+1,j)$, $(i,j+1)$ - 三角形2:$(i+1,j)$, $(i+1,j+1)$, $(i,j+1)$ ### 4. OpenGL渲染 使用OpenGL绘制三角网格。核心步骤包括: 1. **初始化OpenGL环境**(创建窗口、设置视口等)。 2. **创建顶点缓冲区对象(VBO)和顶点数组对象(VAO)**存储顶点数据。 3. **设置着色器程序(Shader)**处理顶点变换和光照。 4. **绘制三角网格**。 #### 示例代码框架(使用现代OpenGL,GLFW + GLAD) ```c++ #include <glad/glad.h> #include <GLFW/glfw3.h> #include <vector> // 顶点结构体 struct Vertex { float x, y, z; // 位置 float nx, ny, nz; // 法线(用于光照) }; // 生成地形网格 std::vector<Vertex> generateTerrainMesh(float* elevation, int width, int height, float scale) { std::vector<Vertex> vertices; // 遍历高程数组,生成顶点和法线 // ...(此处省略具体实现,见下文说明) return vertices; } int main() { // 初始化GLFW glfwInit(); GLFWwindow* window = glfwCreateWindow(800, 600, "TIFF Terrain", NULL, NULL); glfwMakeContextCurrent(window); gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); // 读取TIFF文件(此处省略,假设已获取高程数组和尺寸) int width = 512, height = 512; std::vector<float> elevation_data = ...; // 从TIFF读取的高程数据 // 生成网格 std::vector<Vertex> terrain = generateTerrainMesh(elevation_data.data(), width, height, 0.1f); // 创建VAO和VBO unsigned int VAO, VBO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, terrain.size() * sizeof(Vertex), terrain.data(), GL_STATIC_DRAW); // 设置顶点属性指针 // 位置属性 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 法线属性 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); // 着色器程序(顶点和片段着色器) const char* vertexShaderSource = "#version 330 core\n" "layout (location = 0) in vec3 aPos;\n" "layout (location = 1) in vec3 aNormal;\n" "out vec3 Normal;\n" "out vec3 FragPos;\n" "uniform mat4 model;\n" "uniform mat4 view;\n" "uniform mat4 projection;\n" "void main() {\n" " gl_Position = projection * view * model * vec4(aPos, 1.0);\n" " FragPos = vec3(model * vec4(aPos, 1.0));\n" " Normal = mat3(transpose(inverse(model))) * aNormal;\n" "}\0"; // ...(片段着色器代码,包含光照计算) // 主循环 while (!glfwWindowShouldClose(window)) { // 输入处理、清除颜色缓冲和深度缓冲 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 激活着色器程序 glUseProgram(shaderProgram); // 设置变换矩阵(模型、视图、投影) // ...(根据相机位置计算) // 绘制地形 glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, terrain.size()); glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); return 0; } ``` ### 5. 法线计算 为了在光照下显示地形细节,需要计算每个顶点的法线。法线可以通过相邻三角形法线的加权平均得到。 #### 计算三角形法线 对于一个三角形$(v_0, v_1, v_2)$,其法线为: $$\vec{n} = \frac{(v_1 - v_0) \times (v_2 - v_0)}{\|(v_1 - v_0) \times (v_2 - v_0)\|}$$ #### 顶点法线计算 遍历所有三角形,将每个三角形的法线累加到其三个顶点上,最后归一化每个顶点的法线向量。 ### 6. 纹理映射(可选) 如果TIFF包含颜色信息(如卫星图像),可将其作为纹理贴到地形上: 1. 读取TIFF的RGB波段。 2. 生成纹理ID并绑定。 3. 设置纹理坐标(每个顶点的$(s,t)$坐标)。 ### 注意事项 - **性能优化**:对于大型TIFF,使用细节层次(LOD)技术或分块加载。 - **坐标系统**:确保正确转换地理坐标到OpenGL世界坐标。 - **着色器**:使用Phong光照模型增强三维效果。 ### 总结 通过以上步骤,可将TIFF高程数据转换为三维地形并在OpenGL中渲染。核心是正确解析TIFF数据、构建三角网格、计算法线并使用现代OpenGL管线绘制。 §§相关问题§§ 1. 如何从多波段TIFF文件中提取高程和颜色信息? 2. OpenGL中如何实现地形LOD(细节层次)优化? 3. 在三维地形渲染中,如何添加纹理和光照效果? 4. 如何处理大型TIFF文件(内存和性能优化)? 5. 除了OpenGL,还有哪些库可以用于三维地形可视化?</think>### 使用OpenGL渲染TIFF文件为三维图像的完整方法 #### 1. 理解TIFF文件结构 TIFF文件包含像素值(高度信息)和地理元数据。渲染三维图像的核心是将像素灰度值转换为高度值: - **高度映射原理**: $$ z = \text{scale} \times \frac{\text{pixelValue} - \text{minValue}}{\text{maxValue} - \text{minValue}} $$ 其中 $\text{scale}$ 是高度缩放因子,$\text{pixelValue}$ 是像素灰度值[^4]。 #### 2. 数据准备与解析 ##### 2.1 读取TIFF文件 使用GDAL库(跨平台开源工具)读取TIFF数据: ```python from osgeo import gdal def read_tiff(file_path): dataset = gdal.Open(file_path, gdal.GA_ReadOnly) width = dataset.RasterXSize height = dataset.RasterYSize band = dataset.GetRasterBand(1) elevation = band.ReadAsArray(0, 0, width, height) return width, height, elevation ``` ##### 2.2 构建顶点网格 将二维像素阵列转换为三维顶点坐标: ```c++ std::vector<float> vertices; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float z = elevation[y][x] * height_scale; // 高度缩放 vertices.push_back(x); // X坐标 vertices.push_back(y); // Y坐标 vertices.push_back(z); // Z坐标(高度) } } ``` #### 3. OpenGL核心渲染流程 ##### 3.1 初始化OpenGL环境 使用GLFW创建窗口并加载OpenGL: ```c++ glfwInit(); GLFWwindow* window = glfwCreateWindow(800, 600, "TIFF 3D Render", NULL, NULL); glfwMakeContextCurrent(window); gladLoadGL(); ``` ##### 3.2 创建顶点缓冲区对象 (VBO) ```c++ unsigned int VBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW); // 设置顶点属性指针 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); ``` ##### 3.3 着色器程序(关键步骤) **顶点着色器 (vertex.glsl)**: ```glsl #version 330 core layout (location = 0) in vec3 aPos; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { gl_Position = projection * view * model * vec4(aPos, 1.0); } ``` **片段着色器 (fragment.glsl)**: ```glsl #version 330 core out vec4 FragColor; uniform vec3 objectColor; void main() { FragColor = vec4(objectColor, 1.0); } ``` ##### 3.4 渲染循环 ```c++ while (!glfwWindowShouldClose(window)) { // 清除缓冲 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 激活着色器 shader.use(); // 设置变换矩阵 glm::mat4 model = glm::rotate(glm::mat4(1.0f), (float)glfwGetTime(), glm::vec3(0.5f, 1.0f, 0.0f)); glm::mat4 view = glm::lookAt(cameraPos, cameraTarget, cameraUp); glm::mat4 projection = glm::perspective(glm::radians(45.0f), 800.0f/600.0f, 0.1f, 100.0f); shader.setMat4("model", model); shader.setMat4("view", view); shader.setMat4("projection", projection); // 绘制地形 glBindVertexArray(VAO); glDrawArrays(GL_POINTS, 0, width * height); // 使用点云或GL_TRIANGLES glfwSwapBuffers(window); glfwPollEvents(); } ``` #### 4. 高级优化技术 ##### 4.1 三角网格生成 将点云转换为三角网格提高渲染效率: ```c++ std::vector<unsigned int> indices; for (int y = 0; y < height-1; y++) { for (int x = 0; x < width-1; x++) { unsigned int i0 = y * width + x; unsigned int i1 = y * width + (x+1); unsigned int i2 = (y+1) * width + x; unsigned int i3 = (y+1) * width + (x+1); // 两个三角形构成一个网格单元 indices.push_back(i0); indices.push_back(i1); indices.push_back(i2); indices.push_back(i1); indices.push_back(i3); indices.push_back(i2); } } // 创建EBO(元素缓冲区对象) unsigned int EBO; glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW); ``` ##### 4.2 法线计算(光照效果) ```c++ glm::vec3 calculateNormal(glm::vec3 p1, glm::vec3 p2, glm::vec3 p3) { glm::vec3 U = p2 - p1; glm::vec3 V = p3 - p1; return glm::normalize(glm::cross(U, V)); } ``` ##### 4.3 纹理映射(卫星图像叠加) ```c++ // 加载卫星图像纹理 unsigned int texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texWidth, texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData); ``` #### 5. 完整工作流程 1. **数据准备**:读取TIFF高程数据 2. **顶点生成**:将像素值转换为三维坐标 3. **网格构建**:创建三角网格和索引 4. **OpenGL初始化**: - 创建窗口和上下文 - 编译着色器程序 - 配置VBO/VAO/EBO 5. **渲染循环**: - 设置模型/视图/投影矩阵 - 应用光照和纹理 - 绘制地形网格 6. **交互控制**:实现相机旋转/缩放 #### 注意事项 1. **坐标系转换**:TIFF的地理坐标需转换为OpenGL坐标系 2. **性能优化**: - 使用细节层次(LOD)技术处理大型TIFF - 实现视锥体裁剪 3. **错误处理**:检查OpenGL错误 `glGetError()` 4. **内存管理**:大型TIFF需分块加载 > 关键参考:使用高度图生成三维地形是计算机图形学经典技术,通过灰度值控制顶点高度实现地形可视化[^4]。OSGOSGEarth提供了更高级的地理空间渲染能力[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值