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

OSG基础教程:Hello World

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

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

    选自《OSG程序设计教程》第二章

    1 HelloWorld

    现在我们开始来学习使用OSG来编程了,在这里需要明确一个概念,就是OSG的语法是标准C++的。所以会使用C++的初学者肯定就能够很好的使用OSG了。

    剩下的就是OSG中的各种库的使用了,严格来说并不是十分的难以理解,现在我们来逐一破解这些库。 在学习各种语言的时候当中,都会有程序名为Hello World,在OSG中也不例外,在OSG中也会有了了几行的程序来说明OSG中的一些功能和机制。 这里需要配置VS才能正确的编译与运行OSG程序,如果对配置库与头文件还不清楚,可以直接查阅第一章的3.3节。

    示例一:HelloWorld

    在这里建立和以后的示例当中建立程序的步骤都是一致的,如果不特殊指明都按照这个方法。打开VS2005,点击菜单:文件->新建->项目->Visual C++ ->WIN32控制台应用程序,项目名称设置为:Hello OSG’s World,在源文件中添加main.cpp,现在需要添加OSG相关的库,在这里我们把所有的链接库都添加进去,以免出什么乱子。

    菜单:项目->属性->配置属性->链接器->输入->在附加依赖项中输入:OpenThreadsd.lib osgd.lib osgDBd.lib osgFXd.lib osgGAd.lib osgIntrospectiond.lib osgManipulatord.lib osgParticled.lib osgShadowd.lib osgSimd.lib osgTerraind.lib osgTextd.lib osgUtild.lib osgViewerd.lib,或者到项目->属性->配置属性->链接器->命令行下输入这些。这时需要在R版中也配置R版的库。如图1所示。

OSG基础教程:Hello World
图1 添加库示意图

    这里要注意,调度版的库后面都是带个d的,比如osgd.lib,然而R版的就不带这个D。发布版的可执行文件会比较小,因为里面不含调试信息。

    在main.cpp中输入以下代码:

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

    1.#include <osgDB/ReadFile>
    #include <osgViewer/Viewer>
    3.void main()
    4.{ 5. osgViewer::Viewer viewer;
    6. viewer.setSceneData(osgDB::readNodeFile("glider.osg"));
    7. viewer.realize();
    8. viewer.run();
    9.}

    CTRL+F5编译执行后可以得到如图2所示结果:

OSG基础教程:Hello World
图2 Hello World运行结果

    老实来讲,说个私事,我非常喜欢这个小飞机。可能由于Don Burns和Robert Osfield都是滑翔机的爱好者,所以这个小飞机做的特别漂亮。这让我想起Visual C++内幕的作者,也是滑翔机爱好者,最终死于飞机失事,英年早逝。

    下面首先来逐行来解释整个程序:

    第1~2行:这里是包含头文件,可以打开OSG的安装目录,发现应该存在osgViewer和osgDB的文件夹,而ReadFile和Viewer都是其中的头文件。这里要说一下,一般需要头文件与对应库和动态链接DLL就可以编译了,不需要CPP文件,头文件是说明库文件和DLL的。

    第3行:主函数,这里主函数还没有任何的参数。

    第4~5行:这里申请了一个viewer,这里要解释一下,为什么要osgViewer::Viewer, osgViewer是名字空间,与std::的地位是一样的,关于名字间是C++防止重名的很重要的一个机制,从而使程序看起来井井有条。这里你可以理解为申请一个观察器,该观察可以查看模型就可以了,在现实中我们也是叫Viewer的。比如,你的Viewer写的有问题,用Viewer等等打招乎语。注意这里申请的并非一个指针,而实是一个对象实体。

    第6行:这里是设置观察器Viewer中的数据,换句话说,有了观察器, 得可以年模型呀,模型中要以含有路径,比如viewer.setSceneData(osgDB::readNodeFile("C://glider.osg"));表示打开C盘根目录下的该模型。其中//为转义字符,编译器会识别为/,从某种意义上讲,我个人从来不把文件夹设置为中文名,OSG对中文支持的可不是很好。所以最好不用要中文路径名,还有就是有空格的路径名,最好也不用,不要让这些无所谓的东西干扰你。

    第7行:这个语句表达的意思非常多,事实上可以定位到Viewer.cpp的第377行,会发现里面的操作非常多,可以理解为这是在渲染前的最后一步,会检查和设置图形上下文,屏幕啊什么的,会让你以前的设置,对Viewer的设置都生效。

    第8行:这一句的意思就是渲染了,如果要解释它的意思的话,可以用下面的几个语句来替代:while(!viewer.done()){viewer.frame();}.意思也就是说,只要viewer没有结束,那么就绘制它的每一个帧[frame]。

    这个程序有一些问题,经如怎么不像我们知道那样,键下小S键会显示帧速,再点一下下会显示每一帧各部分所占用的时间,怎么不像我们以前看的那样点下W键显示网格,再点一下会显示点集,最让人无法容忍的是点F键竟然也没有一点点的反应。现在我们来一点点的完善这个程序,让我们来让它看起来“正常”。

    1.1 改进HelloWorld

    现在明确一下功能需求,首先点击S键会显示帧速,那个S是小写的。点击W键会显示网格,点击L键灯光会开启等等以前都具有的功能。等等这些功能。

    示例二:添加状态

    把main的代码改至如下:

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

    #include <osgDB/ReadFile>
    #include <osgViewer/Viewer>
    #include <osgViewer/ViewerEventHandlers>
    #include <osgGA/StateSetManipulator>

    void main()
    { osgViewer::Viewer viewer; viewer.setSceneData(osgDB::readNodeFile("glider.osg")); //添加状态事件

    1. viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) ); //窗口大小变化事件
     viewer.addEventHandler(new osgViewer::WindowSizeHandler); //添加一些常用状态设置
    3. viewer.addEventHandler(new osgViewer::StatsHandler); viewer.realize(); viewer.run();
    }

    现在运行一下,点击S时会显示帧速,而且点击W时会显示网格,点F时会从大化与最小化之间来回的变化,点击L键时会显灯光。解释一下这几行。

    注意,添加了一些头文件。代码中添加的部分添另的有行号:

    第1行:从函数的意思上来看是添加一个事件句柄,可以理解为添加一个响应,鼠标或是键盘的,这个响应可以看做响应键盘或是鼠标事件,故函数名字叫:addEventHandler[添加事件句柄]。这个事件是库中自己写好的,叫做状态设置(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet())),其中我们点击L键时有操作器的味道,所以叫状态设置操作器。众所周知,灯光是状态控制的,所以这句话可以控制L键开启与关闭灯光,至于为什么定位在L键上,怎么搞的,可以查看StateSetManipulator源码,我们以后也会写自己的操作器。发现默认的灯光比加强的L灯光好看一些,这是这个模型,大多数情况下都不是这样的。

    第2行:添加的是窗口大小改变的句柄,这里响应的是F键,与上面的原理是差不多。在早期版本中,他们是在一起的,至1.20以后就把他们分开了。

    第3行:添加常用的状态操作,这里会响应S键,W键等等,原理与上述两个是一样的。 上面讲述了如何在一个viewer当中添加状态,但是显然这个操作器还是不那么如人意,我们需要多一些操作器,而且在早期的版本当中,会有点击Z记录路径再播放什么的,而且点击H键会显示出来帮助文档,这里我们再把代码修改一下:

    示例三:设置操作器

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

     #include <osgDB/ReadFile>
     #include <osgViewer/Viewer>
     #include <osgViewer/ViewerEventHandlers>
     #include <osgGA/TrackballManipulator>
     #include <osgGA/FlightManipulator>
     #include <osgGA/DriveManipulator>
     #include <osgGA/KeySwitchMatrixManipulator>
     #include <osgGA/StateSetManipulator>
     #include <osgGA/AnimationPathManipulator>
     #include <osgGA/TerrainManipulator>
   
     void main()
     {
     osgViewer::Viewer viewer; viewer.setSceneData(osgDB::readNodeFile("glider.osg")); //添加状态事件
     viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) ); //窗口大小变化事件
     viewer.addEventHandler(new osgViewer::WindowSizeHandler); //添加一些常用状态设置
     viewer.addEventHandler(new osgViewer::StatsHandler); //添加一些操作器
    1 {
    2 osg::ref_ptr<osgGA::KeySwitchMatrixManipulator> keyswitchManipulator = new osgGA::KeySwitchMatrixManipulator;
    3 keyswitchManipulator->addMatrixManipulator( '1', "Trackball", new osgGA::TrackballManipulator() );
    4 keyswitchManipulator->addMatrixManipulator( '2', "Flight", new osgGA::FlightManipulator() );
    5 keyswitchManipulator->addMatrixManipulator( '3', "Drive", new osgGA::DriveManipulator() );
    6 keyswitchManipulator->addMatrixManipulator( '4', "Terrain", new osgGA::TerrainManipulator() );
    7 viewer.setCameraManipulator( keyswitchManipulator.get() );
    8 } //添加路径记录
    9 viewer.addEventHandler(new osgViewer::RecordCameraPathHandler); viewer.realize(); viewer.run(); }

    运行后,比之以前键下2或者3键可以发现可以换操作器了,先按Z再换过来按小写Z,发现可以记录路径了,而且在文件夹里还为我们生成了一个路径文件:saved_animation.path真是非常非常的方便。注意我们添加了一些头文件。下面我们来解释一下这些代码:

    第1~2行:申请一个使用按键来换操作器的类,即:osgGA::KeySwitchMatrixManipulator,意思是这样的,往这个类中添操作器,添的时候带个标识和快捷键,然后再把这个类添加到viewer当中,这样viewer运行的时候就可以通过按键来换操作器了,真方便。

    第3~6行:申请一些操作器,把这些操作器加入到“用按键来换操作器的类”中,这样相当于有了个总闸来控制哪个小东西开始用。

    第7行:把这个“用按键来换操作器的类”添加到viewer当中去,这样就可以用键来激活这些操作器。

    第8~9行:把路径记录的功能添加到viewer当中,和添加状态是一样样的。

    这样我们运行开来,就会得到一个几乎正常的功能了。现在我们突然想写一个好的程序,这个程序比之上面的功能更加强大,有哪些功能呢,比如:可以在程序运行之初来决定输入哪个模型而不是程序中定的那样,可以用来读路径文件,那么我们就需要一个好的程序了。

    1.2 最好的HelloWorld

    我们暂把此程序命令为最好的HelloWorld这样就可以我们控制输入哪些模型了。由于我们还没有设计菜单什么的,所以我们只有通过命令行了,可能听说过main里面是有参数值的,这些参数值就可以传进程序当中当做真的参数来使用。

    首先我们必须来解释一下main里面的两个参数int main(int argc, char** argv): argc: 为整数,表示传给main()的命令行参数个数。argv: 为符串数组。在DOS3.X版本中, argv[0]为程序运行的全路径名;对DOS3.0 以下的版本,argv[0]为空串("")。

    argv[1]为在DOS命令行中执行程序名后的第一个字符串; argv[2]为执行程序名后的第二个字符串; ... argv[argc]为NULL。

    因此我们完全可以通过读取参数列表到OSG程序当中,来利用参数列表来寻找用户传入的模型,在OSG当中,有一个类名为osg::ArgumentParser ,该类是专 门用来管理main中的一些参数的,里面有一些方法来提取和控制main里面的参数,在本章后会有类方法说明列表。下面我们来看一下最终可以通过参数来读取模型什么的代码:

    示例四:最好的HelloWorld

    //by FreeSouth www.osgChina.org ieysx@163.com

      #include <osgDB/ReadFile>
      #include <osgUtil/Optimizer>
      #include <osg/CoordinateSystemNode>
      #include <osg/Switch>
      #include <osgText/Text>
      #include <osgViewer/Viewer>
      #include <osgViewer/ViewerEventHandlers>
      #include <osgGA/TrackballManipulator>
      #include <osgGA/FlightManipulator>
      #include <osgGA/DriveManipulator>
      #include <osgGA/KeySwitchMatrixManipulator>
      #include <osgGA/StateSetManipulator>
      #include <osgGA/AnimationPathManipulator>
      #include <osgGA/TerrainManipulator>
      #include <iostream>

      int main(int argc, char** argv) {
    1. osg::ArgumentParser arguments(&argc,argv);
    2. arguments.getApplicationUsage()->setApplicationName(arguments.getApplicationName());
    3. arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" is the standard OpenSceneGraph
    4.example which loads and visualises 3d models.");
    5. arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] filename ...");
    6. arguments.getApplicationUsage()->addCommandLineOption("--image <filename>","Load an image and render it on a quad");
    7. arguments.getApplicationUsage()->addCommandLineOption("--dem <filename>","Load an image/DEM and render it on a HeightField");
    8. arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display command line parameters");
    9. arguments.getApplicationUsage()->addCommandLineOption("--help-env","Display environmental variables available");
    10. arguments.getApplicationUsage()->addCommandLineOption("--help-keys","Display keyboard & mouse bindings available");
    11. arguments.getApplicationUsage()->addCommandLineOption("--help-all","Display all command line, env vars and keyboard & mouse bindings.");
    1 arguments.getApplicationUsage()->addCommandLineOption("--SingleThreaded","Select SingleThreaded threading model"Select SingleThreaded threading model40. char keyForAnimationPath = '5'; for viewer.");
    13. arguments.getApplicationUsage()->addCommandLineOption("--CullDrawThreadPerContext","Select CullDrawThreadPerContext threading model for viewer.");
    14. arguments.getApplicationUsage()->addCommandLineOption("--DrawThreadPerContext","Select DrawThreadPerContext threading model for viewer.");
    15. arguments.getApplicationUsage()->addCommandLineOption("--CullThreadPerCameraDrawThreadPerContext","Select CullThreadPerCameraDrawThreadPerContext threading model for viewer.");
    16. bool helpAll = arguments.read("--help-all");
    17. unsigned int helpType = ((helpAll || arguments.read("-h") || arguments.read("--help"))? osg::ApplicationUsage::COMMAND_LINE_OPTION : 0 ) | ((helpAll || arguments.read("--help-env"))? osg::ApplicationUsage::ENVIRONMENTAL_VARIABLE : 0 ) | ((helpAll || arguments.read("--help-keys"))? osg::ApplicationUsage::KEYBOARD_MOUSE_BINDING : 0 );
    18. if (helpType) { 19. arguments.getApplicationUsage()->write(std::cout, helpType);
    20. return 1; }
    21. osgViewer::Viewer viewer(arguments);
    2 if (arguments.errors())
    23. {
    24. arguments.writeErrorMessages(std::cout);
    25. return 1;
    26. }
    27. if (arguments.argc()<=1)
    28. {
    29. arguments.getApplicationUsage()->write(std::cout,osg::ApplicationUsage::COMMAND_LINE_OPTION);
    30. return 1;
    31. }
    3
    33. {
    34. osg::ref_ptr<osgGA::KeySwitchMatrixManipulator> keyswitchManipulator = new osgGA::KeySwitchMatrixManipulator;
    35. keyswitchManipulator->addMatrixManipulator( '1', "Trackball", new osgGA::TrackballManipulator() );
    36. keyswitchManipulator->addMatrixManipulator( '2', "Flight", new osgGA::FlightManipulator() );
    37. keyswitchManipulator->addMatrixManipulator( '3', "Drive", new osgGA::DriveManipulator() );
    38. keyswitchManipulator->addMatrixManipulator( '4', "Terrain", new osgGA::TerrainManipulator() );
    39. std::string pathfile;
    40. char keyForAnimationPath = '5';
    41. while (arguments.read("-p",pathfile))
    4 {
    43. osgGA::AnimationPathManipulator* apm = new osgGA::AnimationPathManipulator(pathfile);
    44. if (apm || !apm->valid())
   
    45. {
    46. unsigned int num = keyswitchManipulator->getNumMatrixManipulators();
    47. keyswitchManipulator->addMatrixManipulator( keyForAnimationPath, "Path", apm );
    48. keyswitchManipulator->selectMatrixManipulator(num); 49. ++keyForAnimationPath;
    50. }
    51 }
    52 viewer.setCameraManipulator( keyswitchManipulator.get() );
    53 }
    54 viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) );
    55 viewer.addEventHandler(new osgViewer::ThreadingHandler);
    56 viewer.addEventHandler(new osgViewer::WindowSizeHandler);
    57 viewer.addEventHandler(new osgViewer::StatsHandler);
    58 viewer.addEventHandler(new osgViewer::HelpHandler(arguments.getApplicationUsage()));
    59 viewer.addEventHandler(new osgViewer::RecordCameraPathHandler);
    60 osg::ref_ptr<osg::Node> loadedModel = osgDB::readNodeFiles(arguments);
    61 if (!loadedModel)
    62 {
    63 std::cout << arguments.getApplicationName() <<": No data loaded" << std::endl;
    64 return 1;
    65 }
    66 arguments.reportRemainingOptionsAsUnrecognized();
    67 if (arguments.errors())
    68 {
    69 arguments.writeErrorMessages(std::cout);
    70 return 1;
    71 }
    72 osgUtil::Optimizer optimizer;
    73 optimizer.optimize(loadedModel.get());
    74 viewer.setSceneData( loadedModel.get() );
    75 viewer.realize();
    76 viewer.run(); }

    这回不得不费时使劲解释一下里面的代码了,他运行后定位到他的文件夹,在命令行中输入:4.最好的HelloWorld.exe Cessna.osg这样它就可以读取你指定的cessna模型。

    代码解释如下:

    第1行:申请一个ArgumentParser类,它是总的来负责MAIN里的参数的,它可以判断里面的参数是否合法,也可以读取里面的参数给OSG程序来用。也可以写一些帮助信息到程序里显示出来给客户来看。

    第2行:设置应用程序的名称,一般而言程序的名称是你的项目名字。因为在main的第一个命令行中,是程序的路径全名,比如会是”E:\OSG 大全\第二章OSG基础\4.最好的HelloWorld\debug\4.最好的HelloWorld.exe“这样ArgumentParser就可以从这个路当中获得程序的名称,然后传到ArgumentParser当中去。

    第3~4行:设置一下对这个程序的描述,描述的内容是后面的字串。

    第5行:命令行的格式说明,注意这里只是说明,并没有实际意义,即程序后[arguments.getApplicationName()]加操作[+[options]]加文件名[filename ..."]。

    第6~15行:对osgviewer的一些命令行功能进行一些说明,比如-image filename为展示一张图片,下面雷同,它主要的功能是查看模型。

    第16~20行:为输出帮助信息,里面有帮助信息的类型,其中—help-all为最全的帮助信息,但是显然没有必要查看这么全,因为行数太多翻着忒累。

    第21行:申请一个viewer但是这时可以使用类对象arguments做为参数来读取main中输入的参数。

    第22~26行:用来判断参数是否合法,比如你乱输一气,那就是不合法,程序会直接结束并返回。

    第27~31行:当程序的命令行参数只有一个时,会输出一些命令提示。只有一个代表用户没有输入模型文件,因为这一个命令行参数是该程序的全路径名,所以如果只有一个,代表用户并未输入模型,程序无法演示。会输出可执行命令行,然后退出。

    第33~38行:这个非常熟悉了,是将要添加的操作器。

    第39~51行:这里的功能是:看是否输入了路径文件,如果输入路径文件就按路径文件走。里面具体类的用法,会在别章讲到。

    第54~59行:添加一些事件响应,在前面有所涉及。

    第60~65行:从参数中读取模型,以前我们是直接读取的模型,这一次我们会从外部参数中来读。如果找不到模型就会输出错误信息,然后程序退出。

    第66~71行:报到无法识别的错误参数。如果有错,这些错将会被报告出来,且程序退出。

    第72~73行:优化读到的模型,为什么模型需要优化呢,主要是关于模型状态的优化,OSG对模型的状态渲染进行了重组,以便达到最佳的性能与最少的GPU损耗。

    第74~76行:渲染最终的模型。

    到这里来讲,读者会对OSG程序有了一个大概的了解,可能不会太深,但是所谓熟能生巧,多敲一些代码,慢慢的进去了。其实前面的程序在osg的源代码中就有,就是Application中的osgViewer程序,osgViewer程序功能非常强大,用的也非常非常多,我们平时都用它来查看一些模型。

    在这里我需要讲一下关于osgViewer的异常,有些时候读取模型的时候可能会很慢,这可能是模型的原因。有些时候没有纹理,因为模型的启动文件有时候与纹理是分开存放的,可能会找不到纹理,这可能是因为纹理的路径错误了。 在开始->运行->cmd输入osgViewer glider.osg也会得到和上面一样的结果,输出一个小飞机。 

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