我的订单|我的收藏|我的商城|帮助中心|返回首页
虚拟现实新闻>VR>行业资讯>培训教程

OSG基础教程:读取模型

文章来源:第三维度 作者: 发布时间:2012年04月02日 点击数: 字号:

    来源:第三维度
    作者:FreeSouth[杨石兴]编著

    选自《OSG程序设计教程》第三章第1节  

    一、基本操作

    1: 添加模型

    在OSG当中模型是使用osg::Group和osg::Node来装载在一起的,比如同时需要加入两个模型,模型A了模型B,AB各自是一个NODE,那么可以使用以下语句来做到,首先使用一个Group,然后Group ->addChild(A),同样,之后要Group->addChild(B)。然后再把Group添加到viewer当中就可以了。

    如图1所示AB之间的关系。在这里要申明的是NODE是Group的父类,在类中都有相应的方法可以转到对方,故Node与Group是通用的,Node也可以被当作Group来用。

OSG基础教程:读取模型
图3.1 AB都加入到Group当中

    下面来看一下具体代码:

    示例五:添加模型 ,功能:在场景中显示多个模型。

    //By FreeSouth ieysx@163.com www.osgChina.org 2008 6 13

    1.#include <osgDB/ReadFile>
    2.#include <osgViewer/Viewer>
    3.#include <osg/Node>
    4.void main()
    5.{
    6. osgViewer::Viewer viewer;
    7. osg::Group * root = new osg::Group() ;
    8. root ->addChild(osgDB::readNodeFile("glider.osg")) ;
    9. root ->addChild(osgDB::readNodeFile("osgcool.osg")) ;
    10. viewer.setSceneData(root);
    11. viewer.realize();
    12. viewer.run();
    13}

    如果需要添加类似点下S键会显示帧速L键灯光开启这样的功能,可以参照示例二,添加操作器可以参照示例三。

    下面来解释一下这些代码:

    第1~3行:添加头文件,其中需要添加node头文件,因为group是node的派生类,头文件可不加,即便加了也不会重复包含。

    第6行:申请一个viewer 第7行:申请一个root结点,即根结点,该结点下会组织所有的场景结点。

    第8~9行:在根结点下加入两亿node,一个是osgcool.osg 另一个是glider.osg,默认时两结点是加在场景的中间。

    第10行:设置场景数据,把root的数据传递到场景当中去。

    第11~13行:程序运行至结束。 这里来提取一下重要的几行代码:
 
    7. osg::Group * root = new osg::Group() ;
    8. root ->addChild(osgDB::readNodeFile("glider.osg")) ;
    9. root ->addChild(osgDB::readNodeFile("osgcool.osg")) ;

    如果想要添加别的模型,还可以root ->addChild,但是,如果想在飞机上面添加一些什么,就需要把飞机当成组结点,然后再飞机结点上再添加一些东西。可以这样。

    osg::Group* root = new osg::Group() ;
    osg::Node *glider = osgDB::ReadNodeFile(“glider.osg”) ;
    glider ->asGroup() ->addChild(osgDB::ReadNodeFile(“xxx.osg””)) ;
    root ->addChild(glider) ; root ->addChild(osgDB::readNodeFile(“osgcool.osg”)) ;

    运行效果如图2所示:

OSG基础教程:读取模型
图 2 示例五运行结果

    2:删除结点

    如果我们不需要某个结点了,比如图3.2我们看那个小飞机很不爽,我们想把它从场景中删除掉。不知道出于某种目的,反正现在要删除掉,可能是开始想看见它现在不想看见它了。可以通过removeChild方法,删除多个孩子也可以通过removeChildren方法,里面的参数有些需要索引值,有些需要结点本身的指针,读者可以自己尝试。

    这里要提醒大家的是,如果要删除一个结点,那么该结点下的所有结点都会被删除。如果一个结点被加入到一个组中两次,那么这两次是分别存在的,删除一次还有另一次。 删除操作不能说不是个危险的操作,有些时候,尤其在有移动结点等等混在一起时,删除操作有时候会发生一些比较奇怪的现象。

    在内存映象当中,如果一个模型被读取一次,而用了多次,那么所占用的空间是不会改变的。

    3:隐藏模型

    如果说我们只是想让模型暂是的消失,我想它出来它还要出来,如果删除则它会消失在内存中,再调出来会花费很长的时间,这样就得不偿失了,于是我们想起了用隐藏的方法。

    隐藏模型其实模型仍在渲染当中,因此损耗并未减少,只不过隐藏了而已,隐藏的确不是个什么好操作,但是有时候对小模型确实也很实用。node ->setNodeMask可以设置隐藏与显示。

    示例六:隐藏模型

    程序功能:隐藏指定模型 代码:

    //By FreeSouth ieysx@163.com www.osgChina.org 2008 6 13

    1.#include <osgDB/ReadFile>
    2.#include <osgViewer/Viewer>
    3.#include <osg/Node>
    4.void main()
    5.{
    6. osgViewer::Viewer viewer;
    7. osg::Group * root = new osg::Group() ;
    8. osg::Node* osgcool = osgDB::readNodeFile("osgcool.osg") ;
    9. root ->addChild(osgcool) ;
    10. root ->addChild(osgDB::readNodeFile("glider.osg")) ;
    11. osgcool ->setNodeMask(0x0) ;
    12. viewer.setSceneData(root);
    13. viewer.realize();
    14. viewer.run();
    15}

    程序第11行用来控制osgcool的隐藏。其中osgcool ->setNodeMask(0x0) ;代表隐藏。osgcool ->setNodeMask(1)代表显示。如何来判断该模型确实在场景当中而且被隐藏了呢。在运行程序时按空格键时会回到中心点,该中心点是面向包围球的圆心,如果不存在osgcool,那么按空格会把飞机置于场景中映,而有了osgcool飞机会如图3.2一样,被放置左边,因为有osgcool事实存在,只是没有显示。 程序运行如图3.3所示:足以证明模型确实是被隐藏了。

OSG基础教程:读取模型
图3.3事实上存在osgcool 在图的中央。

    4:结点开关

这时不禁有人要问,如何要隐掉一个结点,它还在内存当中消失,而且想要的时候它才能出来。因为可能很久才要用到它,所以不想让它一直停留在内存当中,OSG中提供了相应方法,叫做开关。 在OSG当中,专门有一个类来负责打开与关闭结点,该类名为osg::Switch,里面有相应的方法来控制它所管理的结点的打开与关闭。

    示例七:结点开关

    功能:打开或关闭一个/些结点,而在关闭时这些结点占用的内存会被释放掉。 代码:

    1.//By FreeSouth ieysx@163.com www.osgChina.org 2008 6 13
    2.#include <osgDB/ReadFile>
    3.#include <osgViewer/Viewer>
    4.#include <osg/Node>
    5.#include <osg/Switch>
    6.void main()
    7.{
    8. osgViewer::Viewer viewer;
    9. osg::Group * root = new osg::Group() ;
    10. osg::Switch *sw = new osg::Switch() ;
    11.
    12. osg::Node* osgcool = osgDB::readNodeFile("osgcool.osg") ;
    13. sw ->addChild(osgcool,false) ;
    14. sw ->addChild(osgDB::readNodeFile("glider.osg")) ;
    15 root ->addChild(sw) ;
    16.
    17. viewer.setSceneData(root);
    18. viewer.realize();
    19. viewer.run();
    20.}
   
    第10行:申请一个开关

    第13行:往开关中添加osgcool但是关掉它,第二个参数为false

    第14~15行:把glider.osg添加到开关,并把开关添加到root中 运行结果如图3.4,从图中可以看出osgcool根本不存在,注意与图3.3的区别:

OSG基础教程:读取模型
图3.4 osgcool被关掉了,不存在内存中。

    二、超级指针

    关于超级指针的问题由来已久,用的颇广。在OSG中问题是这样提出来的,有这样一个场景,有一辆卡车,卡车上有一个箱子。如果说卡车在运行过程中,箱子突然掉了,这样就知道该把箱子的结点删除。那么如果说还有一个箱子,在地上放着,从感觉上来讲他们应该用同一块内存。

    卡车上的箱子虽然掉了,但是地上的箱子还在,所以箱子所占的资源不能被粗鲁的释放掉。 这样就引用了超级指针的机制,其实就是引用一个计数器,这个计数器会计算这个箱子被引用的次数,被别人引用一次这个计数器增加一,别人不用一次,即:释放一次,则计数器减一。当减至0时,内存放掉不用。

    现在我们再来看一个通俗的类:

    class ThingWrapper {

       public: ThingWrapper()  { handle_ = AllocateThing(); }

      ~ThingWrapper()     { DeallocateThing (handle_); }

      ThingHandle& get()      { return handle_; }

     private: ThingHandle handle_;

    };

    首先在类的构造函数当中,分配了资源,即handle_ = AllocateThing(),然后传出一个指针,该指针就是handle_,在析构函数当中又释放了该资源,这就是资源的自动管理功能。

    ThingWrapper things ;
    UserThings(things.get()) ;

    在用完之后,things管理资源自动被放掉,OSG也运用了类似巧妙的设计,在类osg::ref_ptf当中。下面我们来看使用一个Node的三种方法,对比一下:

    1://方法一,最好的方法,十分安全,也是OSG中最常用的方法,多少版本它都没变

    osg::ref_ptr<osg::Node> aNode (new osg::Node());
    group->addChild (aNode.get());

    2://方法二,也是非常好的方法,有时候不适用,但也十分安全

    group->addChild (new osg:: Node ());

    3://方法三,非常危险,但是令许多人无故铤而走险的方法

    osg:: Node* anotherNode = new
    osg:: Node (); group->addChild (anotherNode);

    方法一:在new::Node()时申请了一个Node的资源,这时在堆内引用该Node的计算器会被置1。在group ->addChild(aNode.get())时又引用了一次,会再加1。在这两次引用都结束时,Node的资源就会被释放。

    方法二:这个方法也是很实用的,但是无法引出Node的指针,也许在别处可以用到,事实上会经常用到。如果已经这样做了,得到Node指针也不是不可以的,可以使用NodeVisitor来得到Node的指针,也可以使用findChild方法来做这件事。

    方法三:这个应该是最常用,但是最烂的方法了,原因在于如果在osg::Node*antherode = new osg::Node()之后发生了错误,抛出了异常,谁来释放Node所占用的资源呢。而这个异常在后面被捕获,程序正常的走下去,而内存却没有被正常的放掉。

    通常说,我们建议一直使用osg::ref_ptf,今天论坛上有位朋友问,官方例子也没有见用啊,在这里我要解释一下。有几个原因,首先官方的例子代码都短,没有复杂的交互与场景的换入换入,物体的显示与隐藏等等。其二,如果代码在分配之后有抛出异常的代码是需要超级指针的,而那里面没有那些代码。在有大量交互的时候以及场景变换时,建议使用超级指针。 从下面的代码开始我们就开始使用osg::ref_ptf了。

  • 暂无资料
  • 暂无资料
  • 暂无资料
  • 暂无资料
  • 暂无资料