OpenScene Graph程序设计 www.osgChina .org 第二章:OSG基础 第二章0SG基础 2.1 Hel loWorld 现在我们开始来学习使用OSG来编程了,在这里需要明确一个概念,就是OSG的语法是标准C++的。所以 会使用C+的初学者肯定就能够很好的使用OSG了。剩下的就是OSG中的各种库的使用了,严格来说并不 是十分的难以理解,现在我们来逐一破解这些库。 在学习各种语言的时候当中,都会有程序名为Hello World,在OsG中也不例外,在OsG中也会有了了几行 的程序来说明OSG中的一些功能和机制。 这里需要配置VS才能正确的编译与运行OSG程序,如果对配置库与头文件还不清楚,可以直接查阅第一章 的3.3节。 示例一:Hel loWor Id 在这里建立和以后的示例当中建立程序的步骤都是一致的,如果不特殊指明都按照这个方法。打开VS2005, 点击菜单:文件>新建->项目->Visual C+>WIN32控制台应用程序,项目名称设置为:Hello OSG's World, 在源文件中添加mai.cpp,现在需要添加OsG相关的库,在这里我们把所有的链接库都添加进去,以免出 什么乱子。菜单:项目->属性->配置属性->链接器->输入->在附加依赖项中输入:OpenThreadsd..lib osgd.ib osgDBd.lib osgFXd.lib osgGAd.lib osgIntrospectiond.lib osgManipulatord.lib osgParticled.lib osgShadowd.lib osgSimd.lib osgTerraind...lib osgTextd..lib osgUtild..lib osgViewerd..ib,或者到项目->属性->配置属性->链接器->命 令行下输入这些。这时需要在R版中也配置R版的库。如图2.1所示。 配置(G: Release 平台P: 活动Win32) 配置管理器(Q), 田通用属性 所有选顶L): 白配置属性 /OUT:"E:\code\osg\1.Hello OSG's World\Release\1.Hello OSG's World.exe" 常规 /INCREMENTAL:NO /NOLOGO/MANIFEST/MANIFESTFILE:"Release\1.Hello OSG's 调试 World.exe.intermediate.manifest"/DEBUG /PDB:"e:\code\osg\1.Hello OSG's 田C/C++ World\release\1.Hello OSG's World.pdb"/SUBSYSTEM:CONSOLE/OPT:REF 日链接器 /OPT:ICF/LTCG /MACHINE:X86/ERRORREPORT:PROMPT kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.ib ole32.lib oleaut32.lib 一常规 uuid.lib odbc32.lib odbccp32.lib 输入 清单文件 调试 一系统 优化 嵌入的IDL 高级 命令行 附加选顶(D): ⊕清单工具 OpenThreads.lib osg.lib osgDB.lib osgFX.lib osgGA.lib osgIntrospection.lib 由XML文档生成器 osgManipulator.lib osgParticle.lib osgShadow.lib osgSim.lib osgTerrain.lib 中削监信自 osgText.lib osgUtil.lib osgViewer.lib 23
OpenSceneGraph 程序设计 www.osgChina.org 第二章:OSG 基础 23 第二章 OSG 基础 2.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 版的库。如图 2.1 所示
OpenScene Graph程序设计 www.osgChina.org 第二章:OsG基础 2.1添加库示意图 这里要注意,调度版的库后面都是带个d的,比如osgd.lib,然而R版的就不带这个D。发布版的可执行文 件会比较小,因为里面不含调试信息。 在main.cpp中输入以下代码: //By FreeSouth ieysx@163.com www.osgChina.org 2008 6 11 1.#indude <osgDB/ReadFile> 2.#indude <osgViewer/Viewer> 3.void main() 4.{ 5. osgViewer::Vie wer viewer; 6. vie wer.setSceneDa ta(os gDB::read NodeFile("glider.os g")); 1 vie wer.realize(); 8. viewer.run(); 9.} CTRL十F5编译执行后可以得到如图2.2所示结果: 图2.2 Hello World运行结果 老实来讲,说个私事,我非常喜欢这个小飞机。可能由于Don Burns和Robert Osfield都是滑翔机的爱好者, 所以这个小飞机做的特别漂亮。这让我想起Visual C+内幕的作者,也是滑翔机爱好者,最终死于飞机失事, 英年早逝。 下面首先来逐行来解释整个程序: 第1~2行:这里是包含头文件,可以打开OSG的安装目录,发现应该存在osgViewer和osgDB的文件夹, 而ReadFile和Viewer都是其中的头文件。这里要说一下,一般需要头文件与对应库和动态链接DLL就 可以编译了,不需要CPP文件,头文件是说明库文件和DL的。 第3行:主函数,这里主函数还没有任何的参数。 第45行:这里申请了一个viewer,这里要解释一下,为什么要osgViewer:Viewer,osgViewer是名字空间, 与std:的地位是一样的,关于名字间是C+防止重名的很重要的一个机制,从而使程序看起来井井有条。 这里你可以理解为申请一个观察器,该观察可以查看模型就可以了,在现实中我们也是叫Viewer的。 比如,你的Viewer写的有问题,用Viewer等等打招乎语。注意这里申请的并非一个指针,而实是一个 对象实体。 24
OpenSceneGraph 程序设计 www.osgChina.org 第二章:OSG 基础 24 2.1 添加库示意图 这里要注意,调度版的库后面都是带个 d 的,比如 osgd.lib,然而 R 版的就不带这个 D。发布版的可执行文 件会比较小,因为里面不含调试信息。 在 main.cpp 中输入以下代码: //By FreeSouth ieysx@163.com www.osgChina.org 2008 6 11 1.#include <osgDB/ReadFile> 2.#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.2 所示结果: 图 2.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 等等打招乎语。注意这里申请的并非一个指针,而实是一个 对象实体
OpenScene Graph程序设计 www.osgChina .org 第二章:OSG基础 第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键竞然也没有一点点的反应。现在我们来一点点的完善这个程序,让我们来让它看起来“正常”。 2.1.1改进Hel loWorId 现在明确一下功能需求,首先点击S键会显示帧速,那个S是小写的。点击W键会显示网格,点击L键灯 光会开启等等以前都具有的功能。等等这些功能。 示例二:添加状态 把main的代码改至如下: //By FreeSouth ieysx@163.com www.osgChina.org 2008 6 11 #indude <os gDB/ReadFile> #indude <os gViewer/Viewer> #indude <osgViewer/ViewerEventHandlers> #indude <os gGA/State SetManipula tor> void main() osgViewer::Viewer viewer; viewer.setScene Data(os gDB:readNodeFile("glider.osg")); /添加状态事件 y viewer.add EventHa ndler(new osgGA::Sta teSetManipulator(viewer.ge tCa mera()->getOrCrea teStateSet())); /窗口大小变化事件 vieweradd EventHandler(new osgVie wer::WindowSize Handler); /添加一些常用状态设置 vieweraddEventHandler(new os gViewer:Sta tsHandler); vie wer.realize(); viewer.run(); 25
OpenSceneGraph 程序设计 www.osgChina.org 第二章:OSG 基础 25 第 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 键竟然也没有一点点的反应。现在我们来一点点的完善这个程序,让我们来让它看起来“正常”。 2.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()) ); //窗口大小变化事件 2. viewer.addEventHandler(new osgViewer::WindowSizeHandler); //添加一些常用状态设置 3. viewer.addEventHandler(new osgViewer::StatsHandler); viewer.realize(); viewer.run();
OpenScene Graph程序设计 www.osgChina .org 第二章:OsG基础 现在运行一下,点击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键会显示出来帮助文档, 这里我们再把代码修改一下: 示例三:设置操作器 //ByFreeSouth ieysx@163.com wwwosgChina.ong 2008 611 #indude <os gDB/ReadFile> #indude <osgViewer/Viewer> #indude <os gViewer/ViewerEventHandlers> #indude <os gGA/Tra cballManipulator> #indude <os gGA/FlightManipulator> #indude <os gGA/Drive Manipula tor> #indude <os gGA/KeySwitch Ma trixManipulator> #indude <os gGA/State SetManipula tor> #indude <os gGA/Animation Pa th Manipulator> #indude <osgGA/TerrainManipulator> void main() osgViewer::Viewer viewer; viewer.setScene Data(osgDB::readNodeFile("glider.osg")); /添加状态事件 viewer.addEventHandler new osgGA::StateSetManipulator(viewer.getCa mera()->getOrCreateStateSet())); /窗口大小变化事件 26
OpenSceneGraph 程序设计 www.osgChina.org 第二章:OSG 基础 26 } 现在运行一下,点击 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 <os gGA/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()) ); //窗口大小变化事件
OpenSceneGraph程序设计 www.osgChina .org 第二章:OsG基础 vie wer.add EventHandler(new osgVie wer::WindowSize Handler); /添加一些常用状态设置 vie wer.addEventHandler(new osgVie wer::Sta tsHandler); /添加一些操作器 2 osg::ref_ptr<osgGA::Ke ySwitch MatrixMa nipulator>keys witch Manipulator=new osgGA::Ke ySwitch Matrix Ma nipula tor; keyswitch Ma nipulator->addMa trixMa nipula tor('1',"Tra ckball",new osgGA::TradballManipulator()); keyswitch Ma nipulator->addMa trixMa nipula tor('2',"Flight",new osgGA:FlightManipulator()); 5 keyswitch Ma nipula tor->addMatrixMa nipula tor('3',"Drive",new osgGA::Drive Manipula tor()); keyswitch Ma nipulator->addMa trixMa nipula tor('4',"Terrain",new osgGA::TerrainManipulator()) viewer.setCamera Ma nipulator(keys witchManipulator.get(); 8 /添加路径记录 vie wer.add EventHandler(new os gViewer::Record Came ra PathHandler); viewer.realize(); viewer.run(); 运行后,比之以前键下2或者3键可以发现可以换操作器了,先按Z再换过来按小写Z,发现可以记录路径 了,而且在文件夹里还为我们生成了一个路径文件:saved_animation.path真是非常非常的方便。注意我们 添加了一些头文件。下面我们来解释一下这些代码: 第12行:申请一个使用按键来换操作器的类,即:osgGA::KeySwitchMatrixManipulator,意思是这样的, 往这个类中添操作器,添的时候带个标识和快捷键,然后再把这个类添加到viewer当中,这样viewer 运行的时候就可以通过按键来换操作器了,真方便。 第3~6行:申请一些操作器,把这些操作器加入到“用按键来换操作器的类”中,这样相当于有了个总闸 来控制哪个小东西开始用。 第7行:把这个“用按键来换操作器的类”添加到viewer当中去,这样就可以用键来激活这些操作器。 第8g行:把路径记录的功能添加到viewer当中,和添加状态是一样样的。 这样我们运行开来,就会得到一个几乎正常的功能了。现在我们突然想写一个好的程序,这个程序比之上 面的功能更加强大,有哪些功能呢,比如:可以在程序运行之初来决定输入哪个模型而不是程序中定的那 样,可以用来读路径文件,那么我们就需要一个好的程序了。 2.1.2最好的Hel loWor Id 我们暂把此程序命令为最好的HelloWorld这样就可以我们控制输入哪些模型了。由于我们还没有设计菜单 什么的,所以我们只有通过命令行了,可能听说过main里面是有参数值的,这些参数值就可以传进程序当 中当做真的参数来使用。 首先我们必须来解释一下main里面的两个参数int main(int argc,char*arg吵:argc:为整数,表示传给main) 的命令行参数个数。argv:为符串数组。在DOS3.X版本中,argv[oj为程序运行的全路径名;对DOS3.0以下的 27
OpenSceneGraph 程序设计 www.osgChina.org 第二章:OSG 基础 27 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 当中,和添加状态是一样样的。 这样我们运行开来,就会得到一个几乎正常的功能了。现在我们突然想写一个好的程序,这个程序比之上 面的功能更加强大,有哪些功能呢,比如:可以在程序运行之初来决定输入哪个模型而不是程序中定的那 样,可以用来读路径文件,那么我们就需要一个好的程序了。 2.1.2 最好的 HelloWorld 我们暂把此程序命令为最好的 HelloWorld 这样就可以我们控制输入哪些模型了。由于我们还没有设计菜单 什么的,所以我们只有通过命令行了,可能听说过 main 里面是有参数值的,这些参数值就可以传进程序当 中当做真的参数来使用。 首先我们必须来解释一下 main 里面的两个参数 int main(int argc, char** argv): argc: 为整数,表示传给 main() 的命令行参数个数。argv: 为符串数组。在 DOS3.X 版本中, argv[0]为程序运行的全路径名;对 DOS3.0 以下的