OSG/C++ 实现GPU加速计算计算的详细步骤

在这里插入图片描述

阶段一:深度图生成(Depth Map Generation)

您提到您已经完成了这一步,但为了完整性,我们简要回顾关键点。

  1. 创建深度纹理(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);
    
  2. 设置正交相机(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());
    
  3. 获取矩阵:

    osg::Matrix VPMXt = depthCamera->getProjectionMatrix() * depthCamera->getViewMatrix();
    osg::Matrix IVPMX = osg::Matrix::inverse(VPMXt);
    
阶段二:高程计算(Elevation Calculation)

此阶段使用 GPU 计算,并将结果渲染到一个浮点纹理(即高程图)。

  1. 创建高程输出纹理(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);
    
  2. 创建全屏四边形(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)
    );
    
  3. 创建高程计算相机(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());
    
  4. 编写和应用着色器:

    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 中。

  1. 从纹理中读取数据:

    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 端的进一步处理 ...
    }
    
  2. 保存高程图(可选):

    如果您想将高程图保存为文件(例如 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 加速高程计算:

  1. 准备: 定义一个正交相机,并从其 RTT 获得 DepthTextureIVPMX
  2. 渲染: 创建一个 computeCamera,将 elevationTexture 挂载为颜色输出。
  3. 计算: 渲染一个全屏 QuadFragment Shader 利用 v_uvDepthTextureIVPMX 执行屏幕坐标 -> NDC -> 世界坐标的反投影计算,并将世界坐标的 Y(高程)写入 elevationTexture
  4. 读取: 使用 elevationImage->readPixels(...) 将 GPU 结果读回 CPU 内存进行后续处理或保存。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FE_Jinger

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值