转自:http://blog.****.net/timothyfly/article/details/7826139
osg格式文件中如何处理多个节点共享一个子节点
下面一段程序中,共有三个Group类型根节点:root,grp1和grp2;两个Geode类型节点:geode1和geode2。他们之间的从属关系是:grp1,grp2和geode2是root的孩子,geode1和geode2是grp1的孩子,geode2是grp2的孩子。
.osg格式的文件存储像geode2这个的节点时,采用'Use'这个关键词。
---------------------------------------------------------------------------------------------
.osg文件内容:
Group {
name "root"
nodeMask 0xffffffff
cullingActive TRUE
num_children 3
Group {
UniqueID Group_0
name "grp1"
nodeMask 0xffffffff
cullingActive TRUE
num_children 2
Geode {
UniqueID Geode_1
name "geode1"
nodeMask 0xffffffff
cullingActive TRUE
num_drawables 1
Geometry {
useDisplayList TRUE
useVertexBufferObjects FALSE
PrimitiveSets 1
{
DrawArrays LINE_STRIP 0 3
}
VertexArray Vec3Array 3
{
0 1 0
0 0 0
1 1 0
}
}
}
Geode {
UniqueID Geode_2
name "geode2"
nodeMask 0xffffffff
cullingActive TRUE
num_drawables 1
Geometry {
useDisplayList TRUE
useVertexBufferObjects FALSE
PrimitiveSets 1
{
DrawArrays LINE_STRIP 0 3
}
VertexArray Vec3Array 3
{
1 0 0
2 1 0
2 0 0
}
}
}
}
Group {
UniqueID Group_3
name "grp2"
nodeMask 0xffffffff
cullingActive TRUE
num_children 1
Use Geode_2
}
Use Geode_2
}
-----------------------------------------------------------------------------------------
osg程序代码:
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osg/Point>
#include <osg/LineWidth>
#include <osgDB/WriteFile>
// 根据一组点集生成一个Geode对象
osg::ref_ptr< osg::Geode > createProfileGeode( int size, osg::Vec3 *points )
{
osg::ref_ptr< osg::Geode > geode = new osg::Geode();
osg::ref_ptr< osg::Geometry > pointsGeom = new osg::Geometry();
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array( size, points);
pointsGeom->setVertexArray(vertices);
pointsGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP,0,vertices->size()));
geode->addDrawable(pointsGeom);
return geode.release();
}
int main( int argc, char** argv )
{
osg::Vec3 myCoords1[]=
{
osg::Vec3(0,1,0),
osg::Vec3(0,0,0),
osg::Vec3(1,1,0)
};
osg::Vec3 myCoords2[]=
{
osg::Vec3(1,0,0),
osg::Vec3(2,1,0),
osg::Vec3(2,0,0)
};
osg::ref_ptr< osg::Geode > geode1 = createProfileGeode( sizeof(myCoords1)/sizeof(myCoords1[0]), myCoords1 );
osg::ref_ptr< osg::Geode > geode2 = createProfileGeode( sizeof(myCoords2)/sizeof(myCoords2[0]), myCoords2 );
geode1->setName("geode1");
geode2->setName("geode2");
osg::ref_ptr<osg::Group> root = new osg::Group;
osg::ref_ptr<osg::Group> grp1 = new osg::Group;
osg::ref_ptr<osg::Group> grp2 = new osg::Group;
root->setName("root");
grp1->setName("grp1");
grp2->setName("grp2");
grp1->addChild(geode1);
grp1->addChild(geode2);//geode2的第一个父节点
grp2->addChild(geode2);//geode2的第二个父节点
root->addChild( grp1 );
root->addChild( grp2 );
root->addChild(geode2);//geode2的第三个父节点
osgDB::writeNodeFile( *root, "test.osg" );
osgViewer::Viewer viewer;
viewer.setSceneData( root.get() );
return viewer.run();
}
参考:http://blog.****.net/gelu1231/article/details/6655679
我们以将节点写入文件为例,节点读取与此相同,看看究竟是如何将节点写入文件的,我们从文件读写插件的最顶层入口函数来看,即
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我们就可以实现对文件内容的压缩和解压缩,或者加密和解密,或者加入自己的头信息等等,都非常方便;
转一个别人细读cow.osg的
对,就是那只著名的奶牛。
//Group节点,可有子节点。
Group {
UniqueID Group_0 //Gourp名称
DataVariance STATIC //不知道用来干嘛,一般都是static
cullingActive TRUE //参与culling?
num_children 1 //子节点数
Geode { //子节点是Geode节点(Geode节点是叶节点,它不会再有子节点,可以与任意多个Drawable的对象关联)
DataVariance DYNAMIC
name "cow.osg" //Geode名称,应该是模型路径吧
cullingActive TRUE //参与culling
num_drawables 1 //可绘制元素Drawable对象的数目,1
Geometry { //Drawable :Gemetry,是可绘制对象类型之一。用指定顶点数据,绘制几何体。
DataVariance DYNAMIC
StateSet { //渲染状态。分为渲染属性和渲染模式两部分。是个状态值,直到子节点重新设置前,它的值一直沿节点树向下有效。
DataVariance STATIC
rendering_hint OPAQUE_BIN //和渲染有关的
renderBinMode INHERIT //和渲染有关的
GL_CULL_FACE OFF //和裁剪有关的
GL_LIGHTING ON //和裁剪有关的
Material { //材质
DataVariance STATIC
ColorMode OFF //光的啥啥
ambientColor 0.5 0.5 0.5 1 //环境光
diffuseColor 1 1 1 1 //漫反射
specularColor 1 1 1 1 //镜面反射
emissionColor 0 0 0 1 //自发光
shininess 1 //????????
} ////材质结束
textureUnit 0 { //纹理单元,1,可以多个。序数依次。
GL_TEXTURE_GEN_S ON //啥啥啥???
GL_TEXTURE_GEN_T ON //啥啥啥???
GL_TEXTURE_2D ON //啥啥啥???
Texture2D { //2D的贴图
DataVariance STATIC
file "Images/reflect.rgb" //贴图文件
wrap_s REPEAT //repeat嘛,很容易懂
wrap_t REPEAT
wrap_r REPEAT
min_filter NEAREST_MIPMAP_LINEAR //什么filter
mag_filter LINEAR //另一个filter
internalFormatMode USE_IMAGE_DATA_FORMAT //??
subloadMode OFF //??
}////Texture结束
TexGen { //贴图坐标?
DataVariance STATIC
mode SPHERE_MAP //啥啥球面?——"环境反射贴图,选球面就可以了"?
}////贴图坐标结束
}////纹理单元1结束
}////Geometry的stateset结束
useDisplayList TRUE //显示模型的列表????
Primitives 1 //貌似是Gemotry的图元信息
{
DrawArrayLengths TRIANGLE_STRIP 0 984 //画几何面?985个?
{
3
// 略一堆面?的数值。。。3是指3边形么?@v@
}
} ////primitives完
VertexArray 7772 //顶点坐标
{
// 略一堆表示顶点的数值。
0.701499 2.00244e-05 0.71267
0.501693 4.00296e-05 0.865046
0.465203 0.372921 0.802818
}
NormalBinding PER_VERTEX //法向量绑定方式,每个顶点
NormalArray 7772 //法向量数组
{
0.254622 -0.918791 -0.301648
0.440603 -0.85537 -0.272417
0.244499 -0.920072 -0.306084
}
ColorBinding OVERALL //Geometry的颜色绑定,啥意思我也不知。
ColorArray Vec4Array 1 //颜色的值
{
0.8 0.8 0.8 1
}
TexCoordArray 0 Vec2Array 7772 //应该是修饰顶点的啥东西=.=
{
0 0
// 略一堆。。。。。。数值。
}
}////Geometry完
}////Geode完
}////Group完
1 总结一下
一个根节点Group,名字叫Group0,它的属性和参数包括:
UniqueID
DataVariance
cullingActive
num_children
1个Geode
Geode,名字叫"cow.osg",它的属性参数包括
DataVariance
name "cow.osg"
cullingActive
num_drawables
1个Geometry
这1个Geometry的属性参数包括
DataVariance
StateSet
useDisplayList
Primitives
VertexArray
NormalBinding
NormalArray
ColorBinding
ColorArray
TexCoordArray
其中StateSet又有
DataVariance STATIC
rendering_hint
renderBinMode
GL_CULL_FACE
GL_LIGHTING
Material
1个textureUnit
Material和textureUnit又有各自的blablabla.....