目录

1.前言

2.原理

3.实现


1.前言

使用射线投掷法(Ray-casting)在三维场景中拾取物体时,我们会从观察点,即眼睛所在位置向场景中发射一条射线,射线的方向通常由"鼠标"位置确定。在此类应用中需要解决的一个主要问题是:如何将二维窗口坐标转为三维场景坐标?我们接下来将对此问题进行探讨。

2.原理

世界坐标系中的点通过视图矩阵和投影矩阵变换到裁剪空间(Clip space),再通过透视除法变换为标准化设备坐标(NDC, normalized device coordinate)。NDC是一个长宽高取值都为[-1, 1]的空间,也就是说世界坐标变换为NDC坐标后,x,y,z都在[-1,1]的顶点才是可见的。

本位以下公式中的坐标向量使用齐次坐标,即,坐标分量分别用表示。

假设世界坐标系中有一点P,齐次坐标记为,其中,则有

                                      (1)

                                                             (2)

在公式(1)中, 表示P点在裁剪空间坐标系中的坐标,表示视图矩阵,表示投影矩阵。在公式(2)中,视图矩阵与投影矩阵相乘得到“视图投影矩阵”记为 。

由公式(1)和公式(2)得

                                                                            (3)

P点的NDC坐标记为,透视除法的过程是用裁剪空间坐标的4个分量分别除以w分量,即

                                                                              (4)

由公式(3)和公式(4)得

                                                             (5)

在公式(5)中,由相机参数获取,是输入参数,是P点在裁剪空间中坐标的第四个分量w。

由前文可知,结合公式(5)有

                                      (6)

由公式(6)可得

                                                                (7)

将公式(7)带入公式(5)可得

                                          (8)

使用公式(8)就可将NDC坐标转换为世界坐标。

3.实现

我们根据上述原理,在osg中实现以下功能:鼠标左键点击屏幕,从眼睛(相机)处向场景中发射一条射线并在场景中绘制出来(绘制射线从相机到远裁剪平面之间的线段)。

我们首先创建一个osgViewer::Viewer对象,并在窗口中创建视图。自定义函数createScene()创建一个简单场景并返回其根节点,然后将其加入到根节点root下。

而后我们创建一个自定义的事件处理类MyHandler并加入到osgViewer::Viewer对象的事件处理器列表中,用来处理我们的输入事件以实现交互。

最后进入主循环,直到程序退出。

int main()
{osgViewer::Viewer viewer;viewer.setUpViewInWindow(100, 100, 800, 600);osg::ref_ptr<osg::Group> root = new osg::Group;root->addChild(createScene());viewer.setSceneData(root);viewer.addEventHandler(new MyHandler(root));return viewer.run();
}

自定义函数createScene()创建一个由一个球体和一个立方体组成的简单场景并返回场景的根节点。

osg::ref_ptr<osg::Node> createScene()
{osg::ref_ptr<osg::Group> root = new osg::Group;osg::ref_ptr<osg::Geode> geode = new osg::Geode;root->addChild(geode);osg::ref_ptr<osg::Shape> sphere = new osg::Sphere(osg::Vec3(-5.f, 0.f, 0.f), 3.f);osg::ref_ptr<osg::ShapeDrawable> sphereDrawable = new osg::ShapeDrawable(sphere);sphereDrawable->setColor(osg::Vec4(1.f, 1.f, 1.f, 1.f));geode->addDrawable(sphereDrawable);osg::ref_ptr<osg::Shape> box = new osg::Box(osg::Vec3(5.f, 0.f, 0.f), 4.f);osg::ref_ptr<osg::ShapeDrawable> boxDrawable = new osg::ShapeDrawable(box);boxDrawable->setColor(osg::Vec4(0.f, 0.f, 1.f, 1.f));geode->addDrawable(boxDrawable);return root;
}

自定义类MyHandler 继承自osgGA::GUIEventHandler。定义osg::ref_ptr<osg::Group>类型的成员变量 _root作为绘制射线子场景的根节点,同时定义一系列成员变量对射线进行管理。

class MyHandler : public osgGA::GUIEventHandler
{
public:MyHandler(osg::ref_ptr<osg::Group> parent): _parent(parent){_root = new osg::Group;if (_parent.valid()){_parent->addChild(_root);}}private:osg::ref_ptr<osg::Geode> _rayNode;osg::observer_ptr<osg::Group> _parent;osg::ref_ptr<osg::Vec3Array> _rayVertices;osg::ref_ptr<osg::Group> _root;
};

通过重写虚函数handle()实现事件处理。在本例中,用户按下Ctrl+鼠标左键组合键,就可以在三维空间中画出一条线段,这条线段起源于eye坐标,终止于鼠标点击位置的远裁剪平面。

在handle()函数中,取出相机的视图矩阵viewMatrix和投影矩阵projMatrix,由viewMatrix可以获取相机的位置eye,观察点center和上方向up。

根据公式(8),我们需要获取视图和投影矩阵乘积的逆矩阵invvpMatrix ,然后与NDC坐标相乘得到对应的世界坐标。使用osgGA::GUIEventAdapter::getXnormalized()和getYnormalized()方法可以获取鼠标当前位置的NDC坐标的x和y分量,我们取1.0作为NDC坐标的z分量,在相机视椎体frustum中,远裁剪平面对应的NDC坐标z分量为1.0,因此变换后的世界坐标位于远裁剪平面上。

透视投影视椎体frustum

下面代码中矩阵的运算顺序与公式(8)中推导的有所不同,原因是为了与GLSL中的矩阵对应,osg中默认的向量是列主序存储的,因此要将公式推导中的矩阵和向量做转置处理,即代码中的形式。

使用相机位置eye和远裁剪平面上的点worldFar绘制射线,本例子中添加了一个自定义成员函数createOrUpdateRay()完成此工作,如果场景中还不存在射线(线段)节点,则创建一个节点,否则用最新的起始点和终止点更新它。

class MyHandler : public osgGA::GUIEventHandler
{
public:...  virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor*) override{if ((ea.getEventType() & osgGA::GUIEventAdapter::EventType::PUSH) &&(ea.getModKeyMask() & osgGA::GUIEventAdapter::ModKeyMask::MODKEY_CTRL) && (ea.getButton() & osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)){do{auto view = aa.asView();if (!view){break;}auto camera = view->getCamera();if (!camera){break;}const auto& viewMatrix = camera->getViewMatrix();const auto& projMatrix = camera->getProjectionMatrix();auto vpMatrix = viewMatrix * projMatrix;auto invvpMatrix = osg::Matrixd::inverse(vpMatrix);osg::Vec3d eye, center, up;viewMatrix.getLookAt(eye, center, up);osg::Vec4d ndcFar(ea.getXnormalized(), ea.getYnormalized(), 1.f, 1.f);auto worldFar = invvpMatrix.preMult(ndcFar);worldFar /= worldFar.w();createOrUpdateRay(eye, worldFar);} while (0);}return false;}private:void createOrUpdateRay(const osg::Vec3d& start, const osg::Vec3d& end){if (!_rayNode && _root){//create new ray node_rayNode = new osg::Geode;_root->addChild(_rayNode);osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;_rayNode->addDrawable(geom);geom->setUseVertexBufferObjects(true);_rayVertices = new osg::Vec3Array(2);geom->setVertexArray(_rayVertices);osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array(2);colors->at(0) = osg::Vec4(1.f, 0.f, 0.f, 1.f);colors->at(1) = osg::Vec4(1.f, 1.f, 0.f, 1.f);geom->setColorArray(colors, osg::Array::Binding::BIND_PER_VERTEX);geom->addPrimitiveSet(new osg::DrawArrays(GL_LINES, 0, _rayVertices->size()));geom->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);}if (_rayVertices){_rayVertices->at(0) = start;_rayVertices->at(1) = end;_rayVertices->dirty();}}...
}

至此,主要代码就完成了。程序运行的效果如下:

按住ctrl点击窗口,则会从相机(眼睛)位置到远裁剪平面发射一条射线并绘制,此时我们是看不到这条射线的,想象一下,一条从眼睛发出的射线,经过投影后变为一个没有面积的“点”,在光栅化的过程中,这样的线段不形成像素,因此不可见。只需拖动鼠标移动一下视点位置就可以看到射线了。

文末,贴上全部代码。另外我已将此例子的CMake工程代码提交至Gitee,传送门。

完整代码

#include <osg/ShapeDrawable>
#include <osgViewer/Viewer>osg::ref_ptr<osg::Node> createScene()
{osg::ref_ptr<osg::Group> root = new osg::Group;osg::ref_ptr<osg::Geode> geode = new osg::Geode;root->addChild(geode);osg::ref_ptr<osg::Shape> sphere = new osg::Sphere(osg::Vec3(-5.f, 0.f, 0.f), 3.f);osg::ref_ptr<osg::ShapeDrawable> sphereDrawable = new osg::ShapeDrawable(sphere);sphereDrawable->setColor(osg::Vec4(1.f, 1.f, 1.f, 1.f));geode->addDrawable(sphereDrawable);osg::ref_ptr<osg::Shape> box = new osg::Box(osg::Vec3(5.f, 0.f, 0.f), 4.f);osg::ref_ptr<osg::ShapeDrawable> boxDrawable = new osg::ShapeDrawable(box);boxDrawable->setColor(osg::Vec4(0.f, 0.f, 1.f, 1.f));geode->addDrawable(boxDrawable);return root;
}class MyHandler : public osgGA::GUIEventHandler
{
public:MyHandler(osg::ref_ptr<osg::Group> parent): _parent(parent){_root = new osg::Group;if (_parent.valid()){_parent->addChild(_root);}}virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, osg::Object*, osg::NodeVisitor*) override{if ((ea.getEventType() & osgGA::GUIEventAdapter::EventType::PUSH) &&(ea.getModKeyMask() & osgGA::GUIEventAdapter::ModKeyMask::MODKEY_CTRL) && (ea.getButton() & osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON)){do{auto view = aa.asView();if (!view){break;}auto camera = view->getCamera();if (!camera){break;}const auto& viewMatrix = camera->getViewMatrix();const auto& projMatrix = camera->getProjectionMatrix();auto vpMatrix = viewMatrix * projMatrix;auto invvpMatrix = osg::Matrixd::inverse(vpMatrix);osg::Vec3d eye, center, up;viewMatrix.getLookAt(eye, center, up);osg::Vec4d ndcFar(ea.getXnormalized(), ea.getYnormalized(), 1.f, 1.f);auto worldFar = invvpMatrix.preMult(ndcFar);worldFar /= worldFar.w();createOrUpdateRay(eye, osg::Vec3d(worldFar.x(), worldFar.y(), worldFar.z()));} while (0);}return false;}private:void createOrUpdateRay(const osg::Vec3d& start, const osg::Vec3d& end){if (!_rayNode && _root){//create new ray node_rayNode = new osg::Geode;_root->addChild(_rayNode);osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;_rayNode->addDrawable(geom);geom->setUseVertexBufferObjects(true);_rayVertices = new osg::Vec3Array(2);geom->setVertexArray(_rayVertices);osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array(2);colors->at(0) = osg::Vec4(1.f, 0.f, 0.f, 1.f);colors->at(1) = osg::Vec4(1.f, 1.f, 0.f, 1.f);geom->setColorArray(colors, osg::Array::Binding::BIND_PER_VERTEX);geom->addPrimitiveSet(new osg::DrawArrays(GL_LINES, 0, _rayVertices->size()));geom->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);}if (_rayVertices){_rayVertices->at(0) = start;_rayVertices->at(1) = end;_rayVertices->dirty();}}private:osg::ref_ptr<osg::Geode> _rayNode;osg::observer_ptr<osg::Group> _parent;osg::ref_ptr<osg::Vec3Array> _rayVertices;osg::ref_ptr<osg::Group> _root;
};int main()
{osgViewer::Viewer viewer;viewer.setUpViewInWindow(100, 100, 800, 600);osg::ref_ptr<osg::Group> root = new osg::Group;root->addChild(createScene());viewer.setSceneData(root);viewer.addEventHandler(new MyHandler(root));return viewer.run();
}

osg中NDC坐标转换为世界坐标相关推荐

  1. 像素坐标转换为世界坐标

    介绍 像素坐标转换为世界坐标 方法 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); 像素坐标指的是屏幕上像素的位置坐标, ...

  2. matlab 投影笛卡尔坐标转换,如何将一个三维笛卡尔坐标系中的坐标转换为另一个三维笛卡尔坐标系...

    我从一个坐标系开始,沿Z轴的一个点由P1 = [0 0 h]给出 由P2 = [h * tan(A),h * tan(B),0]给出的XY平面中的一个点 然后我求解垂直于从P2到P1的矢量的平面的方程 ...

  3. canvas中window坐标转换为canvas坐标

    function getMousePos(canvas, evt) { var rect = canvas.getBoundingClientRect(); return { x: evt.clien ...

  4. osg中父子节点关系对坐标系的影响(相对坐标绝对坐标%矩阵变换)

    从头梳理一下碰到的坐标问题. osgEarth中坐标系有很多,如世界坐标系,地理坐标系,相机坐标系等等,然后根据形式又分为相对坐标系和绝对坐标系. 这里先记录一下绝对坐标系和相对坐标系 当你构建场景的 ...

  5. JAVA 拍照 exif GPS_读取图片EXIF块中GPS信息,转换为高德地图API坐标

    最近两天做了一个需求,从APP端上传的照片信息里面读取出GPS位置信息,然后翻译成可读的地点信息. 总结一下,分为三步: (1)提取图片中的GPS信息,使用到了metadata-extractor; ...

  6. OpenGL如何将像素点的窗口空间坐标转换为世界坐标?

    坐标变换的流程 我们探讨的其实就是输入的顶点怎么变换为像素点的坐标(也就是窗口空间坐标),具体矩阵怎么求得感兴趣可以看下这篇文章. 从物体空间到世界空间的变换是通过乘以基本变换矩阵(模型矩阵Model ...

  7. osg中三维模型的位置变换

    对模型的平移和旋转等操作是我们在做三维软件开发时必然会解决的事情.但是由于基本变换默认是以世界坐标系的原点为变换中心,所以我们在进行平移旋转等操作时往往会发现其并非如我们想象的那样去执行.举个简单的例 ...

  8. 游戏世界三维坐标转换为屏幕坐标原理分析:三角函数转换与矩阵变换

    在3D游戏中获得游戏人物敌人坐标xyz后,并不能直接绘制到屏幕上,需要进行一系列的转换算法,目前主流的算法包括三角函数转换与矩阵变换,三角函数转换方法过时且繁琐,这里不推荐使用,本文主讲矩阵变换原理: ...

  9. cocos子节点转父节点坐标 原理浅析(局部坐标转世界坐标同理)

    在CCNode的类中,有一个得到 一个节点坐标系转换父亲坐标系的一个矩阵,节点内坐标乘以这个矩阵,就可以转换为在节点父节点中的坐标,方法名为: Mat4& Node::getNodeToPar ...

最新文章

  1. 双11大返厂,用Python打造最强告白代码,单身节脱单靠自己
  2. 主从数据库之互为主备
  3. 总体设计和登陆服务器 [游戏服务器的设计思路 转]
  4. yum使用、单用户模式、救援模式
  5. boost::geometry::model::multi_linestring用法的测试程序
  6. [转]常用数字处理算法的Verilog实现
  7. C++基本入门资料搜集
  8. Android 申请权限结果返回处理
  9. 一个实时精准触达系统的自我修养
  10. lightning接口_Lightning太赚钱?iPhone永远不会用Type-C
  11. python二分法查找程序_Python程序查找地板划分
  12. 两空间异面直线公垂线及交点坐标的求解过程
  13. ai作文批改_每日即评 | 积极适应“AI批改作文”的潮流
  14. 20191114每日一句
  15. 导入 ArcMap 文档
  16. 2011年河南省国民经济和社会发展统计公报
  17. 动力电池集成关键技术及电池测试与验证
  18. oracle周数计算方法
  19. uniapp主题切换
  20. 华为防火墙基于IP地址的带宽管理

热门文章

  1. [数据通信与网络]eNSP实验三、四
  2. rscp胎位图_肩先露的胎方位为什么只有四种?
  3. 离散数学知识点总结(详细)
  4. shiro的 认证 与 授权
  5. 视百年眼科受邀出席2021健康丝绸之路国际合作论坛
  6. C++ 牛客网做题笔记【500题总结】
  7. CSS问题:导航栏设置通栏后,放大页面背景显示不全
  8. 基于MQTT的消息发布订阅python实现
  9. Mysql 性能优化神器Explain详解
  10. mysql创建触发器遇到的问题ERROR 1415 (0A000)