当前位置: 首页 > news >正文

Open Scene Graph 3D到2D坐标转换

OSG中实现3D世界坐标到2D屏幕坐标的转换是许多应用的基础功能,如HUD显示、对象拾取等。以下是详细的实现方法:

1. 基本坐标转换原理

3D到2D坐标转换流程图:

2D到3D坐标转换流程图:

2. 核心转换方法

2.1 使用osg::Matrix实现转换

// 3D世界坐标转2D屏幕坐标
bool worldToScreen(const osg::Vec3& worldPos, osg::Vec3& screenPos, 
                  const osg::Camera* camera)
{
    // 获取视图矩阵、投影矩阵和视口
    const osg::Matrix& viewMatrix = camera->getViewMatrix();
    const osg::Matrix& projMatrix = camera->getProjectionMatrix();
    const osg::Viewport* viewport = camera->getViewport();
    
    if(!viewport) return false;
    
    // 执行坐标转换
    osg::Vec3 eyePos = worldPos * viewMatrix;
    osg::Vec3 clipPos = eyePos * projMatrix;
    
    // 归一化设备坐标(NDC)
    osg::Vec3 ndcPos;
    ndcPos.x() = clipPos.x() / clipPos.w();
    ndcPos.y() = clipPos.y() / clipPos.w();
    ndcPos.z() = clipPos.z() / clipPos.w();
    
    // 转换为屏幕坐标
    screenPos.x() = (ndcPos.x() * 0.5f + 0.5f) * viewport->width() + viewport->x();
    screenPos.y() = (ndcPos.y() * 0.5f + 0.5f) * viewport->height() + viewport->y();
    screenPos.z() = (ndcPos.z() * 0.5f + 0.5f);
    
    // 检查点是否在视锥体内
    return (ndcPos.x() >= -1.0f && ndcPos.x() <= 1.0f &&
            ndcPos.y() >= -1.0f && ndcPos.y() <= 1.0f &&
            ndcPos.z() >= -1.0f && ndcPos.z() <= 1.0f);
}

2.2 使用osgUtil::SceneView简化转换

osg::Vec3 worldToScreen(osg::Vec3 worldPos, osgUtil::SceneView* sceneView)
{
    osg::Matrix MVPW = sceneView->getProjectionMatrix() * 
                      sceneView->getViewMatrix() * 
                      osg::Matrix::identity();
    
    osg::Viewport* viewport = sceneView->getViewport();
    osg::Vec3 screenPos = worldPos * MVPW;
    
    // 透视除法
    screenPos.x() = (screenPos.x() / screenPos.z() * 0.5f + 0.5f) * viewport->width() + viewport->x();
    screenPos.y() = (screenPos.y() / screenPos.z() * 0.5f + 0.5f) * viewport->height() + viewport->y();
    
    return screenPos;
}

3. 2D到3D坐标转换(逆向)

// 2D屏幕坐标转3D世界坐标(获取地面交点)
bool screenToWorld(const osg::Vec3& screenPos, osg::Vec3& worldPos, 
                  const osg::Camera* camera, const osg::Vec3& planeNormal, float planeDistance)
{
    if(!camera || !camera->getViewport()) return false;
    
    // 创建从屏幕到世界的射线
    osg::Matrix VPW = camera->getViewport()->computeWindowMatrix();
    osg::Matrix inverseMVPW = osg::Matrix::inverse(
        camera->getViewMatrix() * 
        camera->getProjectionMatrix() * 
        VPW);
    
    // 近平面和远平面点
    osg::Vec3 nearPoint = osg::Vec3(screenPos.x(), screenPos.y(), 0.0f) * inverseMVPW;
    osg::Vec3 farPoint = osg::Vec3(screenPos.x(), screenPos.y(), 1.0f) * inverseMVPW;
    
    // 计算与指定平面的交点
    osg::Plane plane(planeNormal, planeDistance);
    return plane.intersect(nearPoint, farPoint, worldPos);
}

4. 实际应用示例

4.1 在3D对象上显示2D标签

class LabelUpdateCallback : public osg::NodeCallback {
public:
    LabelUpdateCallback(osgText::Text* label, osg::Node* target, osg::Camera* camera)
        : _label(label), _target(target), _camera(camera) {}
    
    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) {
        // 获取目标的世界坐标
        osg::Matrix worldMat = _target->getWorldMatrices()[0];
        osg::Vec3 worldPos = osg::Vec3(0,0,0) * worldMat;
        
        // 转换为屏幕坐标
        osg::Vec3 screenPos;
        if(worldToScreen(worldPos, screenPos, _camera)) {
            _label->setPosition(osg::Vec3(screenPos.x(), screenPos.y(), 0));
            _label->setNodeMask(0xFFFFFFFF); // 显示
        } else {
            _label->setNodeMask(0x0); // 隐藏
        }
        
        traverse(node, nv);
    }
    
private:
    osg::ref_ptr<osgText::Text> _label;
    osg::ref_ptr<osg::Node> _target;
    osg::ref_ptr<osg::Camera> _camera;
};

4.2 实现3D对象的屏幕空间固定效果

osg::Camera* createHUDCamera(osgViewer::Viewer* viewer)
{
    // 创建HUD相机
    osg::Camera* camera = new osg::Camera;
    camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
    camera->setProjectionMatrix(osg::Matrix::ortho2D(0, viewer->getCamera()->getViewport()->width(), 
                                                   0, viewer->getCamera()->getViewport()->height()));
    camera->setViewMatrix(osg::Matrix::identity());
    camera->setClearMask(GL_DEPTH_BUFFER_BIT);
    camera->setRenderOrder(osg::Camera::POST_RENDER);
    camera->setAllowEventFocus(false);
    return camera;
}

void addScreenFixedNode(osg::Group* root, osgViewer::Viewer* viewer, osg::Node* node, const osg::Vec3& screenPos)
{
    osg::Camera* hudCamera = createHUDCamera(viewer);
    
    // 更新回调保持屏幕位置
    node->setUpdateCallback(new ScreenPositionCallback(viewer->getCamera(), screenPos));
    
    hudCamera->addChild(node);
    root->addChild(hudCamera);
}

5. 高级应用:拾取(Picking)实现

class PickHandler : public osgGA::GUIEventHandler {
public:
    bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) {
        if(ea.getEventType() != osgGA::GUIEventAdapter::RELEASE || 
           ea.getButton() != osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)
            return false;
        
        osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
        if(!view) return false;
        
        // 2D屏幕坐标转3D世界射线
        osgUtil::LineSegmentIntersector::Intersections intersections;
        if(view->computeIntersections(ea.getX(), ea.getY(), intersections)) {
            for(auto& hit : intersections) {
                osg::Vec3 worldPos = hit.getWorldIntersectPoint();
                osg::Vec3 screenPos;
                
                // 3D到2D转换验证
                worldToScreen(worldPos, screenPos, view->getCamera());
                std::cout << "Picked at: World(" << worldPos << ") Screen(" << screenPos.x() << "," << screenPos.y() << ")\n";
            }
        }
        return true;
    }
};

6. 性能优化建议

  1. 批量转换:对多个点进行转换时,预计算MVPW矩阵

  2. 缓存结果:对于静态对象,缓存转换结果避免每帧计算

  3. 视锥体剔除:先检查点是否在视锥体内再执行转换

  4. 使用着色器:对大量点的大规模转换,考虑使用GPU计算

7. 常见问题解决

问题1:转换后的坐标不准确

  • 检查是否使用了正确的模型矩阵(对于变换节点)

  • 确认视口(viewport)设置正确

  • 验证投影矩阵是否匹配当前相机设置

问题2:点在屏幕外时计算错误

  • 添加视锥体检查代码

  • 使用clipPos.w()进行透视除法而非直接使用z值

问题3:正交投影下的转换问题

  • 正交投影下可以简化计算,不需要透视除法

  • 检查正交投影矩阵的near/far平面设置

http://www.dtcms.com/a/122488.html

相关文章:

  • 【数据库原理及安全实验】实验二 数据库的语句操作
  • 【软件测试】自动化测试框架Pytest + Selenium的使用
  • Ubuntu 24.04启用root账户
  • Hi168云平台部署Ansible学习环境
  • Mysql(继续更新)
  • linux入门三:Linux 编辑器
  • 查看手机在线状态,保障设备安全运行
  • js chrome 插件,下载微博视频
  • 树和图论【详细整理,简单易懂!】(C++实现 蓝桥杯速查)
  • Python | 第十三章 | 多态 | 魔术方法 | 静态方法 | 抽象类
  • c++day7
  • 基于 SpringBoot 的民宿管理平台系统
  • 用PHPExcel 封装的导出方法,支持导出无限列
  • DNS常见问题:什么是主机记录和记录值?(国科云)
  • 【Android】Android 获取当前前台应用包名与自动化控制全流程实践笔记(适配 Android 10+)
  • 网络安全防护技术
  • Python与DeepSeek应用:解锁AI开发新姿势
  • C# 设置Excel中文本的对齐方式、换行、和旋转
  • 【深度学习】Downstream Model:预训练模型的下游应用与微调技术
  • 网络空间安全(54)CSRF
  • 边缘计算盒子是什么?
  • Dify教程01-Dify是什么、应用场景、如何安装
  • 解决python manage.py shell ModuleNotFoundError: No module named xxx
  • OCR之行驶证识别
  • 【MySQL】——详解事务
  • 多账户使用Github的场景,设置 SSH 多账号使用特定 key
  • MySQL下200GB大表备份,利用传输表空间解决停服发版表备份问题
  • Pytorch查看神经网络结构和参数量
  • MongoDB 新手笔记
  • GitHub优秀项目:数据湖的管理系统LakeFS