最近在实现三维模型文件之间的格式转换,发现网上的相关资料少之又少,所以开始按照《OpenSceneGraph快速入门指导》这本书学习一些内容,以下是相关知识整理。小白开头好难,好事多磨吧。
1 场景图形的创建
1.1 OSG内存管理
在应用中,程序保存一个指向根节点的指针,但是不保存三维场景内其他节点的指针,通过根节点进行场景内其他节点的引用。例如:
osg::ref_ptr<osg::Node> model = osgDB::readNodeFile(fileName);
其中model是一个跟节点。示意图如下:
对于内存回收,OSG提供了一个自动废弃物系统,通过释放指向根节点的指针,就会将整个场景内的所有节点上的内存都会释放。内存回收示意图如下:
1.1.1 reference类
所有OSG的节点和场景图形数据,包括状态信息、顶点数组、法线和纹理坐标均派生自该类。该类实现对内存区段的引用计数,其作用在于进行内存的释放。
1.1.2 ref_ptr模板类
这里着重记录ref_ptr模板类,在实际应用中重要。
ref_ptr<>用于实现指向reference类的智能指针。ref_ptr<>保证了在错误的调用堆栈展开时,对象内存也可以被正确的释放。
ref_ptr<>模板类,有三个主要的组成部分:
1)_ptr,私有指针用于保存和管理内存地址,可用get()返回_ptr的值。
2)operator()和operator->()。
3)valid(),用于验证ref_ptr()是否为空,不为NULL时返回TRUE。
1.1.3 内存管理示例
//C++方式构建新的osg::Geode对象,不会将其计数加一
osg::Geode *geode = new osg::Geode;
//将geode的地址赋予一个ref_ptr<>的指针对象grp
grp->addchild(geode);
上述示例通过将新建的geode地址赋予grp,实现了内存的管理,这样grp内存释放时,geode的内存也会自动被释放。如果不这样操作,geode将无法释放从而引起内存泄漏。因为在这里,即便是采用C++智方式创建的指针geode,也无法按照delete[] geode这样的显式方式将内存释放。
1.2 叶节点(Geode)和几何信息
以下代码创建了一个四边形。下面内容将对
#include <osg/Geode>
#include <osg/Geometry>
osg::ref_ptr<osg::Node> createSceneGraph()
{
// 创建一个用于保存几何信息的对象
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
// 创建四个顶点的数组
osg::ref_ptr<osg::Vec3Array> v = new osg::Vec3Array;
geom->setVertexArray( v.get() );
v->push_back( osg::Vec3( -1.f, 0.f, -1.f ) );
v->push_back( osg::Vec3( 1.f, 0.f, -1.f ) );
v->push_back( osg::Vec3( 1.f, 0.f, 1.f ) );
v->push_back( osg::Vec3( -1.f, 0.f, 1.f ) );
// 创建四种颜色的数组
osg::ref_ptr<osg::Vec4Array> c = new osg::Vec4Array;
geom->setColorArray( c.get() );
geom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
c->push_back( osg::Vec4( 1.f, 0.f, 0.f, 1.f ) );
c->push_back( osg::Vec4( 0.f, 1.f, 0.f, 1.f ) );
c->push_back( osg::Vec4( 0.f, 0.f, 1.f, 1.f ) );
c->push_back( osg::Vec4( 1.f, 1.f, 1.f, 1.f ) );
// 为唯一的法线创建一个数组
osg::ref_ptr<osg::Vec3Array> n = new osg::Vec3Array;
geom->setNormalArray( n.get() );
geom->setNormalBinding( osg::Geometry::BIND_OVERALL );
n->push_back( osg::Vec3( 0.f, -1.f, 0.f ) );
// 由保存的数据绘制四个顶点的多边形
geom->addPrimitiveSet(new osg::DrawArrays( osg::PrimitiveSet::QUADS, 0, 4 ) );
// 向 Geode 类添加几何体( Drawable)并返回 Geode
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable( geom.get() );
return geode.get();
}
1.2.1 Geometry概述
向量和数组类
OSG定义了大量的类用于保存对象和数组,例如Vec2,Vec3,和Vec4分别用来保存贴图坐标、顶点或法线坐标、颜色数据。
OSG还提供了对向量数据数组的一系列类型定义,包括:OSG::Vec2Array、OSG::Vec3Array、OSG::Vec3Array。
这些类继承自std::vector,所以可以采用push_back将vec2这样的对象放进Vec2Array这样的数组。
Drawable类
OSG::Drawable类用于保存要渲染的数据。其派生出了三个子类,其中包括OSG::Geometry类。Geometry是使用最广泛最灵活的子类之一。
在1.2开始的四边形创建代码中,Geometry类的方法包括:
1)setVertexArray()、setNormalArray()setColorArray()。其中setVertexArray()、setNormalArray()传入的是Vec3Array的指针,而setColorArray()传入的是Vec4Array()指针,这是因为顶点和法线的数据都是mX3的,而颜色数据则是mX4的。
2)setColorBinding()和 setNormalBinding() - 这些方法用于设置 Geometry 类中颜色和法线数据的绑定方式。其输入参数为 Geometry 类的枚举量。四边形创建代码中,颜色绑定方式为osg::Geometry::BIND_PER_VERTEX,即每 种 颜 色 对 应 一 个 顶 点 。 而 法 线 的 绑 定 方 式 为osg::Geometry::BIND_OVERALL,即整个 Geometry 几何体对应唯一的一个法线数据。
3)addPrimitiveSet(), 这个方法用于设置 Geometry 类数据渲染的方法。其参数为一个 osg::PrimitiveSet 指针。 PrimitiveSet 是一个无法直接实例化的虚基类。一个 Geometry 类可以添加多个 PrimtiveSet 对象。
Geode节点
Geode是OSG的叶节点,保存几何信息以用于渲染,作为叶节点不会再有子节点。
Geode提供了addDrawable进行关联几何信息。Geode::addDrawable()使用Drawable类型的指针作为输入参数。因为Geometry也是派生自Drawable类,所以Geometry类型的指针可以传入addDrawable()。
1.3 Group节点
1.3.1 子接口
Group作为组节点,可以有任意多个子节点,如下图。是由Reference类派生的。Group定义了针对子节点的接口。大多数OSG的子节点均派生自Group,但是Geode是一个例外因为Geode派生自Node。
Group使用std::vector<ref_ptr<Node>>
保存所有子节点的指针,这是一个ref_ptr<>模板的Node类数组,因此可以通过索引访问某一个子节点。
下边是Group关于子节点的接口部分声明:
class Group : public Node {
public:
...
// 添加一个子节点。
bool addChild( Node* child );
// 删除一个子节点,
// 如果输入节点不是子节点,那么放弃操作并返回 false。
bool removeChild( Node* child );
// 用新的子节点替换一个子节点。
bool replaceChild( Node* origChild, Node* newChild );
// 返回子节点的数目。
unsigned int getNumChildren() const;
// 如果指定节点是一个子节点,那么返回 true。
bool containsNode( const Node* node ) const;
...
};
创建一个简单的只含有一个Group父节点和两个Geode子节点的场景如下:
osg::ref_ptr<osg::Group> root = new osg::Group;
osg::ref_ptr<osg::Geode> geode1 = new osg::Geode;
root->addChild(geode1.get());
osg::ref_ptr<osg::Geode> geode2 = new osg::Geode;
root->addChild(geode2.get());
1.4 文件I/O
应用程序通常需要读取和显示模型,这就需要一个函数读取文件中的模型并返回场景信息。
osgDB提供了用户读取和写入模型的函数接口。
1.4.1 接口
用户需要采用连个头文件将接口包含进程序,头文件如下:
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
读取文件
用户采用osgDB::readNodeFile()和 osgDB::readImageFile()来读取 3D 模型和2D 图像文件。string filename是文件名。
osg::Node* osgDB::readNodeFile( const std::string& filename );
osg::Image* osgDB::readImageFile( const std::string& filename );
使用readNodeFile()读取3D模型文件,OSG将根据文件扩展名识别模型文件的具体格式,并自动调用相应的插件将文件转换到场景图形中。readNodeFile返回一个指向场景图形节点的指针。
写入文件
OSG采用writeNodeFile()和writeImageFile()写入文件到路径。
bool osgDB::writeNodeFile( const osg::Node& node,
const std::string& filename );
bool osgDB::writeImageFile( const osg::Image& image,
const std::string& filename );
文件格式支持
可到OSGWiKi查看OSG库支持的最新文件格式。