锐's profile锐的共享空间PhotosBlogListsMore Tools Help

Blog


    November 23

    [转载]西单的萨玛阿果

    帮一个朋友转载的,呵呵。我可是圡人,自己还没怎么去过这些地方~~

     

    女人喜欢珠宝,因为珠宝是五光十色,明晃晃亮晶晶的。

    可见女人是天生对色彩敏感的动物。就如我,或见到姹紫嫣红开遍的花草树木,或见到色彩斑斓的糖球甜品,总忍不住要激动兴奋起来。

    我对名贵的珠宝是不感兴趣的。名贵的珠宝仿佛傲气的上等人,需小心地供养,需众目的聚焦,需众口的夸赞。更何况,一介布衣的我是买不起这样名贵的珠宝的,也从未有过哪位男子馈赠予我,或许,我本就不是对珠宝恋慕的女人吧。

    然而也未必。

    在西单明珠熙熙攘攘的人群中第一次留意到萨玛阿果的小店,当我进入到小店里,就被挂满四面墙壁的民族饰品给惊呆了!我觉得自己仿佛置身于阿里巴巴的宝库中,精美绝伦的绣衣首饰,变幻莫测的手工艺品,把这不足三平米的小店填得不剩一点空间。

    我很是喜欢民族饰品的,从新疆到西藏,从四川到海南,从印度到尼泊尔,从泰国到墨西哥,从日本到希腊,来自全国乃至世界各地的服装首饰,只要价格能够承受得起就不遗余力地收集着,但凡有名的民族商店也非得亲自参观不可。但像萨玛阿果这样的小店,品种之丰富,品相之精良,至少在北京地区是不多见的。

    萨玛阿果的店主人,一位达斡尔族的大哥,全名是多吉扎·萨玛。

    与萨玛熟识之后,有一日,我笑着对他说:大哥,你知道么?起初我来过小店几次,总是转转,也不问价,然后走人。

    萨玛大哥不解,问:为什么呢?

    呵呵,我答道,因为那时觉得你家的饰品太美丽了,我以为是需要花高价才能买得的,所以没有勇气问你。

    萨玛大哥哈哈大笑:现在你是知道了。

    我一边点头,又一边摇头,叹息说道:大哥,你做人也太厚道太豪爽了吧,那么美丽的手工艺品全被你几十块钱几百块钱就卖出去了,我都替那些宝贝不值。在我看来,莫说一件宝贝不止成百上千的价钱,其中的艺术价值更是无法估量的,我是你的话,出多少钱都舍不得卖呢。

    萨玛大哥笑得更加爽朗了:嗨,我这不是照顾那些学生顾客嘛!他们喜欢这些宝贝,可是又没钱买,那就便宜拿去呗。

    我不同意:胡说~大哥你谁都卖得便宜,而且还以旧换新,买回去的宝贝看着不喜欢还可以随时拿回来换,哪有你这样做生意的老板呀。

    萨玛大哥稍稍收了点笑容,认真地对我说:就算挣不了钱,只要大家喜欢我家的宝贝,他们高兴我也高兴。

    哎哎哎,这个大哥做的不是生意而是人呀。

    我常常去萨玛大哥的小店做客,工作日逛西单明珠的人很少,挑这个时间去,萨玛大哥便得闲把他店里的宝贝翻出来,让我一件件地仔细欣赏。次数多了,我发现了另一个现象,精美的不只是这店里的宝贝物件,就连来逛小店的大多数顾客都是美女,而且还是风情各异,简直令我大开眼界。呵呵,美人娱珠玉,果然不假啊。

    于是我开大哥的玩笑说:大哥,那些美女都是冲着你来的呢~

    这话,倒有一半是事实。若是时光倒退十年,萨玛大哥的帅气英俊绝对不输给陆毅黄晓明了。常常有影视剧组来他店里买东西,怎么就没把他相中呢?

    在萨玛大哥的店里我挑了很多宝贝,无论哪件,戴在身上,总是引来无数的目光。常有人问我你这东西在哪儿买的呀?我总笑着回答:我这宝贝,独此一件,上哪儿都买不着!

    我的第一本专著

    首先恭喜自己一下:经历了半年的努力与煎熬,几周没日没夜的辛苦码字,五天充满了新鲜风味的异国旅行,一次在百余双洋人大眼注视下的演讲,以及数月的等待之后……我的第一本专著终于在今天面世了!带着幽然的墨香味道,带着还算不斐的重量和价格,还有自己、朋友和家人的淡淡期待。

    封面 
    OpenSceneGraph三维渲染引擎设计与实践,
    王锐、钱学雷编著,639千字,
    清华大学出版社出版,2009年11月第一版,
    印数:1~4000,定价:45.00元
    ISBN:978-7-302-21303-1,CIP核字(2009)第173727号。

    更多的话,就是感谢所有帮助过、支持过我的朋友们了。嗯,虽然有些长,但是我却必须怀着感激之心,分列如下:

    感谢Don Burns和Robert Osfield,没有他们的富有创造力的努力,就没有OpenSceneGraph这个出色引擎的诞生,也就不会有本书的面世。

    感谢372名OSG引擎核心贡献者(包括笔者在内),感谢超过1600名来自全球各地的OSG开发者,以及超过2000名来自中国的OSG开发者和爱好者的不懈努力。没有你们,OSG引擎的发展就只会停滞不前。

    感谢Skew Matrix公司主席Paul Martz先生,作为全世界第一本OSG读物《OpenSceneGraph快速入门指导》的作者,同时也是OpenGL架构评估委员会(ARB)的独立撰稿人,他在本书的编写过程中为笔者提供了大量有价值的参考意见,支持笔者在SIGRGAPH 2009 OSG BOF大会上发言介绍自己的成绩,并且为本书作序。

    感谢北京四维远见信息技术有限公司的全体同仁,尤其是三维软件项目组的魏占营老师,关艳玲老师,陈学霞老师和苏玉扬老师,为笔者的写作创造了巨大的便利和一切可能的支持。

    感谢西安虹影科技有限公司(3DVRI)的朱幼虹老师,神州普惠公司的高峰老师,北京林业大学图形图像实验室,以及osgChina团队的杨石兴、肖鹏、贺思聪等朋友,为本书提供了第一手的技术资料。

    感谢浙江大学计算机辅助设计与图形学国家重点实验室的徐明亮博士,以及清华大学出版社第六事业部的熊健编辑,付出了很多努力以帮助我向公众推出这样一部“难以大卖”的专业性书籍,并且在出版相关的繁杂事宜上给予我耐心的辅导与帮助。

    感谢我的合作者钱学雷博士,在自身工作十分繁忙的情况下,依然抽出为数不多的时间协助笔者完成了本书的第1章和第13章,并对全书的格式与用语规范进行了审校和修订。

    感谢冷琴,在本书一度遇到创作瓶颈的时候,给我以巨大的支持和鼓励,并愿意静静地倾听我的满腹苦水。

    最后感谢我的父母,没有你们的支持,我无法想象自己能够完成这本内容浩繁,创作难度极大的专业著作。希望你们在这一刻能够为自己的儿子所取得的些许微薄成绩感到骄傲!

    需要购买本书的开发者朋友(如果您无意中漫步到这里的话),可以与清华大学出版社直接联络,或者向我直接发邮件或者论坛发帖咨询。

    总之,今天终于长抒了一口气,觉得之前的一切努力总算是见到了硕果。尽管在别人看来那可能是无所助益的,错误百出的,不值一提的,销量惨淡的……但那丝毫不会影响我个人继续在这个领域奋进的决心,以及从中吮吸的每一滴充满乐趣的甘泉。我做了我应该做的,以及,得到了我所期望得到的——就算只是微不足道的一点而已。也许,这一切都正如我在本书的最后一章中提到的那样:

    “让我点亮我的灯吧,”星星说,“而且永远不要争论它会不会驱散黑暗。”

    代序 章节 封底

    PS:MSN的空间以后应该基本不会再更新了,现在我的文章和胡乱涂鸦基本都集中在QQ空间上(http://user.qzone.qq.com/183081818)。欢迎大家常来常往吧~~

    February 02

    新的开始

    离开原单位已有了一段时间,中间又做外包又策划创业,乱冲乱撞,经历了不少波折;再加上个人感情的混混沌沌,算是度过了一个灰蒙蒙的本命年。今天正式和新单位签订了两年的合同,地方远了很多,待遇倒是在危机大潮中逆流涨了不少;希望不要旋即淹没在工薪阶层的焦虑和麻木中吧。^_^

    为此,制定一下自己2009年的个人计划。该努力的自然要加倍努力,该随缘的还是要静静随缘~~

    • 公司的开发计划不谈,总之既然被期以重任,就要做好系统架构的本职工作。
    • 两个开源工程的主力开发:osgNV,之前就被国外同行广泛认可的NV软件接口支持库;osgPhysics,计划加入OSG核心的物理引擎接口库,只是和法国/澳洲同行的“洲际合作”实在有些费力~~
    • OSG初学者教程《OpenSceneGraph跬步》(OSG: A Short Step)的编写,希望能将这个发展异常迅速的3D引擎及其使用过程中的方方面面继续在国内推广开来。
    • 原创小说《斩龙旗》的创作,这次血腥的战场要转到二十世纪初的拳乱和屈辱年代了:有人假装英勇,更多人假装安逸……而我就继续遨游在自己的纷繁脑海中好了。

    炎炎者灭,隆隆者绝;观雷观火,为盈为实,天收其声,地藏其热。高明之家,鬼瞰其室。攫挐者亡,默默者存;位极者宗危,自守者身全。(汉·扬雄 《解嘲赋》)开垦了荒置已久的博客之后,就用这句古语来告诫自己要更加谨慎与淡然吧。

    August 21

    OpenSceneGraph最新教程连载

    OpenSceneGraph最新原创教程《最长的一帧》已经开始在
    http://bbs.osgchina.org
    连载。标题基本上是个噱头,灵感大概来自电影《最长的一天》和《最长的一码》。对于最近热衷于《Eyeshield 21》的本人而言,后者还算是个不错的热血体育片。

    国内目前OSG教程还比较有限,除去标准入门读物OSGQSG(Paul Martz著,我和钱学雷译),以及FreeSouth那本不太容易得到的《Step Into OSG》之外,更多的是源自论坛和博客上的文章和心得。写这个连载的目的也是为了丰富一下目前各种教程的种类,毕竟现在似乎还没有哪个OSG甚至虚拟仿真的教程,尝试叙述过高级图形渲染技术的代码实现。多线程渲染,分页数据处理(DatabasePager),场景裁减(cull)技术,软件扩展性研究……这些东西都将力求在这篇拙劣的教程中体现。

    在这里发布主要是为了给版面除草,测试一下新版的Windows Live Writer,还有就是炫耀一下新置的Core Quad+GeForce 9600。

    May 22

    使用OpenSceneGraph+GLSL实现的水面效果

    首先为灾区的民众默哀……死者已沉睡于荒野,但是我们仍然前行。

    折腾了大约两天的时间,总算是调试通过了,中间曾有一次严重的死机……可能是立方图纹理没有设置好导致Shader崩溃了吧~~不过最后的效果还蛮不错?

    myOcean

    实现海洋平面的GLSL源代码来自:http://emiug.alanabram.co.uk,感觉他的代码比Bonzai公司的要简单些(后者需要水面纹理的反射/散射/法线/深度/Dudv贴图,3DVRI实现的水面效果好像也与之类似)。不过我个人还是GLSL的初学者,没资格多评论~~

    OSG方面的实现就是我的强项了,注意所需的uniform变量包括:LightPos,time,normalMap和cubeMap。后两者属于纹理采样器,在OSG中需要使用

    osg::Uniform( "normalMap", 0 )和osg::Uniform( "cubeMap", 1 )

    来指定纹理采样器对应的纹理单元,同时使用setTextureAttributeAndModes在对应的纹理单元设置正确的贴图图样。

    详细的文字介绍和源代码下载预计于本周末在bbs.osgchina.org上放出~~这里只是用以更新一下陈旧的版面。

    April 06

    从今天起~~

    要认真一段时间了。一直以来自己的努力不够,而且逐渐感到能力的不足~~嗯,所以从新年起到今天受了不少挫折也是理所当然地。
    那么从现在开始重新振作一下,努力把这几件事做好:
    · 数控行业。机会多得很,而且肯定是赖以吃饭的行当;
    · OpenSceneGraph。很有前途的事业,但自己只做了这么一点,最近却懈怠了,还要更加用功啊~~
    · 小说写作。这个肯定要写的,作为业余爱好,以及重要的抒发思想和排解心情的渠道;
    · Maya学习与设计。感觉从开始自学到现在,建模水平已经有了一定的提高,那么就继续把动漫设计作为梦想前进吧。
    通常是不会在Space里面写心情的,这次算是例外吧~~敦促自己一下。
     
    对了,还有感谢一个人,一直以来给予我的阳光般的温暖与希望,纵使这只是我自己的感受而已~~因为白天依然会到来,所以这样的感觉也不会改变。祝愿她的未来会愈发灿烂和美好,当然身体健康是第一位的。^_^
    February 19

    OpenSceneGraph中文网站发布!

    其实在大年初一就已经可以浏览了。不过今天OSG官方网站(www.openscenegraph.org)终于公布了这一消息,这才算是名正言顺吧。想想自己在过年之前没日没夜地翻译和编写了90多页的教程和资料(也就是网站的“文档”部分),这次终于能够见得天日,不禁“热泪盈眶”,嗯~~

    网站由OSGChina-Team建立和维护,地址为:
    http://www.osgchina.org

    同时还有网站论坛,OSGChina-Team的成员大都是论坛的成员,论技术实力应该在国内是数得上的了。地址为:

    http://bbs.osgchina.org

    因为我也是OSGChina Team的一员(王锐,论坛名称是array),所以以后OSG相关的技术类文章就有了归宿,不会发在这里了~~看了我的Space之后不知所云的情况想必不会再发生了吧~~^_^

    那么这里以后放些什么呢?估计用来吹牛的机率比较大。因为我的习惯一向是“报喜不报忧”,所以自己的倒霉事大概不会主动公布出来,有点小收获倒是可能放上来大吹大擂~~当然,不排除在心情好或者无聊的时候写一些小文章什么的,不过最近还是先要忙着写书的事了……

    osg_banner

    December 21

    Navy17.2- 粒子系统的保存以及读取

    如果你已经在上一章创建了一个粒子系统,你应该已经构建了一个类似图1的场景图形。粒子系统对象(Drawable)作为Geode叶节点的子对象,而Geode的父节点是根节点。其它的粒子系统元素(标准编程器,更新器,放射极对象)均作为位置变换节点的子节点存在。我们的粒子系统的结构设计可谓“通用而合理的”:上一教程中我们已经可以看到场景中坦克的周围有烟尘效果环绕。粒子系统对象并非作为位置变换节点的子节点,因此其稳定性更好。

    course17.2.1 图1

    如果我们把一个漂亮的OSG效果作为osg文件保存,那么可能会出现一个小问题。当我们使用osgDB::ReadNodeFile()读取文件时,我们将仅仅得到一个节点的指针。这个节点将把所有粒子系统相关的元素作为子节点关联。如果我们把这个节点作为变换节点的子节点加入场景中,那么粒子系统(Drawable)也会随之变换。也就是说,这个粒子系统已经不再是“通用而合理的”了。

    合理的粒子系统要求其粒子系统对象不能进行变换。因此我们有别的方法来实现这一要求。例如读取文件并获取Drawable对象的句柄,然后将这个Drawable对象从读入节点的子节点中去除,并重新添加到根节点上。但是如果你有较多数量的不同效果需要处理的话,这种方法就显得繁琐了:需要不停地取消和关联,不停地更改节点树,等等。

    将所有的粒子效果置于场景图形的同一节点之下也是有好处的。但是我们必须保证Drawable对象不能被任何变换节点所影响。要实现这一目的,我们可以针对粒子系统类对象设置一个“绝对”变换。这可能对拣选过程的算法产生不好的影响。(因为查找“绝对变换”节点是需要进行遍历的,它的所有父节点也会因此进行遍历)下面的方案也许更加可行一些:创建一个变换节点,它作为粒子系统对象的父类,将自己以上所有的变换效果均进行反转。它的实现结构如图2所示。其源代码可以在下面的地址下载:
    http://www.nps.navy.mil/cs/sullivan/osgTutorials/Download/psHelper.cpp

    course17.2.2图2

    Navy17.1 - 向场景中添加osgParticle粒子效果 (2)

    建立粒子系统的基本步骤是:创建一个粒子系统对象,一个更新器对象,以及一个粒子对象,同时设置场景。

    // 创建并初始化粒子系统。
    osgParticle::ParticleSystem *dustParticleSystem = new osgParticle::ParticleSystem;

    // 设置材质,是否放射粒子,以及是否使用光照。
    dustParticleSystem->setDefaultAttributes(dust2.rgb", false, false);

    // 由于粒子系统类继承自Drawable类,因此我们可以将其作为Geode的子节点加入场景。
    osg::Geode *geode = new osg::Geode;

    rootNode->addChild(geode);
    geode->addDrawable(dustParticleSystem);

    // 添加更新器,以实现每帧的粒子管理。
    osgParticle::ParticleSystemUpdater *dustSystemUpdater = new osgParticle::ParticleSystemUpdater;

    // 将更新器与粒子系统对象关联。
    dustSystemUpdater->addParticleSystem(dustParticleSystem);
    // 将更新器节点添加到场景中。
    rootNode->addChild(dustSystemUpdater);

    // 创建粒子对象,设置其属性并交由粒子系统使用。
    osgParticle::Particle smokeParticle;
    smokeParticle.setSizeRange(osgParticle::rangef(0.01,20.0)); // 单位:米
    smokeParticle.setLifeTime(4); // 单位:秒
    smokeParticle.setMass(0.01); // 单位:千克
    // 设置为粒子系统的缺省粒子对象。
    dustParticleSystem->setDefaultParticleTemplate(smokeParticle);

    下面的代码将使用标准放射极对象来设置粒子的一些基本参数:例如每帧创建的粒子数,新生粒子的产生位置,以及新生粒子的速度。

    // 创建标准放射极对象。(包括缺省的计数器,放置器和发射器)
    osgParticle::ModularEmitter *emitter = new osgParticle::ModularEmitter;

    // 将放射极对象与粒子系统关联。
    emitter->setParticleSystem(dustParticleSystem);

    // 获取放射极中缺省计数器的句柄,调整每帧增加的新粒子数目。
    osgParticle::RandomRateCounter *dustRate =
       static_cast<osgParticle::RandomRateCounter *>(emitter->getCounter());
    dustRate->setRateRange(5, 10); // 每秒新生成5到10个新粒子。

    // 自定义一个放置器,这里我们创建并初始化一个多段放置器。
    osgParticle::MultiSegmentPlacer* lineSegment = new osgParticle::MultiSegmentPlacer();

    // 向放置器添加顶点,也就是定义粒子产生时所处的线段位置。
    // (如果将坦克和标准放射极定义与同一位置,那么我们可以实现一种
    // 灰尘粒子从坦克模型后下方的延长线上产生的效果。)
    lineSegment->addVertex(0,0,-2);
    lineSegment->addVertex(0,-2,-2);
    lineSegment->addVertex(0,-16,0);

    // 为标准放射极设置放置器。
    emitter->setPlacer(lineSegment);

    // 自定义一个发射器,这里我们创建并初始化一个RadialShooter弧度发射器。
    osgParticle::RadialShooter* smokeShooter = new osgParticle::RadialShooter();

    // 设置发射器的属性。
    smokeShooter->setThetaRange(0.0, 3.14159/2); // 弧度值,与Z轴夹角。
    smokeShooter->setInitialSpeedRange(50,100); // 单位:米/秒

    // 为标准放射极设置发射器。
    emitter->setShooter(smokeShooter);

    现在我们将把放射极和坦克模型作为Transform变换节点的子节点添加到场景中。放射极和坦克均由变换节点决定其位置。刚才定义的放置器将会根据变换的参量安排粒子的位置。

    osg::MatrixTransform * tankTransform = new osg::MatrixTransform();
    tankTransform->setUpdateCallback( new orbit() ); // 回调函数,使节点环向运动。

    // 把放射极和坦克模型添加为变换节点的子节点。
    tankTransform->addChild(emitter);
    tankTransform->addChild(tankNode);
    rootNode->addChild(tankTransform);

    下面的代码将创建一个标准编程器ModularProgram实例,用于控制粒子在生命周期中的更新情况。标准编程器对象使用Operator计算器来实现对粒子的控制。

    // 创建标准编程器对象并与粒子系统相关联。
    osgParticle::ModularProgram *moveDustInAir = new osgParticle::ModularProgram;
    moveDustInAir->setParticleSystem(dustParticleSystem);

    // 创建计算器对象,用于模拟重力的作用,调整其参数并添加给编程器对象。
    osgParticle::AccelOperator *accelUp = new osgParticle::AccelOperator;
    accelUp->setToGravity(-1); // 设置重力加速度的放缩因子。
    moveDustInAir->addOperator(accelUp);

    // 向编程器再添加一个计算器对象,用于计算空气阻力。
    osgParticle::FluidFrictionOperator *airFriction = new osgParticle::FluidFrictionOperator;
    airFriction->setFluidToAir();

    moveDustInAir->addOperator(airFriction);

    // 最后,将编程器添加到场景中。
    rootNode->addChild(moveDustInAir);

    下面的代码就是仿真循环的内容了。

    viewer.setCameraManipulator(new osgGA::TrackballManipulator());
    viewer.setSceneData(rootNode);
    viewer.realize();

    while( !viewer.done() )
    {
       viewer.frame();
    }
    return 0;

    Navy17.1 - 向场景中添加osgParticle粒子效果 (1)

    目的:

    向场景中添加自定义的osgParticle实例,模拟坦克模型在地形上运动时产生的烟尘。

    course17.1

    -----------------------------

    概述:

    添加粒子效果可以有效提高仿真程序的外观和真实性。粒子引擎一般用于模拟烟雾,火焰,尘埃以及其他一些类似的效果。如果要向OSG场景中添加粒子效果,通常可以使用下面的一些类:

    粒子系统(osgParticle::ParticleSystem)- 维护并管理一系列粒子的生成,更新,渲染和销毁。粒子系统类继承自Drawable类。粒子的渲染控制因此与其它Drawable对象的渲染类似:控制其渲染属性StateAttribute即可。OSG提供了一个方便的函数以允许用户控制三个常用的渲染状态属性。方法setDefaultAttributes可以用于指定材质(或者指定为NULL以禁用材质),允许/禁止附加的图像融合,允许/禁止光照。

    粒子(osgParticle::Particle)- 粒子系统的基本单元。粒子类同时具有物理属性和图像属性。它的形状可以是任意的点(POINT),四边形(QUAD),四边形带(QUAD_TRIPSTRIP),六角形(HEXAGON)或者线(LINE)。每个粒子都有自己的生命周期。生命周期也就是每个粒子可以存活的秒数。(生命周期为负数的粒子可以存活无限长时间)所有的粒子都具有大小(SIZE),ALPHA值和颜色(COLOR)属性。每一组粒子都可以指定其最大和最小值。为了便于粒子生命周期的管理,粒子系统通过改变生命周期的最大和最小值来控制单个粒子的渲染。(根据已经消耗的时间,在最小和最大值之间进行线性插值)
    程序员也可以自行指定最小到最大值的插值方法。(参见osgParticle::Interpolator的代码)

    放置器(osgParticle::Placer)- 设置粒子的初始位置。用户可以使用预定义的放置器或者定义自己的放置器。已定义的放置器包括:点放置器PointPlacer(所有的粒子从同一点出生),扇面放置器SectorPlacer(所有的粒子从一个指定中心点,半径范围和角度范围的扇面出生),以及多段放置器MultiSegmentPlacer(用户指定一系列的点,粒子沿着这些点定义的线段出生)。

    发射器(osgParticle::Shooter)- 指定粒子的初始速度。RadialShooter类允许用户指定一个速度范围(米/秒)以及弧度值表示的方向。方向由两个角度指定:theta角 - 与Z轴夹角,phi角 - 与XY平面夹角。

    计数器(osgParticle::Counter)- 控制每一帧产生的粒子数。RandomRateCounter类允许用户指定每帧产生粒子的最大和最小数目。

    标准放射极(osgParticle::ModularEmitter)- 一个标准放射极包括一个计数器,一个放置器和一个发射器。它为用户控制粒子系统中多个元素提供了一个标准机制。

    粒子系统更新器(osgParticle::ParticleSystemUpdater)- 用于自动更新粒子。将其置于场景中时,它会在拣选遍历中调用所有“存活”粒子的更新方法。

    标准编程器(osgParticle::ModularProgram)- 在单个粒子的生命周期中,用户可以使用ModularProgram实例控制粒子的位置。ModularProgram需要与Operator对象组合使用。

    计算器(osgParticle::Operator)- 提供了控制粒子在其生命周期中的运动特性的方法。用户可以改变现有Operator类实例的参数,或者定义自己的Operator类。OSG提供的Operator类包括:AccelOperator(应用常加速度),AngularAccelOperator(应用常角加速度),FluidFrictionOperator(基于指定密度和粘性的流体运动进行计算),以及ForceOperator(应用常力)。

    代码:
    为了使用上面的类创建高度自定义化的粒子系统,我们可以遵循以下的步骤。(括号中的步骤是可选的,如果所建立的粒子系统比较基本,也可以忽略)

    • 创建粒子系统实例并将其添加到场景。
    • (设置粒子系统的渲染状态属性。)
    • 创建粒子对象并将其关联到粒子系统。
    • (设置粒子的参数。)
    • 创建粒子系统更新器,将其关联到粒子系统实例,并添加到场景中。
    • (创建标准放射极,以定义:计数器 - 每帧创建的粒子数,放置器 - 粒子的出生位置,发射器 - 初始速度。)
    • (将标准放射极关联到粒子系统。)
    • (创建ModularProgram标准编程器实例,以控制粒子在生命周期中的运动:首先创建并定义Operator计算器;然后添加计算器到标准编程器。)

    下面的代码将完成上述的步骤。首先,我们需要建立基本的坦克和地形模型。(稍后我们再添加坦克模型到场景中)

    osg::Group* rootNode = new osg::Group();

    osg::Node* terrainNode = new osg::Node();
    osgViewer::Viewer viewer;

    terrainNode = osgDB::readNodeFile("\\Models\\JoeDirt\\JoeDirt.flt");
    if (! terrainNode)
    {
       std::cout << "Couldn't load models, quitting." << std::endl;
       return -1;
    }
    rootNode->addChild(terrainNode);

    osg::Node* tankNode = osgDB::readNodeFile("\\Models\\T72-Tank\\T72-tank_des.flt");
    if ( ! tankNode)
    {
       std::cout << "no tank" << std::endl;
       return -1;
    }

    December 19

    Navy16 - 交集测试 (2)

    下面将获取地形模型在给定X,Y位置的高度值,并放置坦克模型。

       double tankXPosition = -10.0;
       double tankYPosition = -10.0;

       osg::LineSegment* tankLocationSegment = new osg::LineSegment();
       tankLocationSegment->set(
          osg::Vec3(tankXPosition, tankYPosition, 999) ,
          osg::Vec3(tankXPosition, tankYPosition, -999) );

       osgUtil::IntersectVisitor findTankElevationVisitor;
       findTankElevationVisitor.addLineSegment(tankLocationSegment);
       terrainNode->accept(findTankElevationVisitor);

       osgUtil::IntersectVisitor::HitList tankElevationLocatorHits;
       tankElevationLocatorHits =
          findTankElevationVisitor.getHitList(tankLocationSegment);
       osgUtil::Hit heightTestResults;
       if ( tankElevationLocatorHits.empty() )
       {
          std::cout << " couldn't place tank on terrain" << std::endl;
          return -1;
       }
       heightTestResults = tankElevationLocatorHits.front();
       osg::Vec3d terrainHeight = heightTestResults.getWorldIntersectPoint();

       tankXform->setPosition( terrainHeight );
       tankXform->setAttitude(
          osg::Quat(osg::DegreesToRadians(-45.0), osg::Vec3(0,0,1) ) );

    下面的代码将设置视口,告示牌节点,以及渲染状态。

       viewer.setCameraManipulator(new osgGA::TrackballManipulator());
       viewer.setSceneData(rootNode);
       viewer.realize();

       osg::Billboard* shrubBillBoard = new osg::Billboard();
       rootNode->addChild(shrubBillBoard);

       shrubBillBoard->setMode(osg::Billboard::AXIAL_ROT);
       shrubBillBoard->setAxis(osg::Vec3(0.0f,0.0f,1.0f));
       shrubBillBoard->setNormal(osg::Vec3(0.0f,-1.0f,0.0f));

       osg::Texture2D *ocotilloTexture = new osg::Texture2D;
       ocotilloTexture->setImage(osgDB::readImageFile("images\\ocotillo.png"));

       osg::StateSet* billBoardStateSet = new osg::StateSet;

       billBoardStateSet->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
       billBoardStateSet->setTextureAttributeAndModes(0, ocotilloTexture, osg::StateAttribute::ON );
       billBoardStateSet->setAttributeAndModes( new osg::BlendFunc, osg::StateAttribute::ON );

       osg::AlphaFunc* alphaFunction = new osg::AlphaFunc;
       alphaFunction->setFunction(osg::AlphaFunc::GEQUAL,0.05f);
       billBoardStateSet->setAttributeAndModes( alphaFunction, osg::StateAttribute::ON );

    按照上面所述的检测射线和地形模型碰撞交点的基本步骤,现在我们将向IntersectVisitor添加多个LineSegment实例。每条射线的交集测试结果(交点位置)将用于放置相应的告示牌。

       srand(time(0)); // 初始化随机数生成器。

       osgUtil::IntersectVisitor isectVisitor;
       osg::LineSegment* terrainIsect[NUMBER_O_SHRUBS];

       int randomX, randomY;

       for (int i=0; i< NUMBER_O_SHRUBS; i++ )
       {
          randomX = (rand() % 100) + 1;
          randomY = (rand() % 100) + 1;
          terrainIsect[i] = new osg::LineSegment(
             osg::Vec3(randomX, randomY, 999) ,
             osg::Vec3(randomX, randomY, -999) );
          isectVisitor.addLineSegment(terrainIsect[i]);
       }
       terrainNode->accept(isectVisitor);

       osg::Drawable* shrubDrawable[NUMBER_O_SHRUBS];

       for (int j = 0 ; j < NUMBER_O_SHRUBS; j ++)
       {
          float randomScale = ((rand() % 15) + 1 ) / 10.0;
          shrubDrawable[j] = createShrub( randomScale, billBoardStateSet);
          osgUtil::IntersectVisitor::HitList hitList = isectVisitor.getHitList(terrainIsect[j]);
          if (! hitList.empty() )
          {
             osgUtil::Hit firstHit = hitList.front();
             osg::Vec3d shrubPosition = firstHit.getWorldIntersectPoint();

             // osg::Vec3d shrubPosition =
             // isectVisitor.getHitList(terrainIsect[j]).front().getWorldIntersectPoint();

             shrubBillBoard->addDrawable( shrubDrawable[j] , shrubPosition );
          }
       }

       while( !viewer.done() )
       {
          viewer.frame();
       }
       return 0;
    }

    编程愉快!

    Navy16 - 交集测试 (1)

    目的:

    向场景中添加一系列随机放置着的灌木。使用osgUtil库的交集函数来获取正确的地形高度值,以便正确安置灌木告示牌节点。

    course16

    -----------------------------

    概述:


    如果我们希望扩展前一个教程的内容,向场景中的地形添加一些随意放置的告示牌节点,那么我们最好能够获取任意经纬(x,y)位置的地形高度值。我们可以使用osgUtil库提供的交集运算模块函数来实现这一功能。我们可能会用到的基本类描述如下:

    线段(osg::LineSegment)- 交集测试的基础是场景中的射线。线段类提供了一种定义射线的方法。它包括两个osg::Vec3实例:一个用于定义线段的起点,另一个用于定义终点。当交集测试被触发时,它将检测射线的相交情况并执行相应的操作。

    交点(osgUtil::Hit)- 这个类向程序员提供了获取交集检测的基本数据的方法。交点类包括一条射线与场景中几何体相交的状态信息。尤为重要的是,它以Vec3的形式提供了局部和世界坐标的位置以及法线数据。它的成员方法getLocalIntersectPoint,getLocalIntersectNormal,getWorldIntersectPoint和getworldIntersectNormal分别以osg::Vec3作为返回值,返回局部/世界坐标的相交点/法线数值。

    交点列表(osgUtil::IntersectVisitor::HitList)- 一条单一的线段可能与场景中的多个几何体实例(或者多次与同一个几何体)产生交集。对于每一条参与交集测试的线段,系统均会产生一个列表。这个列表包含了所有交集测试产生的Hit实例。如果没有监测到任何交集,该列表保持为空。

    交集访问器(osgUtil::IntersectVisitor)- 射线与场景中几何体的交集测试由交集访问器来创建并实现初始化。IntersectionVisitor类继承自NodeVisitor类,因此其创建和触发机制与NodeVisitor实例大致相似。访问器需要维护一个进行交集测试的线段列表。而对于其中的每一条线段,访问器都会创建一个交点列表(osgUtil::IntersectVisitor::HitList实例)。

    代码:

    为了正确获取放置灌木模型所需的地形高度值,我们需要遵循下面的基本步骤:

    • 创建一个LineSegment实例,它使用两个Vec3实例来定义交集测试所用射线的起点和终点。
    • 创建一个IntersectVisitor实例。
    • 将LineSegment实例添加到IntersectVisitor实例。
    • 初始化IntersectVisitor实例,使其从场景图形中适当的节点开始遍历。
    • 获取交集测试结果的世界坐标。

    下面的代码演示了上述步骤的两种实现方式。第一部分是将单个LineSegment添加到IntersectVisitor,以判断坦克模型的放置高度是否正确。第二部分是将与告示牌节点相关的多个线段添加到IntersectVisitor,每一个线段实例均对应一个灌木告示牌对象。针对每个LineSegment执行与其关联的交集测试之后,我们既可正确地放置告示牌的位置了。

    #include <ctime>
    #include <cstdlib>
    #include <iostream>
    #include <osg/Geometry>
    #include <osg/Texture2D>
    #include <osg/Billboard>
    #include <osg/BlendFunc>
    #include <osg/AlphaFunc>
    #include <osg/PositionAttitudeTransform>
    #include <osgUtil/IntersectVisitor>
    #include <osgDB/Registry>
    #include <osgDB/ReadFile>
    #include <osgViewer/Viewer>
    #include <osgGA/TrackballManipulator>
    #include <osgDB/FileUtils>

    #define NUMBER_O_SHRUBS 1000

    osg::Drawable* createShrub(const float & scale, osg::StateSet* bbState)
    {
       float width = 1.5f;
       float height = 3.0f;

       width *= scale;
       height *= scale;

       osg::Geometry* shrubQuad = new osg::Geometry;

       osg::Vec3Array* shrubVerts = new osg::Vec3Array(4);
       (*shrubVerts)[0] = osg::Vec3(-width/2.0f, 0, 0);
       (*shrubVerts)[1] = osg::Vec3( width/2.0f, 0, 0);
       (*shrubVerts)[2] = osg::Vec3( width/2.0f, 0, height);
       (*shrubVerts)[3] = osg::Vec3(-width/2.0f, 0, height);

       shrubQuad->setVertexArray(shrubVerts);

       osg::Vec2Array* shrubTexCoords = new osg::Vec2Array(4);
       (*shrubTexCoords)[0].set(0.0f,0.0f);
       (*shrubTexCoords)[1].set(1.0f,0.0f);
       (*shrubTexCoords)[2].set(1.0f,1.0f);
       (*shrubTexCoords)[3].set(0.0f,1.0f);
       shrubQuad->setTexCoordArray(0,shrubTexCoords);

       shrubQuad->addPrimitiveSet(new osg::DrawArrays osg::PrimitiveSet::QUADS,0,4));

       shrubQuad->setStateSet(bbState);

       return shrubQuad;
    }

    int main( int argc, char **argv )
    {
       osgViewer::Viewer viewer;

       osg::Group* rootNode = new osg::Group();
       osg::Node* tankNode = NULL;
       osg::Node* terrainNode = NULL;
       osg::PositionAttitudeTransform* tankXform =
          new osg::PositionAttitudeTransform();

       tankNode = osgDB::readNodeFile("\\Models\\T72-Tank\\T72-tank_des.flt");
       terrainNode = osgDB::readNodeFile("\\Models\\JoeDirt\\JoeDirt.flt");
       if (! (tankNode && terrainNode))
       {
          std::cout << "Couldn't load models, quitting." << std::endl;
          return -1;
       }
       rootNode->addChild(terrainNode);
       rootNode->addChild(tankXform);
       tankXform->addChild(tankNode);

    December 18

    Navy15 - 向场景中添加告示牌(Billboard)节点 (2)

    下一步,我们编写场景设置和仿真循环的执行代码。代码中包括我们刚刚编写的向告示牌添加四边形实例的函数(Billboard类继承自Geode类,因此可以向其添加Drawable实例),以及旋转轴和告示牌朝向方向的设置。

    int main( int argc, char **argv )
    {
       osgViewer::Viewer viewer;

       osg::Group* rootNode = new osg::Group();
       osg::Node* tankNode = NULL;
       osg::Node* terrainNode = NULL;
       osg::PositionAttitudeTransform* tankXform =
          new osg::PositionAttitudeTransform();

       osgDB::FilePathList pathList = osgDB::getDataFilePathList();
       pathList.push_back("C:\\Projects\\OpenSceneGraph\\OpenSceneGraph-Data\\NPSData\\Models\\T72-Tank\\");
       pathList.push_back("C:\\Projects\\OpenSceneGraph\\OpenSceneGraph-Data\\NPSData\\Models\\JoeDirt\\");
       pathList.push_back("C:\\Projects\\OpenSceneGraph\\OpenSceneGraph-Data\\NPSData\\Textures\\");
       osgDB::setDataFilePathList(pathList);

       tankNode = osgDB::readNodeFile("T72-tank_des.flt");
       terrainNode = osgDB::readNodeFile("JoeDirt.flt");
       if (! (tankNode && terrainNode))
       {
          std::cout << "Couldn't load models, quitting." << std::endl;
          return -1;
       }
       rootNode->addChild(terrainNode);
       rootNode->addChild(tankXform);
       tankXform->addChild(tankNode);

       tankXform->setPosition( osg::Vec3(10,10,8) );
       tankXform->setAttitude(
          osg::Quat(osg::DegreesToRadians(-45.0), osg::Vec3(0,0,1) ) );

       viewer.setCameraManipulator(new osgGA::TrackballManipulator());
       viewer.setSceneData(rootNode);
       viewer.realize();

    创建告示牌并设置其参数。我们希望告示牌可以环绕轴(0,0,1)旋转,并始终朝向视口。为了保证它始终面向视口,我们需要定义告示牌的法线方向为(0,-1,0)。

       osg::Billboard* shrubBillBoard = new osg::Billboard();
       rootNode->addChild(shrubBillBoard);

       shrubBillBoard->setMode(osg::Billboard::AXIAL_ROT);
       shrubBillBoard->setAxis(osg::Vec3(0.0f,0.0f,1.0f));
       shrubBillBoard->setNormal(osg::Vec3(0.0f,-1.0f,0.0f));

    下面的代码将设置四边形几何体的渲染状态。我们将使用Alpha融合算法,使四边形看起来像是一个复杂的树木的几何形状。

    osg::Texture2D *ocotilloTexture = new osg::Texture2D;
    ocotilloTexture->setImage(osgDB::readImageFile("images\\ocotillo.png"));

    osg::AlphaFunc* alphaFunc = new osg::AlphaFunc;
    alphaFunc->setFunction(osg::AlphaFunc::GEQUAL,0.05f);

    osg::StateSet* billBoardStateSet = new osg::StateSet;

    billBoardStateSet->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
    billBoardStateSet->setTextureAttributeAndModes
          (0, ocotilloTexture, osg::StateAttribute::ON );
    billBoardStateSet->setAttributeAndModes
          (new osg::BlendFunc, osg::StateAttribute::ON );
    osg::AlphaFunc* alphaFunction = new osg::AlphaFunc;
    alphaFunction->setFunction(osg::AlphaFunc::GEQUAL,0.05f);
    billBoardStateSet->setAttributeAndModes( alphaFunc, osg::StateAttribute::ON );

    下面,我们需要创建一些Drawable几何体并将其添加给告示牌节点实例。我们将使用上面定义的函数来创建几何体;并使用Billboard类重载的addDrawable方法来添加并放置这些四边形。

    // 创建不同放缩系数的几何体,并赋予它们已定义好的渲染状态。
    osg::Drawable* shrub1Drawable = createShrub( 1.0f, billBoardStateSet);
    osg::Drawable* shrub2Drawable = createShrub( 2.0f, billBoardStateSet);
    osg::Drawable* shrub3Drawable = createShrub( 1.2f, billBoardStateSet);

    // 将这些几何体添加到告示牌节点,并设置其不同位置。
    shrubBillBoard->addDrawable( shrub1Drawable , osg::Vec3(12,3,8) );
    shrubBillBoard->addDrawable( shrub2Drawable , osg::Vec3(10,18,8));
    shrubBillBoard->addDrawable( shrub3Drawable , osg::Vec3(6,10,8) )

    这之后的代码就很寻常了。

       while( !viewer.done() )
       {
          viewer.frame();
       }
       return 0;
    }

    祝你好运!

    Navy15 - 向场景中添加告示牌(Billboard)节点 (1)

    目的:

    向场景中添加告示牌节点,以模拟沙漠中灌木的效果。

    course15

    -----------------------------

    概述:

    告示牌(Billboard)节点的特性是围绕用户定义的轴(或者点)进行旋转,从而始终面向一个指定的方向。典型的告示牌可以绕Z轴正向旋转,同时面向视口方向,以实现树林的模拟。OSG发行版本的示例程序osgBillboard中通过指定旋转轴和法线(朝向方向)的方式,演示了各种模式的告示牌节点。本教程将创建多个树木样式的告示牌,它们环绕Z轴正向旋转并始终朝向视口。

    Billboard类(osg::Billboard)包括可以绕一个轴或者参考点旋转并朝向指定方向的几何体。Billboard类继承自Geode类。也就是说,继承自Drawable类(包括Geometry类)的实例可以被添加给Billboard对象。我们可以将几何体关联给告示牌节点。因为Billboard类是继承自Geode类的,我们也可以将StateSet关联给告示牌节点。

    除了继承自Geode类的方法和成员外,告示牌类还包括了用于控制其自身特性的成员和方法:告示牌是否环绕参考点或者轴;如果绕某个轴旋转的话,它应该朝向什么方向。为了控制告示牌的类型,可以使用其中的setMode(osg::Billboard::Mode)方法。其合法参数有:POINT_ROT_EYE(绕一个点旋转,相对于眼睛位置),POINT_ROT_WORLD(绕一个点旋转,相对于世界坐标),AXIAL_ROT(绕一个轴旋转)。如果指定了AXIAL_ROT模式,用户就可以使用setAxis(osg::Vec3)方法来设置告示牌绕之旋转的轴。用户还可以使用setNormal(osg::Vec3)方法定义告示牌朝向的法线。为了放置告示牌,用户还需重载osg::Geode的addGeometry方法。这个方法有两个参数:一个Drawable实例,以及一个标识位置的osg::Vec3实例。

    创建告示牌还需要最后一步工作:当几何体根据法线的设置旋转并朝向视口之后,对其进行光照的计算将导致一种怪异的结果。(光照会随着视口的改变而突变)为了创建各个方向的形象均相同的告示牌,我们需要确信关闭告示牌的光照。综上所述,向场景中添加多个告示牌的基本步骤为:

    • 创建一个告示牌实例并添加到场景;
    • 创建光照关闭的渲染状态;
    • 创建几何体(应用上面的渲染状态)并添加给告示牌节点。

    代码:

    需要以下的头文件:

    #include <osg/Geometry>
    #include <osg/Texture2D>
    #include <osg/Billboard>
    #include <osg/BlendFunc>
    #include <osg/AlphaFunc>
    #include <osgViewer/Viewer>
    #include <osgGA/TrackballManipulator>
    #include <osg/PositionAttitudeTransform>

    首先,我们编写一个简便的函数用于生成与告示牌节点相关联的几何体。此函数有两个参数:一个浮点数用于表示放缩特性,以及一个关联给几何体实例的渲染状态指针。这个函数的返回值是一个几何体指针。我们使用简单的四边形来生成几何体。关于如何指定四边形的顶点和纹理坐标,请参照前面的教程。唯一需要注意的是,在创建该几何体的时候,我们需要它相对旋转轴置中。函数的定义如下所示:

    osg::Drawable* createShrub(const float & scale, osg::StateSet* bbState)
    {
       float width = 1.5f;
       float height = 3.0f;

       width *= scale;
       height *= scale;

       osg::Geometry* shrubQuad = new osg::Geometry;

       osg::Vec3Array* shrubVerts = new osg::Vec3Array(4);
       (*shrubVerts)[0] = osg::Vec3(-width/2.0f, 0, 0);
       (*shrubVerts)[1] = osg::Vec3( width/2.0f, 0, 0);
       (*shrubVerts)[2] = osg::Vec3( width/2.0f, 0, height);
       (*shrubVerts)[3] = osg::Vec3(-width/2.0f, 0, height);

       shrubQuad->setVertexArray(shrubVerts);

       osg::Vec2Array* shrubTexCoords = new osg::Vec2Array(4);
       (*shrubTexCoords)[0].set(0.0f,0.0f);
       (*shrubTexCoords)[1].set(1.0f,0.0f);
       (*shrubTexCoords)[2].set(1.0f,1.0f);
       (*shrubTexCoords)[3].set(0.0f,1.0f);
       shrubQuad->setTexCoordArray(0,shrubTexCoords);

       shrubQuad->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4));

       // 创建一个颜色数组,并为所有的顶点添加单一的颜色。
       osg::Vec4Array* colorArray = new osg::Vec4Array;
       colorArray->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f) ); // white, fully opaque
       // 创建颜色索引数组。
       osg::TemplateIndexArray
          <unsigned int, osg::Array::UIntArrayType,4,1> *colorIndexArray;
       colorIndexArray =
          new osg::TemplateIndexArray<unsigned int, osg::Array::UIntArrayType,4,1>;
       colorIndexArray->push_back(0);
       // 使用索引数组将其中的第一个值关联给所有的顶点。
       shrubQuad->setColorArray( colorArray);
       shrubQuad->setColorIndices(colorIndexArray);
       shrubQuad->setColorBinding(osg::Geometry::BIND_OVERALL);

       shrubQuad->setStateSet(bbState);

       return shrubQuad;
    }

    December 10

    Navy14.3 - 更新着色器

    目的:

    持续更新OpenGL着色器程序所用的变量。

    course14.3

    -----------------------------

    概述:

    本章演示了如何使用OpenGL着色器程序持续更新参数,从而实现对场景中对象的动态着色效果控制。我们将使用前述的凹凸贴图着色器,并更新其一致变量LightPosition,以模拟灯光环绕坦克模型移动的效果。我们将使用上一章所用的addUniform方法。不过,这一次我们将使用更新回调来实现变量更新的效果。回调类的主体用于持续改变灯光位置的向量。为了使用这个类的功能,我们简单地将其关联给场景中节点的更新回调。

     

    代码:

    着色器更新回调的定义如下:

    class updateBumpShader : public osg::Uniform::Callback
    {
    public:
       virtual void operator()
          ( osg::Uniform* uniform, osg::NodeVisitor* nv )
       {
          float angle = nv->getFrameStamp()->getReferenceTime();
          float x = sinf(angle)*.2f;
          float y = cosf(angle)*.2f;
          osg::Vec3 lightPos(x,y,.6f);
          uniform->set(lightPos);
       }
    };

    其余的代码与上一章基本相同:设置场景和着色器的实例。唯一不同的是,我们需要将上面的回调类设置为场景中一个节点的更新回调。

    int main( int argc, char **argv )
    {
       osg::Group* rootNode = new osg::Group();
       osg::Node* tankNode = NULL;
       osg::Node* terrainNode = NULL;
       osg::PositionAttitudeTransform* tankXform =
          new osg::PositionAttitudeTransform();

       osgDB::FilePathList pathList = osgDB::getDataFilePathList();
       pathList.push_back
          ("C:\\Projects\\OpenSceneGraph\\OpenSceneGraph-
             Data\\NPSData\\Models\\T72-Tank\\");
       pathList.push_back
          ("C:\\Projects\\OpenSceneGraph\\OpenSceneGraph-
             Data\\NPSData\\Models\\JoeDirt\\");
       osgDB::setDataFilePathList(pathList);

       tankNode = osgDB::readNodeFile("T72-tank_des.flt");
       terrainNode = osgDB::readNodeFile("JoeDirt.flt");
       if (! (tankNode && terrainNode))
       {
          std::cout << "Couldn't load models, quitting." << std::endl;
          return -1;
       }
       rootNode->addChild(terrainNode);
       rootNode->addChild(tankXform);
       tankXform->addChild(tankNode);

       tankXform->setPosition( osg::Vec3(10,10,8) );
       tankXform->setAttitude(
          osg::Quat(osg::DegreesToRadians(-45.0), osg::Vec3(0,0,1) ) );

       osg::StateSet* bumpMapState = tankNode->getOrCreateStateSet();

       osg::Program* bumpMapProgramObject = new osg::Program;

       osg::Shader* bumpVertexObject =
          new osg::Shader( osg::Shader::VERTEX );
       osg::Shader* bumpFragmentObject =
          new osg::Shader( osg::Shader::FRAGMENT );

       bool ok =
          loadShaderSource( bumpVertexObject, "shaders/bumpmap.vert" )
          &&
          loadShaderSource
             ( bumpFragmentObject, "shaders/bumpmap.frag" ) ;

       if(!ok)
       {
          std::cout << "Couldn't load shaders" << std::endl;
          return -1;
       }

       bumpMapProgramObject->addShader( bumpFragmentObject );
       bumpMapProgramObject->addShader( bumpVertexObject );

       osg::Uniform* lightPosU =
          new osg::Uniform("LightPosition",osg::Vec3(0,0,1));
       osg::Uniform* normalMapU = new osg::Uniform("normalMap",1);
       osg::Uniform* baseTextureU = new osg::Uniform("baseTexture",0);

       bumpMapState->addUniform(lightPosU);
       bumpMapState->addUniform(normalMapU);
       bumpMapState->addUniform(baseTextureU);
       lightPosU->setUpdateCallback(new updateBumpShader() );

       bumpMapState->setAttributeAndModes
          (bumpMapProgramObject, osg::StateAttribute::ON);

       osg::Texture2D* tankBodyNormalMap = new osg::Texture2D;
       tankBodyNormalMap->setDataVariance(osg::Object::DYNAMIC);
       osg::Image* tankBody = osgDB::readImageFile("TankBump.png");
       if (!tankBody)
       {
          std::cout << " couldn't find texture, quiting." << std::endl;
          return -1;
       }

       tankBodyNormalMap->setImage(tankBody);
       bumpMapState->setTextureAttributeAndModes
          (1,tankBodyNormalMap,osg::StateAttribute::ON);

       osgViewer::Viewer viewer;

       viewer.setCameraManipulator(new osgGA::TrackballManipulator());
       viewer.setSceneData( rootNode );
       viewer.realize();

       while( !viewer.done() )
       {
          viewer.frame();
       }
       return 0;
    }

    编程愉快!

    Navy14.2 - 向着色器传递变量数据

    目的:

    使用OpenGL着色语言,实现从调用程序向着色器传递所需的参数。

    course14.2

    -----------------------------

    概述:


    着色器有两种类型的参数:一致变量(Uniform variables)指得是在一帧当中保持恒定的数值,典型的参数包括视口的方向和灯光的方向。而易变变量(Varying variables)对于每一个执行单元(包括顶点着色器中的顶点,以及片元着色器中的片元)都是变化的。
    一致变量用于从渲染程序向着色器传递参数。在《OpenGL着色语言》一书中,凹凸贴图着色器的例子使用了三个一致变量参数:三维向量LightPosition,以及两个2D材质变量baseTexture和normalMap。OSG类提供了一种直截了当的机制,使得OpenSceneGraph程序可以向着色器直接传递数据。

     

    代码:

    以下所述为OpenSceneGraph程序向凹凸贴图着色器传递参数的方法。其代码使用了Program对象类的方法addUniform,此方法有两个输入参数:一个用于声明着色器一致变量的字符串,以及一个用于记录参数值的变量。声明2D材质一致变量的方法与此类似,其输入参数为2D材质变量名称的字符串,以及所用材质单元的整数值。用于向凹凸贴图着色器传递所需变量的代码如下:

    bool loadShaderSource(osg::Shader* obj, const std::string& fileName );

    int main( int argc, char **argv )
    {
       osg::Group* rootNode = new osg::Group();
       osg::Node* tankNode = NULL;
       osg::Node* terrainNode = NULL;
       osg::PositionAttitudeTransform* tankXform =
          new osg::PositionAttitudeTransform();

       osgDB::FilePathList pathList = osgDB::getDataFilePathList();
       pathList.push_back
          ("C:\\Projects\\OpenSceneGraph\\OpenSceneGraph-Data\\NPSData\\Models\\T72-Tank\\");
       pathList.push_back
          ("C:\\Projects\\OpenSceneGraph\\OpenSceneGraph-Data\\NPSData\\Models\\JoeDirt\\");
       osgDB::setDataFilePathList(pathList);

       tankNode = osgDB::readNodeFile("T72-tank_des.flt");
       terrainNode = osgDB::readNodeFile("JoeDirt.flt");
       if (! (tankNode && terrainNode))
       {
          std::cout << "Couldn't load models, quitting." << std::endl;
          return -1;
       }
       rootNode->addChild(terrainNode);
       rootNode->addChild(tankXform);
       tankXform->addChild(tankNode);

       tankXform->setPosition( osg::Vec3(10,10,8) );
       tankXform->setAttitude(
          osg::Quat(osg::DegreesToRadians(-45.0), osg::Vec3(0,0,1) ) );

       // 将一个“空”Program对象关联到rootNode,它相当于定义了一个缺省的
       // StateAttribute属性。“空”Program对象(不与任何Shader对象关联)是一种
       // 特殊情形,它表示程序将使用OpenGL 1.x固化功能的渲染管道。
       osg::StateSet* rootState = rootNode->getOrCreateStateSet();
       osg::Program* defaultProgramObject = new osg::Program;
       rootState->setAttributeAndModes(defaultProgramObject, osg::StateAttribute::ON);
       osg::StateSet* bumpMapState = tankNode->getOrCreateStateSet();

       osg::Program* bumpMapProgramObject = new osg::Program;

       osg::Shader* brickVertexObject =
          new osg::Shader( osg::Shader::VERTEX );
       osg::Shader* brickFragmentObject =
          new osg::Shader( osg::Shader::FRAGMENT );

       bool ok =
          loadShaderSource( brickVertexObject, "shaders/bumpmap.vert" )
          &&
          loadShaderSource( brickFragmentObject, "shaders/bumpmap.frag" ) ;

       if(!ok)
       {
          std::cout << "Couldn't load shaders" << std::endl;
          return -1;
       }

       bumpMapProgramObject->addShader( brickFragmentObject );
       bumpMapProgramObject->addShader( brickVertexObject );

       osg::Uniform* lightPosU = new osg::Uniform("LightPosition",osg::Vec3(0,0,1));
       osg::Uniform* normalMapU = new osg::Uniform("normalMap",1);
       osg::Uniform* baseTextureU = new osg::Uniform("baseTexture",0);
       bumpMapState->addUniform(lightPosU);
       bumpMapState->addUniform(normalMapU);
       bumpMapState->addUniform(baseTextureU);
       bumpMapState->setAttributeAndModes(bumpMapProgramObject, osg::StateAttribute::ON);

       osg::Texture2D* tankBodyNormalMap = new osg::Texture2D;
       // 避免在优化过程中静态量出错。
       tankBodyNormalMap->setDataVariance(osg::Object::DYNAMIC);
       osg::Image* tankBody = osgDB::readImageFile("TankBump.png");
       if (!tankBody)
       {
          std::cout << " couldn't find texture, quiting." << std::endl;
          return -1;
       }

       tankBodyNormalMap->setImage(tankBody);
       bumpMapState->setTextureAttributeAndModes(1,tankBodyNormalMap,osg::StateAttribute::ON);

       osgViewer::Viewer viewer;
       viewer.setCameraManipulator(new osgGA::TrackballManipulator());
       viewer.setSceneData( rootNode );
       viewer.realize();

       while( !viewer.done() )
       {
          viewer.frame();
       }
       return 0;
    }

    编程愉快!

    December 09

    Navy14.1 - 使用OpenGL顶点着色器和片元着色器(概述)

    目的:

    使用OpenGL着色语言中的顶点和片元着色器,替代原有的固化函数管道的光照和材质计算,用于场景图形中的节点选择。(砖块着色器程序来自3DLabs)

    course14.1

    ------------------------------------

    概述:

    OpenGL着色语言允许程序员编写自己的像素和顶点着色器。有关着色语言的更多信息,包括最低的硬件和软件需求,请参阅:
    http://developer.3dlabs.com/openGL2/
    OSG的osg::Program和osg::Shader类允许用户将着色器作为StateSet的一部分加入选定的场景图形子树。如果要在OpenSceneGraph中使用自定义的顶点和片元着色器,则需要使用下面的基类:

    osg::Program - 在应用层级上封装了OpenGL着色语言的glProgramObject函数。Program类的对象继承自osg::StateAttribute类。即osg::Program类的实例可以关联到StateSet,并使用setAttributeAndModes()方法来许可自身的使用。开启Program渲染状态之后,与此渲染状态相关联的几何体均会使用该Program的着色器进行渲染。

    osg::Shader - 在应用层级上封装了OpenGL着色语言的glShaderObject函数。这个类用于管理着色器源代码的加载和编译。osg::Shader类的实例可以与一个或多个osg::Program的实例相关联。Shader对象有两种类型:osg::Shader::FRAGMENT和osg::Shader::VERTEX。

    代码:

    要创建一个使用OpenGL像素和片元着色器的程序,可以按照下面的步骤:

    • 创建一个osg::Program实例;
    • 创建一个osg::Shader类的顶点(VERTEX)或片元(FRAGMENT)着色器实例;
    • 加载并编译着色器代码;
    • 将着色器添加到osg::Program实例;
    • 将osg::Program实例关联给StateSet类,并将其激活;

    下面的代码用于加载和应用基本的顶点和片元着色器:

      osg::StateSet* brickState = tankNode->getOrCreateStateSet();

       osg::Program* brickProgramObject = new osg::Program;
       osg::Shader* brickVertexObject =
          new osg::Shader( osg::Shader::VERTEX );
       osg::Shader* brickFragmentObject =
          new osg::Shader( osg::Shader::FRAGMENT );
       brickProgramObject->addShader( brickFragmentObject );
       brickProgramObject->addShader( brickVertexObject );
       loadShaderSource( brickVertexObject, "shaders/brick.vert" );
       loadShaderSource( brickFragmentObject, "shaders/brick.frag" );

       brickState->setAttributeAndModes(brickProgramObject, osg::StateAttribute::ON);

    下面的函数可以方便地加载着色器代码,并将编译后的代码关联给着色器对象:

    bool loadShaderSource(osg::Shader* obj, const std::string& fileName )
    {
       std::string fqFileName = osgDB::findDataFile(fileName);
       if( fqFileName.length() == 0 )
       {
          std::cout << "File \"" << fileName << "\" not found." << std::endl;
          return false;
       }
       bool success = obj->loadShaderSourceFromFile( fqFileName.c_str());
       if ( !success  )
       {
          std::cout << "Couldn't load file: " << fileName << std::endl;
          return false;
       }
       else
       {
          return true;
       }
    }

    祝你好运!

    December 04

    Navy11.3 - 如何获取节点在世界坐标的位置

    获取某个节点的世界坐标(相对世界原点的位置),有一种方法是创建可以更新矩阵的回调函数。有关这一方法的示例可以参照前面的例子。但是这一方法的弊病在于必须使用更新回调。回调被触发之后,无论用户需要与否,每帧都会自动计算矩阵坐标。如果你不喜欢这种方式的话,可以尝试使用访问器。

    使用节点访问器计算世界原点的相对位置

    对于场景图形中的一个OSG节点,在它和根节点之间可能会有其它的一些变换节点存在。那么应该如何获取节点在世界的坐标呢?从技术上讲是很难的。节点的每一个父节点都有且只有自己的变换矩阵。通常情况下,这些矩阵中包含了相对坐标数据。如果要计算目标节点的世界坐标的话,则需要将根节点和目标节点之间所有的矩阵相乘。访问器模式的使用解决了这一问题。访问器(Visitor)可以跟踪记录场景图形中节点遍历的路径。OSG提供了相应的函数,用于获取节点路径nodePath并计算基于路径上各个矩阵的世界坐标。

    • 创建从叶节点到根节点的访问器。
    • 对于目标节点,启动访问器。
    • 访问器将遍历整个场景图形,以得到正确的节点路径。
    • 到达根节点之后,计算得到目标节点的世界坐标。
    • 使用访问器的nodePath计算节点世界坐标。

    C++格式的代码如下:

    // 该访问器类用于返回某个节点的世界坐标。
    // 它从起始节点开始向父节点遍历,并随时将历经的节点记录到nodePath中。
    // 第一次到达根节点之后,它将记录起始节点的世界坐标。连结起始节点到根节点路径上的
    // 所有矩阵之后,即可获得节点的世界坐标。
    class getWorldCoordOfNodeVisitor : public osg::NodeVisitor {
    public:
       getWorldCoordOfNodeVisitor():
          osg::NodeVisitor(NodeVisitor::TRAVERSE_PARENTS), done(false)
          {
             wcMatrix= new osg::Matrixd();
          }
          virtual void apply(osg::Node &node)
          {
             if (!done)
             {
                if ( 0 == node.getNumParents() ) // 到达根节点,此时节点路径也已记录完整
                {
                   wcMatrix->set( osg::computeLocalToWorld(this->getNodePath()) );
                   done = true;
                }
                traverse(node);
             }
          }
          osg::Matrixd* giveUpDaMat()
          {
             return wcMatrix;
          }
    private:
       bool done;
       osg::Matrix* wcMatrix;
    };

    // 对于场景中的合法节点,返回osg::Matrix格式的世界坐标。
    // 用户创建用于更新世界坐标矩阵的访问器之后,既可获取该矩阵。
    // (此函数也可以作为节点派生类的成员函数。)
    osg::Matrixd* getWorldCoords( osg::Node* node) {
       getWorldCoordOfNodeVisitor* ncv = new getWorldCoordOfNodeVisitor();
       if (node && ncv)
       {
          node->accept(*ncv);
          return ncv->giveUpDaMat();
       }
       else
       {
          return NULL;
       }
    }

    编程愉快!

    Navy11.2 - 环绕(始终指向)场景中节点的相机

    目标:

    创建回调,以实现用于沿轨道环绕,同时指向场景中某个节点的世界坐标矩阵的更新。使用此矩阵的逆矩阵来放置相机。

    course11.2

    ----------------------------------------------------------------------

    本章的回调类基于上一篇的osgFollowMe教程。本章中,我们将添加一个新的矩阵数据成员,以保存视口相机所需的世界坐标。每次更新遍历启动时,我们将调用环绕节点的当前轨道世界坐标矩阵。为了实现环绕节点的效果,我们将添加一个“angle”数据成员,其值每帧都会增加。矩阵的相对坐标基于一个固定数值的位置变换,而旋转量基于每帧更新的角度数据成员。为了实现相机的放置,我们还将添加一个方法,它将返回当前的轨道位置世界坐标。类的声明如下所示:

    class orbit : public osg::NodeCallback
    {
    public:
       orbit(): heading(M_PI/2.0) {}

       osg::Matrix getWCMatrix(){return worldCoordMatrix;}

       virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
       {
          osg::MatrixTransform *tx = dynamic_cast<osg::MatrixTransform *>(node);
          if( tx != NULL )
          {
             heading += M_PI/180.0;
             osg::Matrixd orbitRotation;
             orbitRotation.makeRotate(
                osg::DegreesToRadians(-10.0), osg::Vec3(0,1,0), // 滚转角(Y轴)
                osg::DegreesToRadians(-20.0), osg::Vec3(1,0,0) , // 俯仰角(X轴)
                heading, osg::Vec3(0, 0, 1) ); // 航向角(Z轴)
             osg::Matrixd orbitTranslation;
             orbitTranslation.makeTranslate( 0,-40, 4 );
             tx->setMatrix ( orbitTranslation * orbitRotation);
             worldCoordMatrix = osg::computeLocalToWorld( nv->getNodePath() );
          }
          traverse(node, nv);
       }
    private:
       osg::Matrix worldCoordMatrix;
       float heading;
    };

    使用回调时,我们需要向场景添加一个矩阵变换,并将更新回调设置为“orbit”类的实例。我们使用前述osgManualCamera教程中的代码来实现用矩阵世界坐标来放置相机。我们还将使用前述键盘接口类的代码来添加一个函数来更新全局量,该全局量用于允许用户自行选择缺省和“环绕”的视口。

    int main()
    {
       osg::Node* groundNode = NULL;
       osg::Node* tankNode = NULL;
       osg::Group* root = NULL;
       osgViewer::Viewer viewer;
       osg::PositionAttitudeTransform* tankXform = NULL;

       groundNode = osgDB::readNodeFile("\\Models\\JoeDirt\\JoeDirt.flt");
       tankNode = osgDB::readNodeFile("\\Models\\T72-Tank\\T72-tank_des.flt");

       root = new osg::Group();

       // 创建天空。
       osg::ClearNode* backdrop = new osg::ClearNode;
       backdrop->setClearColor(osg::Vec4(0.0f,0.8f,0.0f,1.0f));
       root->addChild(backdrop);

       tankXform = new osg::PositionAttitudeTransform();
       root->addChild(groundNode);
       root->addChild(tankXform);
       tankXform->addChild(tankNode);
       tankXform->setPosition( osg::Vec3(10,10,8) );
       tankXform->setAttitude(
          osg::Quat(osg::DegreesToRadians(-45.0), osg::Vec3(0,0,1) ) );

       osgGA::TrackballManipulator *Tman = new osgGA::TrackballManipulator();
       viewer.setCameraManipulator(Tman);

       viewer.setSceneData( root );
       viewer.realize();

       // 创建矩阵变换节点,以实现环绕坦克节点。
       osg::MatrixTransform* orbitTankXForm = new osg::MatrixTransform();
       // 创建环绕轨道回调的实例。
       orbit* tankOrbitCallback = new orbit();
       // 为矩阵变换节点添加更新回调的实例。
       orbitTankXForm->setUpdateCallback( tankOrbitCallback );
       // 将位置轨道关联给坦克的位置,即,将其设置为坦克变换节点的子节点。
       tankXform->addChild(orbitTankXForm);

       keyboardEventHandler* keh = new keyboardEventHandler();
       keh->addFunction('v',toggleTankOrbiterView);
       viewer.addEventHandler(keh);

       while( !viewer.done() )
       {
          if (useTankOrbiterView)
          {
             Tman->setByInverseMatrix(tankOrbitCallback->getWCMatrix()
                               *osg::Matrix::rotate( -3.1415926/2.0, 1, 0, 0 ));
          }

          viewer.frame();
       }
       return 0;
    }

    提示:按下V键来切换不同的视口。
    祝您好运!

    November 26

    Navy11.1 - 实现跟随节点的相机

    提示:

    自OSG 0.9.7发布之后,新的osgGA::MatrixManipulator类(TrackerManipulator)允许用户将摄相机“依附”到场景图形中的节点。这一新增的操纵器类可以高效地替代下面所述的方法。
    本章教程将继续使用回调和节点路径(NodePath)来检索节点的世界坐标。

    本章目标:

    在一个典型的仿真过程中,用户可能需要从场景中的各种车辆和人物里选择一个进行跟随。本章将介绍一种将摄像机“依附”到场景图形节点的方法。此时视口的摄像机将跟随节点的世界坐标进行放置。

    course11.1

    ----------------------------------------------------------------------

    概述:

    视口类包括了一系列的矩阵控制器(osgGA::MatrixManipulator)。因而提供了“驱动控制(Drive)”,“轨迹球(Trackball)”,“飞行(Fly)”等交互方法。矩阵控制器类用于更新摄像机位置矩阵。它通常用于回应GUI事件(鼠标点击,拖动,按键,等等)。本文所述的功能需要依赖于相机位置矩阵,并参照场景图形节点的世界坐标。这样的话,相机就可以跟随场景图形中的节点进行运动了。
    为了获得场景图形中节点的世界坐标,我们需要使用节点访问器的节点路径功能来具现一个新的类。这个类将提供一种方法将自己的实例关联到场景图形,并因此提供访问任意节点世界坐标的方法。此坐标矩阵(场景中任意节点的世界坐标)将作为相机位置的矩阵,由osgGA::MatrixManipulator实例使用。

    实现:

    首先我们创建一个类,计算场景图形中的多个变换矩阵的累加结果。很显然,所有的节点访问器都会访问当前的节点路径。节点路径本质上是根节点到当前节点的所有节点列表。有了节点路径的实例之后,我们就可以使用场景图形的方法computeWorldToLocal( osg::NodePath)来获取表达节点世界坐标的矩阵了。
    这个类的核心是使用更新回调来获取某个给定节点之前所有节点的矩阵和。整个类的定义如下:

    struct updateAccumulatedMatrix : public osg::NodeCallback
    {
       virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
       {
          matrix = osg::computeWorldToLocal(nv->getNodePath() );
          traverse(node,nv);
       }
       osg::Matrix matrix;
    };
      

    下一步,我们需要在场景图形的更新遍历中启动回调类。因此,我们将创建一个类,其中包括一个osg::Node实例作为数据成员。此节点数据成员的更新回调是上述updateAccumulatedMatrix类的实例,同时此节点也将设置为场景的一部分。为了读取用于描绘节点世界坐标的矩阵(该矩阵与节点实例相关联),我们需要为矩阵提供一个“get”方法。我们还需要提供添加节点到场景图形的方法。我们需要注意的是,用户应如何将节点关联到场景中。此节点应当有且只有一个父节点。因此,为了保证这个类的实例只有一个相关联的节点,我们还需要记录这个类的父节点。类的定义如下面的代码所示:

    struct transformAccumulator
    {
    public:
       transformAccumulator();
       bool attachToGroup(osg::Group* g);
       osg::Matrix getMatrix();
    protected:
       osg::ref_ptr<osg::Group> parent;
       osg::Node* node;
       updateAccumulatedMatrix* mpcb;
    };

    类的实现代码如下所示:

    transformAccumulator::transformAccumulator()
    {
       parent = NULL;
       node = new osg::Node;
       mpcb = new updateAccumulatedMatrix();
       node->setUpdateCallback(mpcb);
    }

    osg::Matrix transformAccumulator::getMatrix()
    {
       return mpcb->matrix;
    }

    bool transformAccumulator::attachToGroup(osg::Group* g)
    // 注意不要在回调中调用这个函数。
    {
       bool success = false;
       if (parent != NULL)
       {
          int n = parent->getNumChildren();
          for (int i = 0; i < n; i++)
          {
             if (node == parent->getChild(i) )
             {
                parent->removeChild(i,1);
                success = true;
             }
          }
          if (! success)
          {
             return success;
          }
       }
       g->addChild(node);
       return true;
    }

    现在,我们已经提供了类和方法来获取场景中节点的世界坐标矩阵,我们所需的只是学习如何使用这个矩阵来变换相机的位置。osgGA::MatrixManipulator类即可提供一种更新相机位置矩阵的方法。我们可以从MatrixManipulator继承一个新的类,以实现利用场景中某个节点的世界坐标矩阵来改变相机的位置。为了实现这一目的,这个类需要提供一个数据成员,作为上述的accumulateTransform实例的句柄。新建类同时还需要保存相机位置矩阵的相应数据。
    MatrixManipulator类的核心是“handle”方法。这个方法用于检查选中的GUI事件并作出响应。对我们的类而言,唯一需要响应的GUI事件就是“FRAME”事件。在每一个“帧事件”中,我们都需要设置相机位置矩阵与transformAccumulator矩阵的数值相等。我们可以在类的成员中创建一个简单的updateMatrix方法来实现这一操作。由于我们使用了虚基类,因此某些方法必须在这里进行定义(矩阵的设置及读取,以及反转)。综上所述,类的实现代码如下所示:

    class followNodeMatrixManipulator : public osgGA::MatrixManipulator
    {
    public:
       followNodeMatrixManipulator( transformAccumulator* ta);
       bool handle (const osgGA::GUIEventAdapter&ea, osgGA::GUIActionAdapter&aa);
       void updateTheMatrix();
       virtual void setByMatrix(const osg::Matrixd& mat) {theMatrix = mat;}
       virtual void setByInverseMatrix(const osg::Matrixd&mat) {}
       virtual osg::Matrixd getInverseMatrix() const;
       virtual osg::Matrixd getMatrix() const;
    protected:
       ~followNodeMatrixManipulator() {}
       transformAccumulator* worldCoordinatesOfNode;
       osg::Matrixd theMatrix;
    };

    The class implementation is as follows:

    followNodeMatrixManipulator::followNodeMatrixManipulator( transformAccumulator* ta)
    {
       worldCoordinatesOfNode = ta; theMatrix = osg::Matrixd::identity();
    }
    void followNodeMatrixManipulator::updateTheMatrix()
    {
       theMatrix = worldCoordinatesOfNode->getMatrix();
    }
    osg::Matrixd followNodeMatrixManipulator::getMatrix() const
    {
       return theMatrix;
    }
    osg::Matrixd followNodeMatrixManipulator::getInverseMatrix() const
    {
       // 将矩阵从Y轴向上旋转到Z轴向上
       osg::Matrixd m;
       m = theMatrix * osg::Matrixd::rotate(-M_PI/2.0, osg::Vec3(1,0,0) );
       return m;
    }
    void followNodeMatrixManipulator::setByMatrix(const osg::Matrixd& mat)
    {
       theMatrix = mat;
    }
    void followNodeMatrixManipulator::setByInverseMatrix(const osg::Matrixd& mat)
    {
       theMatrix = mat.inverse();
    }

    bool followNodeMatrixManipulator::handle
    (const osgGA::GUIEventAdapter&ea, osgGA::GUIActionAdapter&aa)
    {
       switch(ea.getEventType())
       {
          case (osgGA::GUIEventAdapter::FRAME):
          {
             updateTheMatrix();
             return false;
          }
       }
       return false;
    }

    上述的所有类都定义完毕之后,我们即可直接对其进行使用。我们需要声明一个transformAccumulator类的实例。该实例应当与场景图形中的某个节点相关联。然后,我们需要声明nodeFollowerMatrixManipulator类的实例。此操纵器类的构造函数将获取transformAccumulator实例的指针。最后,将新的矩阵操纵器添加到视口操控器列表中。上述步骤的实现如下:

    // 设置场景和视口(包括tankTransform节点的添加)……

    transformAccumulator* tankWorldCoords = new transformAccumulator();
    tankWorldCoords->attachToGroup(tankTransform);
    followNodeMatrixManipulator* followTank =
       new followNodeMatrixManipulator(tankWorldCoords);
    osgGA::KeySwitchMatrixManipulator *ksmm = new osgGA::KeySwitchMatrixManipulator();
    if (!ksmm)
       return -1;
    // 添加跟随坦克的矩阵控制器的。按下“m”键即可实现视口切换到该控制器。
    ksmm->addMatrixManipulator('m',"tankFollower",followTank);
    viewer.setCameraManipulator(ksmm);

    // 进入仿真循环……

    祝你好运!