
阶段一:深度图生成(Depth Map Generation)
您提到您已经完成了这一步,但为了完整性,我们简要回顾关键点。
-
创建深度纹理(Depth Texture):
osg::ref_ptr<osg::Texture2D> depthTexture = new osg::Texture2D; depthTexture->setTextureSize(MAP_WIDTH, MAP_HEIGHT); depthTexture->setInternalFormat(GL_DEPTH_COMPONENT32); // 确保高精度 depthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST); depthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST); -
设置正交相机(Orthographic Camera for RTT):
osg::ref_ptr<osg::Camera> depthCamera = new osg::Camera; // 1. 设置 RTT depthCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); depthCamera->attach(osg::Camera::DEPTH_BUFFER, depthTexture.get()); depthCamera->setClearMask(GL_DEPTH_BUFFER_BIT); depthCamera->setClearDepth(1.0); // 2. 设置视口和投影矩阵 depthCamera->setViewport(0, 0, MAP_WIDTH, MAP_HEIGHT); // 假设您的场景范围是 [MinX, MaxX], [MinY, MaxY], [MinZ, MaxZ] depthCamera->setProjectionMatrixAsOrtho(MinX, MaxX, MinY, MaxY, NearZ, FarZ); // 3. 设置视图矩阵 (例如,向下看) depthCamera->setViewMatrixAsLookAt(Center_Pos + osg::Vec3(0, 0, LookAt_Distance), Center_Pos, osg::Vec3(0, 1, 0)); // 假设Z轴是向上 // 4. 将场景根节点添加到深度相机 depthCamera->addChild(sceneRoot.get()); -
获取矩阵:
osg::Matrix VPMXt = depthCamera->getProjectionMatrix() * depthCamera->getViewMatrix(); osg::Matrix IVPMX = osg::Matrix::inverse(VPMXt);
阶段二:高程计算(Elevation Calculation)
此阶段使用 GPU 计算,并将结果渲染到一个浮点纹理(即高程图)。
-
创建高程输出纹理(Elevation Output Texture):
使用浮点格式存储高程值,以保留精度。
osg::ref_ptr<osg::Texture2D> elevationTexture = new osg::Texture2D; elevationTexture->setTextureSize(MAP_WIDTH, MAP_HEIGHT); elevationTexture->setInternalFormat(GL_R32F); // 单通道32位浮点数,存储高程 elevationTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::NEAREST); elevationTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST); -
创建全屏四边形(Full-Screen Quad):
用于触发片元着色器在每个像素上运行。
osg::ref_ptr<osg::Geometry> quad = osg::createTexturedQuadGeometry( osg::Vec3(-1.0f, -1.0f, 0.0f), osg::Vec3(2.0f, 0.0f, 0.0f), osg::Vec3(0.0f, 2.0f, 0.0f) ); -
创建高程计算相机(Elevation Compute Camera):
这个相机用于渲染 Quad 并将结果输出到
elevationTexture。osg::ref_ptr<osg::Camera> computeCamera = new osg::Camera; // 1. 设置 RTT computeCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); computeCamera->attach(osg::Camera::COLOR_BUFFER0, elevationTexture.get()); // 挂载到颜色附件0 computeCamera->setClearMask(GL_COLOR_BUFFER_BIT); computeCamera->setClearColor(osg::Vec4(0.0f, 0.0f, 0.0f, 0.0f)); // 2. 设置为2D正交投影(绘制全屏Quad) computeCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); computeCamera->setProjectionMatrix(osg::Matrix::identity()); computeCamera->setViewMatrix(osg::Matrix::identity()); computeCamera->setViewport(0, 0, MAP_WIDTH, MAP_HEIGHT); // 3. 渲染设置 computeCamera->setRenderOrder(osg::Camera::POST_RENDER); // 确保在场景渲染后执行 computeCamera->setCullMask(~0); // 始终渲染 computeCamera->setCullingActive(false); // 4. 添加 Quad osg::ref_ptr<osg::Geode> geode = new osg::Geode; geode->addDrawable(quad.get()); computeCamera->addChild(geode.get()); -
编写和应用着色器:
A. GLSL 代码
Vertex Shader (
vertex.glsl):#version 330 core in vec4 osg_Vertex; out vec2 v_uv; void main() { gl_Position = osg_Vertex; // Quad 坐标 [-1, 1] -> 纹理坐标 [0, 1] v_uv = osg_Vertex.xy * 0.5 + 0.5; }Fragment Shader (
fragment.glsl): 核心反投影逻辑#version 330 core in vec2 v_uv; uniform sampler2D DepthTexture; // 深度图纹理 (来自阶段一) uniform mat4 IVPMX; // 正交相机逆VP矩阵 (来自阶段一) layout(location = 0) out vec4 finalElevation; void main() { // 1. 采样深度值 [0, 1] float depth = texture(DepthTexture, v_uv).r; // 2. 构造 NDC 坐标 [-1, 1] vec4 ndc_coord = vec4( v_uv.x * 2.0 - 1.0, // X_NDC: [-1, 1] v_uv.y * 2.0 - 1.0, // Y_NDC: [-1, 1] depth * 2.0 - 1.0, // Z_NDC: [-1, 1] 1.0 ); // 3. 反投影到世界坐标 (World Coordinates) vec4 world_coord = IVPMX * ndc_coord; // 4. 执行透视除法(正交投影的 w 通常是 1.0,但保留以确保正确性) world_coord /= world_coord.w; // 5. 提取高程(假设场景中 Y 轴代表高程) float elevation = world_coord.y; // 6. 输出高程值到颜色附件0的R通道 // 因为输出纹理是 GL_R32F,它只会使用 R 通道。 finalElevation = vec4(elevation, 0.0, 0.0, 1.0); }B. C++ 中设置
osg::Program和 Uniforms:osg::ref_ptr<osg::Program> program = new osg::Program; program->addShader(osg::Shader::readShaderFile(osg::Shader::VERTEX, "vertex.glsl")); program->addShader(osg::Shader::readShaderFile(osg::Shader::FRAGMENT, "fragment.glsl")); osg::ref_ptr<osg::StateSet> stateset = quad->getOrCreateStateSet(); stateset->setAttributeAndModes(program.get(), osg::StateAttribute::ON); // 传递 Uniform 数据 // 传递 IVPMX 矩阵 stateset->addUniform(new osg::Uniform("IVPMX", IVPMX)); // 传递深度纹理 stateset->setTextureAttributeAndModes(0, depthTexture.get(), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("DepthTexture", 0)); // 绑定到纹理单元 0 // 禁用光照等,确保只运行着色器 stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
阶段三:结果提取与输出(Extraction and Output)
渲染完成后,高程值已经存储在 elevationTexture 中。
-
从纹理中读取数据:
osg::ref_ptr<osg::Image> elevationImage = new osg::Image; // 从 computeCamera 的 FBO 读取像素到 Image 对象 elevationImage->readPixels(0, 0, MAP_WIDTH, MAP_HEIGHT, GL_RED, GL_FLOAT); // 检查读取是否成功 if (elevationImage->valid()) { // 获取高程数据指针 (float*) const float* elevationData = reinterpret_cast<const float*>(elevationImage->getDataPointer()); // 此时,elevationData 包含了所有 MAP_WIDTH * MAP_HEIGHT 个高程值。 // elevationData[i + j * MAP_WIDTH] 就是 (i, j) 坐标的高程。 // ... 在此进行 CPU 端的进一步处理 ... } -
保存高程图(可选):
如果您想将高程图保存为文件(例如 GeoTIFF 或简单的 TIFF),OSG 的
osgDB::writeImageFile可以保存图像,但可能不支持直接保存 GL_FLOAT 格式的 TIFF。您可能需要:- 方法一 (推荐): 将浮点高程数据写入专门支持高程数据的格式(如 TIFF/GeoTIFF,需要使用第三方库如 GDAL)。
- 方法二 (调试/可视化): 将浮点高程值重新映射到 8 位或 16 位整数范围,然后使用 OSG 保存为 PNG/TIFF。
示例:将数据保存为 PNG (灰度可视化)
// 仅用于可视化,将浮点高程归一化到 [0, 255] osg::ref_ptr<osg::Image> normalizedImage = new osg::Image; normalizedImage->allocateImage(MAP_WIDTH, MAP_HEIGHT, 1, GL_RED, GL_UNSIGNED_BYTE); unsigned char* data = normalizedImage->getDataPointer(); // 假设您已经计算了 minElevation 和 maxElevation float minEle = /* ... */; float maxEle = /* ... */; float range = maxEle - minEle; for (int i = 0; i < MAP_WIDTH * MAP_HEIGHT; ++i) { float normalized = (elevationData[i] - minEle) / range; data[i] = (unsigned char)(normalized * 255.0f); } osgDB::writeImageFile(*normalizedImage, "elevation_map_vis.png");
总结
您通过以下步骤完成了整个 GPU 加速高程计算:
- 准备: 定义一个正交相机,并从其 RTT 获得
DepthTexture和IVPMX。 - 渲染: 创建一个
computeCamera,将elevationTexture挂载为颜色输出。 - 计算: 渲染一个全屏
Quad,Fragment Shader利用v_uv、DepthTexture和IVPMX执行屏幕坐标 -> NDC -> 世界坐标的反投影计算,并将世界坐标的 Y(高程)写入elevationTexture。 - 读取: 使用
elevationImage->readPixels(...)将 GPU 结果读回 CPU 内存进行后续处理或保存。
562

被折叠的 条评论
为什么被折叠?



