osg readNodeFiles()怎么node.js get 传递参数数,我想一次载入多个.ive文件

2306人阅读
花了两天时间仔细研究了一下array贡献的第二代文件序列化存储,收益颇多。今后将得益于它的很强的可扩展性。通过自身的体验,感觉应该还有很多可能需要花费几个小时才能看透,所以特将自己分析的流程发上来,以加快想搞明白其流程的初学者的学习速度。这两天身体状况不太好,两眼通红,眼睛发涩,所以在写的过程中难免有些拼写错误什么的,流程的分析也难免有纰漏或不妥,还请高手指正。
我们以将节点写入文件为例,节点读取与此相同,看看究竟是如何将节点写入文件的,我们从文件读写插件的最顶层入口函数来看,即
virtual WriteResult writeNode( const osg::Node& node, const std::string& fileName, const Options* options ) const
因为osg第二代文件格式读写插件同时支持文本、二进制以及XML格式的文件,所以,在写文件前要确定具体要写入什么文件,读写不同的文件格式需要用到的序列化器不同,文件打开方式也不同,
进入writeNode后会调用Options* prepareWriting( WriteResult& result, const std::string& fileName, std::ios::penmode&
mode, const Options* options ) const
该函数就完成上面的工作,该函数根据文件扩展名判断如果是osgt格式,就向options中添加文本格式的参数选项,如果是osgx格式,就添加XML格式选项,以让后续的写入操作根据该选项进行相应文件格式的读写,否则以二进制格式读写;
做好以上准备工作后,就像以前一样将文件数据读入文件流开始向文件流写入数据,即进入virtual WriteResult writeNode( const osg::Node& node, std::stream& fout, const Options* options ) const
针对文本、XML、二进制格式的文件分别对应三种不同的输出指示器,在OutputStream类中进行数据写入时要调用相应的输出指示器,所以,首先要获取相应类型的输出指示器;该工作通过函数OutputIterator* writeOutputIterator( std::stream& fout, const Options* options )来完成,该函数从options中查找是否有文本或者XML的文件格式选项(从刚才的分析我们已经知道,如果是osgt或osgx格式的话,在prepareWriting中已经添加进来),如果有对应的选项,就创建相应的输出指示器,如果两种都没有,就创建二进制格式的输出指示器,除文本格式之外,对于XML格式和二进制格式,该函数除了创建相应的输出指示器外,同时还像文件流写入了文件头信息,对于XML格式,直接写入XML文件头,对于二进制格式,写入MD5码;从上面的分析我们不难看出,对于文本和XML格式的文件,我们简单的添加supportsExtension来让插件支持我们自己的扩展名文件类型是做不到的,插件会把它当做二进制来对待,无法正确处理,所以,扩展名得扩展只适用于二进制格式;
得到输出指示器后构造一个OutputStream对象开始写数据。
首先是调用void OutputStream::start( OutputIterator* outIterator, OutputStream::WriteType type ),将输出指示器传给OutputStream并向文件流写入文件的标志信息,如文件内容类型标示(如、对象、图像)、版本号等信息,接下来调用void
OutputStream::writeObject( const osg::Object* obj )从节点对象读取节点数据。下面进入该函数,首先获取一个对象唯一ID,然后向流中写入对象类名(如osg::Group),然后写入开始大括号’{’,接下来写入对象的数据,写入对象数据后写入结束大括号’}’;上面的写入操作都是通过刚才传入的输出指示器来完成;
& & & & 下面我们将如何写入对象数据来分解开进行分析,这是写入操作的核心所在。该过程在void OutputStream::writeObjectFields( const osg::Object* obj )中完成,我们现在就来看看writeObjectFields都玩了哪些花样;一定要看仔细,正是它玩的花样,才使得我们可以在不修改该文件操作插件的情况下通过扩展的方式让自己的节点对象也可以读写的,所以一定要看仔细了;
首先要根据类名获取该类的wrapper(所有要能够将数据写入文件的类都要对应有一个该类的包装类wrapper,所有的wrapper统一由wrapper管理器来管理,每添加一个wrapper类型,自己要将自己注册到该管理器,否则无法对该类型的对象进行读写操作),如果要让自己的对象能够进行文件读写,需要扩展自己的类的wrapper类,在wrapper类中定义要读写哪些数据字段;
类是有继承关系的,那么在进行读写操作时,还是需要各个类自己操作自己本身的成员,一个子类只负责自己扩展出来的成员字段的读写操作,父类的那些成员又父类的wrapper去处理,各司其职。而系统本身是没有办法知道一个子类上面都有哪些父类、祖父类等关联类信息的,所以需要我们告诉它,该工作通过在构造wrapper时指定,将该子类关联的所有上层类名称传进去,即associates参数,在每个wrapper类中有一个associates的数组。
继续上面的分析过程,在writeObjectFields中获取到要写入文件的节点对象(根据类名获得,所以,自己派生的对象要能像osg对象那样能够用这种方式进行读写操作,必须从osg::Object派生,且必须实现ClassName接口)对应的wrapper后,遍历该wrapper的关联类数组,然后再根据关联类获取对应的wrapper进行数据写入工作。在继续该写入过程详细分析之前,需要补充说明一下,注册wrapper时向该wrapper传入的管理类信息包含了该类本身,所以,这里的写入操作统一在遍历关联类数组过程中进行,所以,在注册wrapper时,一定要在最后将类自身也添加到关联类信息里,否则只能读写上层类的数据,本类本身的信息会丢失。
接下来的分析就相对要集中了,就是针对具体的类类型进行数据的写入操作了。如果需要向文件中写入每个类的字段刚要信息,就将每个类的属性字段名称及类型以刚要的形式写入,然后再写该类的数据信息,默认情况下不写入;那么我们接着看写对象的数据字段的内容。该过程由bool ObjectWrapper::write( OutputStream& os, const osg::Object& obj )完成,下面我们来进到里面探个究竟。
在分析之前我们需要插入一段前奏,然后才能继续下面的过程。通过上面的分析,我们知道怎么让插件能够读写每种类对象了(通过扩展wrapper并注册),也知道了怎么区别对待不同类型的文件(Txt、XML、Binary,通过不同的指示器),还有一个重要的问题没有提到,那就是如何知道要读写一个类的哪些数据成员,以及如何调用数据获取接口。在注册wrapper时,除了给wrapper设置其对应的类名、类对象原型、关联类描述之外,还要设置通过何种序列化器对哪些字段进行读写操作。我们通过代码不难看到ADD_USER_SERIALIZER、ADD_OBJECT_SERIALIZER、ADD_DOUBLE_SERIALIZER等等身影,通过这些宏就设定好了要读写该wrapper对应的类的数据字段。一个对象有多少属性字段,就会对应多少序列化器,每个序列化器负责该属性字段的具体的读写操作,wrapper对象会将所有加入的序列化器保存在一个数组中。此外,对于不同的字段数据类型,对应有相应类型的序列化器,如int、double、osg::Vec3d以及对象类型等等,操作相应类型的属性字段要选择正确的序列化器。
通过上面的分析,我们应该知道了如何设定类的哪些属性数据可进行文件存储,接下来我们来看看是如何调用相关的属性访问接口来进行数据存取的。方法用的就是函数指针,在添加序列化器时,相应的序列化器会根据传入的属性字段名称自动创建对应的属性访问函数指针(getter、setter)或字段读写函数指针(reader、writer)并将其传递给序列化器的构造函数(函数指针作为函数参数传递,这些函数都不会有重载函数,基本都是getXXX、setXXX,大可放心使用),然后序列化器会记录下来这些函数指针,在序列化器进行数据字段的读写时调用该函数指针来进行数据字段的读写操作;
好了,前奏到此为止,不要忘了我们还没有进入bool ObjectWrapper::write( OutputStream& os, const osg::Object& obj )呢。现在我们来看这个函数就很简单了,它也就是遍历该wrapper对象的所有序列化器,调用序列化器的write函数将每个属性字段写进去。对于UserSerializer是传递属性字段读写函数指针,对于其他类型的序列化器,传递的是属性字段的访问函数指针,两者最终都是通过OutputStream的”&&”重载操作符将字段值写入到流中,进一步我们可以看到OutputStream的”&&”重载操作符函数最终通过调用最开始时传入OutputStream的输出指示器的类型写入函数来完成最终的写入操作;针对不同类型的格式控制(txt、XML、Binary)都是在相应的输出指示器中完成。
现在我们知道,通过该插件,我们要让该插件支持我们自己扩展的对象类型,只要创建相应的wrapper并注册进来就OK,非常方便。另外一方面,可能在开发自己的应用系统时,需要定义自己的文件格式,这时,如果是二进制的格式的话,直接通过supportsExtension添加自己的扩展名支持即可,在应用系统中通过addFileExtensionAlias来指定一下。此外还有一个重要的扩展可能对我们开发自己的系统更为重要,那就是文件数据的加密存储,简单起见我们可以重写插件的writeOutputIterator和readOutputIterator,在文件头中加入自己的加密数据,如base64/md5加密数据,并做相应的检测处理,通过派生新的compressor我们就可以实现对文件内容的压缩和解压缩,或者加密和解密,或者加入自己的头信息等等,都非常方便;
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:101330次
积分:1178
积分:1178
排名:千里之外
原创:24篇
转载:17篇
评论:35条
(1)(1)(1)(1)(6)(1)(7)(1)(5)(2)(1)(3)(3)(7)(2)1966人阅读
&#include &osgViewer/Viewer&
#include &osgViewer/ViewerEventHandlers&
#include &osg/Node&
#include &osg/Group&
#include &osg/Geode&
#include &osg/Camera&
#include &osg/ShapeDrawable&
#include &osg/ComputeBoundsVisitor&
#include &osg/BoundingBox&
#include &osg/BoundingSphere&
#include &osg/AnimationPath&
#include &osgDB/ReadFile&
#include &osgDB/WriteFile&
#include &osgShadow/ShadowedScene&
#include &osgShadow/ShadowVolume&
#include &osgShadow/ShadowTexture&
#include &osgShadow/ShadowMap&
#include &osgShadow/SoftShadowMap&
#include &osgShadow/ParallelSplitShadowMap&
#include &osgShadow/MinimalShadowMap&
#include &osgShadow/StandardShadowMap&
#include &osgShadow/SoftShadowMap&
#include &osgShadow/ViewDependentShadowTechnique&
#include &osgUtil/Optimizer&
#include &osgUtil/SmoothingVisitor&
#include &osg/AnimationPath&
#include &iostream&
//标识阴影接收对象
const int ReceivesShadowTraversalMask=0x1;
//标识阴影投影对象
const int CastsShadowTraversalMask=0x2;
osg::AnimationPath* createAnimationPath( float radius, float time )
osg::ref_ptr&osg::AnimationPath& path = new osg::AnimationP
path-&setLoopMode( osg::AnimationPath::LOOP );
unsigned int numSamples = 32;
float delta_yaw = 2.0f * osg::PI/((float)numSamples - 1.0f);
float delta_time = time / (float)numS
for ( unsigned int i=0; i&numS ++i )
float yaw = delta_yaw * (float)i;
osg::Vec3 pos( sinf(yaw)*radius, cosf(yaw)*radius, 20.0f );
osg::Quat rot( -yaw, osg::Z_AXIS );
path-&insert( delta_time * (float)i, osg::AnimationPath::ControlPoint(pos, rot) );
return path.release();
//创建场景数据、模型
osg::ref_ptr&osg::Node&createModel()
//创建投影对象,读取房子模型
osg::ref_ptr&osg::Node& node1=new osg::N
node1=osgDB::readNodeFile(&D:/brdm.ive&);
node1-&setNodeMask(CastsShadowTraversalMask);
//设置阴影投射对象和接受对象,使房子恰好在地形模型之上
osg::ref_ptr &osg::MatrixTransform& mat=new osg::MatrixTransform();
osg::Matrix m = osg::Matrix::scale(1.0f,1.0f,1.0f)*osg ::Matrix::translate(osg::Vec3(0,0,10.0f));
mat-&setMatrix(m);
mat-&addChild(node1.get());
osg::ref_ptr&osg::AnimationPathCallback& apcb = new osg::AnimationPathC
apcb-&setAnimationPath( createAnimationPath(50.0f, 6.0f) );
mat-&setUpdateCallback( apcb.get() );
osg::ref_ptr&osg::Node& node2=osgDB::readNodeFile(&D:/brdm.ive&);
node2-&setNodeMask(CastsShadowTraversalMask);
//设置另外一个建筑物的位置和大小,同上
osg::ref_ptr &osg::MatrixTransform& mat2=new osg::MatrixTransform();
osg::Matrix m2=osg::Matrix::scale(1.0f,1.0f,1.0f)*osg ::Matrix::translate(osg::Vec3(20,0,10.0f));
mat2-&setMatrix(m2);
mat2-&addChild(node2.get());
//创建接受投影的对象,读取地面模型
osg::ref_ptr&osg::Node&terrain=new osg::N
terrain=osgDB::readNodeFile(&D:/terrain_simple.ive&);
terrain-&setNodeMask(ReceivesShadowTraversalMask);
//创建一个组节点,将各个子节点添加进来
osg::ref_ptr&osg::Group&group=new osg::G
group-&addChild(terrain.get());
group-&addChild(mat.get());
group-&addChild(mat2.get());
return group.get();
//创建一个光照
osg::ref_ptr&osg::Node&createLight(osg::ref_ptr&osg::Node&model)
osg::ComputeBoundsV
model-&accept(cbbv);
osg::BoundingBox bb=cbbv.getBoundingBox();
osg::ref_ptr&osg::Light&lt=new osg::L
lt-&setLightNum(0);
//设置环境光的颜色
lt-&setAmbient(osg::Vec4(1.0f,1.0f,1.0f,1.0f));
osg::ref_ptr&osg::LightSource&ls=new osg::LightSource();
ls-&setLight(lt.get());
return ls.get();
int main()
osg::ref_ptr&osgViewer::Viewer& viewer = new osgViewer::Viewer();
viewer-&addEventHandler(new osgViewer::WindowSizeHandler);
//创建一个组节点
osg::ref_ptr&osg::Group& root = new osg::Group();
//创建一个阴影节点,并标识接收对象和投影对象
osg::ref_ptr&osgShadow::ShadowedScene& shadowedScene = new osgShadow::ShadowedScene();
shadowedScene-&setReceivesShadowTraversalMask(ReceivesShadowTraversalMask);
shadowedScene-&setCastsShadowTraversalMask(CastsShadowTraversalMask);
//创建阴影纹理,使用的是shadowTexture技法
osg::ref_ptr&osgShadow::ShadowTexture& st = new osgShadow::ShadowT
osg::ref_ptr&osgShadow::ParallelSplitShadowMap& pss = new osgShadow::ParallelSplitShadowM
//osg::ref_ptr&osgShadow::ShadowVolume& sv = new osgShadow::ShadowV
osg::ref_ptr&osgShadow::MinimalShadowMap& ms = new osgShadow::MinimalShadowM
osg::ref_ptr&osgShadow::StandardShadowMap& ss = new osgShadow::StandardShadowM
osg::ref_ptr&osgShadow::SoftShadowMap& softS = new osgShadow::SoftShadowM
osg::ref_ptr&osgShadow::ViewDependentShadowTechnique& vds = new osgShadow::ViewDependentShadowT
//关联阴影纹理
shadowedScene-&setShadowTechnique(softS);
//创建一个根节点,并将场景数据、模型赋予节点
osg::ref_ptr&osg::Node& node =createModel();
//添加场景数据并添加光源
shadowedScene-&addChild(createLight(node.get()));
shadowedScene-&addChild(node.get());
root-&addChild(shadowedScene.get());
//优化场景数据
osgUtil::O
optimizer.optimize(root.get()) ;
viewer-&setSceneData(root.get());
viewer-&realize();
viewer-&run();
return 0 ;
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:425692次
积分:6776
积分:6776
排名:第1711名
原创:237篇
转载:47篇
评论:172条
★★★★★★★★★★★★★★★★★★
分布式仿真、虚拟现实、工业仿真
Delta3D、OpenGL、OSG
★★★★★★★★★★★★★★★★★★
阅读:6225
文章:14篇
阅读:24020
(1)(2)(2)(4)(5)(1)(3)(1)(2)(5)(4)(4)(3)(3)(27)(3)(3)(1)(8)(5)(3)(1)(5)(1)(11)(13)(12)(5)(7)(4)(6)(2)(4)(1)(1)(5)(13)(18)(5)(20)(12)(22)(6)(7)(3)(11)原文:/shapherd/archive//osg.html
1&OSG基础知识
?&OSG是的缩写,于年诞生于以为滑翔机爱好者之手,为了对滑翔机的飞行进行模拟,对的库进行了封装,的雏形就这样诞生了,年遇到了同样喜欢滑翔机和计算机图形学的,从此加入了小组的开发并一直担任开发小组的组长。
?&OSG不但有的跨平台的特性和较高的渲染性能,还提供了一系列可供程序开发者使用的功能接口,包括和数据文件的加载、纹理字体支持、细节层次()控制、多线程数据分页处理等。广泛应用于飞行仿真等领域,包括,及美国军方投资的仿真项目等
1.1&计算机绘图的基本知识
?&首先要先回顾一下,在显示世界中,我们是如何作画的。
?&在现实世界中,绘制一副画,我们需要的东西就是彩笔、白纸。通过选择不同颜色的彩笔,在白纸上移动,就可以将白纸上的不同的点描绘上不同的颜色,而所有这些点连接起来,从人的宏观视野看来,就构成了一副对人有意义的画作。
?&类比到计算机的实际中来。在计算机的世界里。作画的过程又是怎样的呢?
?&同样,绘制虚拟的图像,也需要“彩笔”和“白纸”。在计算机的世界里,“彩笔”就是之类的绘图函数,而“白纸”就是存储数据的内存。我们在内存中划分出一块区域,其中的数据就是对一个真实世界的模拟。一个数据就描述真实世界中一个点的属性。在我们作画前,他们都只有一个初始值,就像白纸在作画前只有白色一样。而在作画后,每一个数据都有了独特的意义,将整片数据连接在一起看,就是一副有意义的图景。作画的过程就是对内存中的每一个数据进行赋值的过程,相当于用彩笔给白纸上的一个点进行着色。选择不同的函数,可以画出不同的形状。
1.2&OSG程序框架
?&一个最简单的程序如下所示,当然在如果是在下面进行编辑的话要进行一些设置,要设置的和目录。
1 #include&osgDB/ReadFile& 2
3 #include&osgViewer/Viewer& 4
5 &void main() 6
9 osgViewer::V10 11 viewer.setSceneData(osgDB::readNodeFile(&glider.osg&));12 13 viewer.realize();14 15 viewer.run();16 17 }18 &
osgViewer::Viewer&viewer 申请了一个,可以理解为申请一个观察器,该观察可以查看模型
viewer.setSceneData(osgDB::readNodeFile(&glider.osg&)) 这里是设置观察器中的数据,换句话说,有了观察器,就可以添加模型了
viewer.realize() 这个语句表达的意思非常多,事实上可以定位到的realize函数,会发现里面的操作非常多,可以理解为这是在渲染前的最后一步,会检查和设置图形上下文,屏幕啊什么的,会让你以前的设置,对的设置都生效。
viewer.run(); 这一句的意思就是渲染了,如果要解释它的意思的话,可以用下面的几个语句来替代:
while(!viewer.done()){viewer.frame();}.意思也就是说,只要没有结束,那么就绘制它的每一个帧[]。
1.3&OSG简单模型控制
1.3.1&添加模型
在当中模型是使用和来装载在一起的,比如同时需要加入两个模型,模型了模型,各自是一个,那么可以使用以下语句来做到,首先使用一个然后同样,之后要。然后再把添加到当中就可以了。如图所示之间的关系。在这里要申明的是是的父类,在类中都有相应的方法可以转到对方,故与
是通用的,也可以被当作来用。
图&31&AB都加入到Group当中
简单示例代码如下:
1 #include&osgDB/ReadFile& 2
3 #include&osgViewer/Viewer& 4
5 #include&osg/Node& 6
7 &void main() 8
9 {10 11 osgViewer::V12 13 osg::Group * root=new osg::Group();14 15 root-&addChild(osgDB::readNodeFile(&glider.osg&));16 17 root-&addChild(osgDB::readNodeFile(&osgcool.osg&));18 19 viewer.setSceneData(root);20 21 viewer.realize();22 23 viewer.run();24 25 }26 &
则运行结果为:
图&32示例运行结果
1.3.2&删除结点
如果我们不需要某个结点了,比如图我们看那个小飞机很不爽,我们想把它从场景中删除掉。不知道于某种目的,反正现在要删除掉,可能是开始想看见它现在不想看见它了。可以通过方法,除多个孩子也可以通过方法,里面的参数有些需要索引值,有些需要结点本身的指针,读者可以自己尝试。这里要注意的是,如果要删除一个结点,那么该结点下的所有结点都会被删除。如果一个结点被加入到一个组中两次,那么这两次是分别存在的,删除一次还有另一次。删除操作不能说不是个危险的操作,有些时候,尤其在有移动结点等等混在一起时,删除操作有时候会发生一些比较奇怪的现象。在内存映象当中,如果一个模型被读取一次,而用了多次,那么所占用的空间是不会改变的。
1.3.3&隐藏模型与结点开关
?&隐藏模型
&&&隐藏模型其实模型仍在渲染当中,因此损耗并未减少,只不过隐藏了而已,隐藏的确不是个什么好操作,但是有时候对小模型确实也很实用。可以设置隐藏与显示。
?&节点开关
在当中,专门有一个类来负责打开与关闭结点,该类名为,里面有相应的方法来控制它所管理的结点的打开与关闭。
两个方法都能控制模型的显示和隐藏,区别在于隐藏模型方法不会让模型在内存中消失,这样对于小的物体频繁的调用会节省一些时间,而对于有些大的模块在用一次以后可能很久再用第二次,这个时候用节点开关可以将模型销毁,再次使用再调入内存,以防止占用更多的资源。
1.3.4&超级指针
超级指针的机制,其实就是引用一个计数器,这个计数器会计算这个箱子被引用的次数,被别人引用一次这个计数器增加一,别人不用一次,即:释放一次,则计数器减一。当减至时,内存放掉不用。
们来看使用一个的三种方法对比一下:
?&//方法一,最好的方法十分安全,也是中最常用的方法,多少版本它都没变
osg::ref_ptr&osg::Node&aNode(new&osg::Node());
group-&addChild(aNode.get());
?&//方法二,也是非常好的方法,有时候不适用,但也十分安全
group-&addChild(new&osg::Node());
?&//方法三,非常危险,但是令许多人无故铤而走险的方法
osg::Node*anotherNode=new&osg::Node();
group-&addChild(anotherNode);
方法一:在时申请了一个的资源,这时在堆内引用该的计算器会被置。在时又引用了一次,会再加。在这两次引用都结束时,的资源就会被释放。
方法二:这个方法也是很实用的,但是无法引出的指针,也许在别处可以用到,事实上会经常用到。如果已经这样做了,得到指针也不是不可以的,可以使用来得到的指针,也可以使用方法来做这件事。
方法三:这个应该是最常用,但是最烂的方法了,原因在于如果在之后发生了错误,抛出了异常,谁来释放所占用的资源呢。而这个异常在后面被捕获,程序正常的走下去,而内存却没有被正常的放掉。
1.3.5&移动旋转缩放模型
移动旋转缩放其实都是对矩阵进行操作,在当中,矩阵可以当作一个特殊的结点加入到当中,而矩阵下也可以另入结点,而加入的结点就会被这个矩阵处理过,比如移动过旋转过缩放过。在中控制矩阵的类为。
osg::Matrix::translate
osg::Matrix::rotate
osg::Matrix::scale
1.4&基本几何图元
1.4.1&基本绘制方法
首先来看一些中的最基本的绘制路数。如果我们要绘制一个正方形,绘制有色彩,未贴图。首先我们必须要申请一个把这个加入到就可以了。在这个中要设置一些元素,最基本的是顶点颜色以及顶点的关联方式和法线就可以了。如图所示。
图&33几何体绘制过程
1.4.2&可绘制的图元
所有可绘制的图元包括:
?&POINTS[点
?&LINES[线
?&LINE_STRIP[线带
?&LINE_LOOP[闭合线段
?&TRIANGLES[三角形
?&TRIANGLE_STRIP[三角带
?&TRIANGLE_FAN[三角扇
?&QUADS[四方块
?&QUAD_STRIP[四方块带
?&POLYGON[多边形
在中设置直线线宽的专门有一个函数来管理,叫做,它本身属于状态与属性类别中的类。事实上也是从那里派生而来。所有设置状态的操作都与此类似。
1.4.3&内置几何类型
如同一样,同样有一套内置几何类型,这些几何类型都在类中,这些本身都可以本当成一个结点加入到中,然后再人中添加到里进行渲染。形状共有九种,分别为:盒子,胶囊形,组合型,圆锥形,圆柱形,高程形,有限面,球形,三角蒙皮。
内置几何类型的渲染过程如图所示
图&34基本几何图元的添加过程
这里要注意的是,一般的形状态都有特定的因素,比如有长宽,圆有半径,以及各个图形所画的精细度都需要指明,这些精细度在球这样的形状上意义还是十分巨大的。在中有专门指明精细度的类,名为:。以球为例,只需要规定,圆心,半径和精细度就可以画出该球。
1.5.1&交互过程
viewer的主要的功能是控制场景,它是场景的核心类,如果能响应键盘时得到,那么也可以从键盘的响应中控制整个场景。中有一个方法,名为就是专门做这件事情的。他会加入一个事件处理器。于是我们就想,一定要自己写一个事件处理器才行,这就必须要了解事件处理器的格式,只要有一个接口就可以了解它的格式,这个接口就是:,于是我们可以写一个类从该类公有派生出来,即::在里面处理好各种操作然后加入到当中,即:里面可以有参数这样就可以完成操作。
假如类是一个事件处理类,那么加入类可以这样理解,如图:
图&35事件A控制场景过程
1.5.2&事件类型与响应
代码&&&&&&&&&&&&&& & 值&&&&&&&&&   事件类型
NONE&&&&&&&&&&& & &0&&&&&&&&&&&&&   无事件。
PUSH&&&&&&&&&&& &&& &1       鼠标某键按下,在上面代码行有用到。
RELEASE&&&&&  &2       鼠标某键弹起。
DOUBLECLICK&& 4       鼠标某键双击。
DRAG&&&    & 8       鼠标某键拖动。
MOVE      16       鼠标移动。
KEYDOWN&   32       键盘上某键按下。
KEYUP&     64       键盘上某键弹起。
FRAME&     128     应该是鼠标每帧。没用过。
RESIZE&     256     窗口大小改变时会有的事件。
SCROLL&&    512     鼠标轮滚动。
PEN_PRESSURE& 1024     手写板的某事件?
PEN_PROXIMITY_ENTER& 2048   手写板的某事件?
PEN_PROXIMITY_LEAVE & 4096   手写板的某事件?
CLOSE_WINDOWS&   8192   关闭窗口。
QUIT_APPLICATION&   16384   退出程序。
USER&          32768   用户定义。
至于为什么都用的次方,主要是因为它的二进制编码只有一位是一,判断事件时很好判断,只要年哪位是一就可以了。
1.5.3&PICK
pick主要是通过鼠标的点击来拾取一些物体,或者判断鼠标所点击的位置在哪里。Pick实现的思路如下图所示:
图&36pick事件流程
判断射线与中物体相交的方法为发出射线并相交。在中有库函数,他共有三个参数:第一个是屏幕坐标,第二个是屏幕坐标,第三个是存放被交的结点以及相交的坐标结点路径等等相关信息。
判断相交结点为我想要的那个结点:只需要判断存放相交射线交场景的结果集中有没有要用的结点就可以了。
1.6.1&MatrixManipulator
场景的核心管理器是,而漫游必须响应事件,比如鼠标动了,场景也在动。响应事件的类是。我们想把响应事件的类派生一个新类出来,这个类专门用来根据响应控制。这个类就是,这个类有一些设置矩阵的公共接口,有了这些接口就可以有效的控制了,根据不同的习惯,大家还会设置不同的控制方式,如同自带的几个操作器,操作都不尽相同。来看一下漫游的主要流程如图:
图&37一般的场景操作器
操作器必须从派生而来。有四个可以控制场景的重要接口:
1 virtual void setByMatrix(const osg::Matrixd&matrix)=02 3 &virtual void setByInverseMatrix(const osg::Matrixd&matrix)=04 5 &virtual osg::Matrixd getMatrix()const=06 7 &virtual osg::Matrixd getInverseMatrix()const=08 &
四个矩阵接口可以有效的向来传递矩阵的相关信息。
1.6.2&碰撞检测
最简单的碰撞检测如下图所示:
图&38简单的碰撞检测原理图
TravelManipulator.dll中用到的就是如图所示的原理,黑三角形代表没有移动之的位置,控制移动的函数是,参数意思是要移动的相对于当前点的增量,在黑三角形没有移动时该函数在计算时先假设一点为移动后的点,而后通过连接这两个点,而后通过判断与场景的模型是否有交点来判定这个移动可不可以执行,如图所示,两者之间有个大盒子,是穿不过去的,所以只有保持在原地。就算没有这个盒子,移动后的新点又与地面在某种程序上有一个交点,这证明移动是不可行的。这可以防止用户穿过地板到达地下去。
1.6.3&路径漫游
使用文件的方法如下面示例
1 #include&osgDB/ReadFile& 2
3 #include&osgViewer/Viewer& 4
5 #include&osg/Node& 6
7 #include&osgGA/AnimationPathManipulator& 8
9 &void main()10 11 {12 13 osgViewer::V14 15 viewer.setSceneData(osgDB::readNodeFile(&glider.osg&));16 17 &//申请一个操作器,参数为一个path文件。18 &19 osg::ref_ptr&osgGA::AnimationPathManipulator&amp=new osgGA::AnimationPathManipulator(&glider.path&);20 21 &//选择使用这个操作器。22 &23 viewer.setCameraManipulator(amp.get());24 25 viewer.realize();26 27 viewer.run();28 29 }30 31 &
我们可以用路径编辑器编辑文件,或者可以控制程序中的某个物体的运动轨迹然后保存为文件。
1.7&更新&回调
回调的意思就是说,你可以规定在某件事情发生时启动一个函数,这个函数可能做一些事情。这个函数就叫做回调函数,我们可以使用已有回调函数或者自定义回调函数。
?&使用已有回调
已有的回调的类型有很多种,一般很容易就想到的是,或者等
?&自定义回调
自定义回调为从一个回调类型派出生自己的回调,然后具有该种回调的特点等等。
NodeVisitor是一个极有用的类,可以访问结点序列,使用的方法大同小异,的工作流程如下图所示:
图&39NodeVisitor工作流程
在主结点之后,结点数据立即传至中去,应用函数,可以将数据定任一些操作,更多的操作还是需要硬性的制做与调用。
1.8&粒子系统初步
在中提供有专门的粒子系统工具,名字空间为,对经常使用的粒子模拟都做了专门的类,如:用于暴炸的模拟,用于火的模拟,用于爆炸后四散的颗粒模拟等等。
在中使用粒子系统一般要经历以下几个步骤:
第一步:确定意图(包括粒子的运动方式等等诸多方面)。第二步:建立粒子模版,按所需要的类型确定粒子的角度(该角度一经确定,由于粒子默认使用有所以站在任何角度看都是一样的),形状(圆形,多边形等等),生命周期等。第三步:建立粒子系统,设置总的属性,第四步:设置发射器(发射器形状,发射粒子的数目变化等),第五步:设置操作(旋转度,风力等等因素)。第六步:加入结点,更新。下图描述了各个部分是协调工作的方式:
图&310粒子系统各个部分是协调工作的方式
上图中各个部分所对应的类如下图所示
图&311粒子系统各部分对应的类
1.9&视口&LOD&Imposter
1.9.1&多视口
多视口的原理是自己创建所有的相机,包括主相机,这样我们可以随意的添加相机。
首先我们要创建视口必须有以下几件东西,第一,了解整个屏幕的分辩率有多大,这样可以分辩视口的大小,好分割开来。第二,上下文。我们必须自己手动的打开设置上下文。每个视口的数据也不一定非要与主视口的相同。但是矩阵一般是同步的。也就是说:主视口里有栋楼,从视口里可以是平面图什么的。了解整个屏幕的分辩率可以用这个类:意思是说系统接口,可以获得当前环境的各种信息。有一方法叫,可以得到分辩率。之后上下文了,里面可以设置窗口大小,缓存什么的,大多数的东西都在这里面设置。
LOD比起而言并非十分的常用,有个地方用的特别多,那就是把一个好好的模型加一个视矩压成一个模型,这个模型比以前的看来就是多了个视矩的控制,远了看不见,近了能看见。
在模型中加头结点的方式如下所示:
1 #include&osgDB/Registry& 2
3 #include&osgDB/ReadFile& 4
5 #include&osgDB/ReaderWri ter& 6
7 #include&osgDB/Wri teFile& 8
9 #include&osg/Node&10 11 #include&osgViewer/Viewer&12 13 &int main()14 15 {16 17 osgViewer::V18 19 &//读取模型20 &21 osg::Node*node=osgDB::readNodeFile(&fountain.osg&);22 23 &//隐藏结点24 &25 node-&asGroup()-&getChild(0)-&setNodeMask(0);26 27 viewer.setSceneDa ta(node);28 29 &//输出结点到free.os g中30 &31 osgDB::writeNodeFile(*(viewer.getSceneData()),&free.osg&,osgDB::Registry32 33 ::instance()-&getOptions());34 35 &return 0;36 37 }38 39 &
1.9.3&Imposter
用动态图片来替换场景的实用技术:可以把它法做一样使用,只不过它
不是变模型变没有,而是使它换成一张图
示例代码如下:设置一个视矩,超过这个视距模型会变为一张动态图
1 #include&osgViewer/Viewer& 2
3 #include&osgGA/TrackballManipulator& 4
5 #include&osgSim/Impostor& 6
7 #include&osgDB/ReadFile& 8
9 &int main(inta rgc,cha r**a rgv)10 11 {12 13 &//申请viewer14 &15 osgViewer::V16 17 &//读取模型18 &19 osg::Node*node=osgDB::readNodeFile(&ceep.ive&);20 21 &//申请一个i mpos tor结点22 23 osgSim::Impos tor*sim=new osgSim::I24 25 //在到之内显示模型,之外显示贴图26 27 sim-&addChild(node,0,50000);28 29 sim-&setImpostorThreshold(1000);30 31 osg::Group*root=new osg::G32 33 root-&addChild(sim);34 35 viewer.setSceneData(root);36 37 viewer.realize();38 39 viewer.run();40 41 return 0;42 43 }44 45
1.10&文字&模型阴影
1.10.1&HUD&
文字在场景中显示往往要经历以下几步:读取字体点阵信息转化为图像反走样最终图像。在反走样期间可以处理可种模糊效果,在最终图像形成时可以设置如何摆放。中有一个类,提供可很多文字显示的方法,比如
1 void setFont(Font*font=0) 2
3 //设置/得到字体,如setFont(&fonts/SIMYOU.TTF&); 4
5 void setFont(const std::string&fontfile) 6
7 const Font*getFont()const 8
9 //设置/得到字体显示的宽高10 11 void setFontResolution(unsigned int width,unsigned int height)12 13 unsigned int getFontWidth()const14 15 unsigned int getFontHeight()const16 17 //设置/得到文字的具体内容18
等等,可以很方便的调用
1.10.2&阴影
OSG对阴影的支持也相当的好,可以很容易的写出简单的阴影效果,可以参考例子
OSG有一个专门的类来支持阴影效果,提供了很多接口,如:
1 void setBackdropType(BackdropType type) 2
3 //说明:设置阴影类型。 4
5 void setBackdropOffset(float offset=0.07f) 6
7 void setBackdropOffset(float horizontal,float vertical) 8
9 //说明:设置阴影的离开程度与方向10 11 void setBackdropColor(const osg::Vec4&color)12 13 //说明:设置阴影颜色14 15 void setColorGradientMode(ColorGradientMode mode)16 17 //说明:设置颜色映射方式,可以得到渐变效果18
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1649次
排名:千里之外
原创:20篇
转载:15篇
(4)(9)(6)(4)(5)(1)(1)(2)(3)}

我要回帖

更多关于 osg node 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信