OpenSceneGraph三维渲染引擎编程指南 笔记

OSG是对OpenGL的封装。

OSG中的场景树和Ogre中的设计类似。

OSG层次结构:应用程序-OpenSceneGraph-OpenGL-图形硬件

OSG主要包括4个库:

  1. OSG核心库
    osg库:基本数据类,负责提供基本场景图类,构建场景图形节点,如节点类、状态类、绘制类、向量和矩阵数学计算以及一般的数据类。
    osgUtil库:工具类库,提供通用的公用类
    osgDB库:数据的读写库,负责提供场景中数据的读写工作,提供了一个文件工具类
    osgViewer库:是在OSG2.0后逐步发展稳定的一个视窗管理库,可以集中各种窗体系统,提供OSG与各种GUI的结合。因此它是跨平台的3D管理窗口类。

  2. OSG工具库
    osgFX库
    osgParticle库
    osgSim库
    osgTerrain库
    osgText库
    osgShadow库

  3. OSG插件库
    OSG插件库是OSG的一个非常重要的特点。通过各种第三方库的支持,OSG能够直接或间接地导入3D模型或图片等场景数据,可以省去大量绘制图形的工作,从而极大地方便了开发者。

  4. OSG内省库

OSG中智能指针的用法:

1
osg::ref_ptr<Geode> geode = new osg::Geode();

HelloWorld

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <osgViewer/Viewer>
#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgUtil/Optimizer>
int main()
{
// 创建Viewer对象,场景浏览器
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
// 创建场景组节点
osg::ref_ptr<osg::Group> root = new osg::Group();
// 创建一个节点,读取牛的模型
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("cow.osg");
// 添加到场景
root->addChild(node.get());
// 优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
// 设置场景数据
viewer->setSceneData(root.get());
// 初始化并创建窗口
viewer->realize();
// 开始渲染
viewer->run();
}

构建OSG场景渲染程序的开发步骤如下:

  1. 设置编程环境,指定头文件
  2. 创建场景浏览器,通过osgViewer::Viewer类创建一个对象,用于渲染场景
  3. 设置场景数据,从外部加载模型或者场景数据
  4. 设置场景数据,为渲染场景建立场景树,确定场景数据之间的关系
  5. 渲染,进入循环,实现场景渲染

OSG实用工具:
osgViewer 场景浏览器
osgVersion 版本信息查看器
osgArchive 场景图形压缩归档工具
osgConv 数据转换工具

坐标系
(文中貌似有个印刷错误,应该是右手坐标系,不是左手)
OSG采用的世界坐标系是右手坐标系,这点与OpenGL保持一致。

OSG: X正方向向右,Y正方向朝里,Z正方向朝上
OpenGL: X正方向向右,Y正方向朝上,Z正方向朝外
D3D: X正方向向右,Y正方向朝上,Z正方向朝里(右手坐标系)
Ogre: X正方向向右,Y正方向朝上,Z正方向朝外(同OpenGL)

常用的数据工具主要包括向量、矩阵及四元数等。

OSG定义了大量的类,用于保存向量数据,如顶点、法线、颜色和纹理坐标等。

osg::Vec3是一个三维浮点数组,可以用来保存顶点和法线数据
osg::Vec4用于保存颜色数据
osg::Vec2可用于保存2D纹理坐标

矩阵类:
osg::Matrix2
osg::Matrix3
osg::Matrixd
osg::Matrixf

四元数:
osg::Quat

在OSG中存在两棵树,即场景树和渲染树。场景树是一颗由Node组成的树,这些Node可能是矩阵变换、状态切换或真正的可绘制对象,它反映了场景的空间结构,也反映了对象的状态。

场景树通常包括了多种类型的节点,以执行各种各样的用户功能,例如开关节点可以设置其子节点可用或不可用:细节层次(LOD)节点可以根据观察者的距离调用不同的子节点;变换节点可以改变子节点几何体的坐标变换状态。

Geode节点:英文全称Geometry Node,几何体节点。

osg::Geode是OSG中的叶节点,它用于保存几何信息以便渲染。同时,作为叶节点,它就不会再包含子节点。在应用程序中,所有相关的几何体的渲染都必须与Geode节点相关联。在osg::Geode类中,也提供了addDrawable()函数来关联应用程序中需要渲染的几何体信息。

Billboard节点
osg::Billboard用于实现布告板。

Group节点
osg::Group允许用户程序为其添加任意数量的子节点。

其他类型的节点:
osg::PositionAttitudeTransform
osg::MatrixTransform
osg::AutoTransform 使节点自动对齐于摄像机或者屏幕
osg::Switch
osg::LOD
osg::PagedLOD
osg::OccluderNode
osg::CoordinateSystemNode
osgSim::Impostor

在OSG中,对于一个LOD模型,它是一次性载入内存的,只是有选择地渲染。

osg::PagedLOD与osg::LOD的区别是,osg::PagedLOD的每个节点都是磁盘中的文件,可以根据需要来加载这些文件,加载过程中有单独的线程负责实时调度和加载。

osg::PagedLOD节点主要用于处理大规模的数据,在地形和GIS方面有广泛的应用。可以把模型进行预处理,在渲染场景时,再根据需要来实时加载需要的数据及卸载无用的数据。

场景中节点的拷贝:osg::CopyOp类

OSG中绘制几何体的方法:

  1. 使用松散封装的OpenGL绘图基元
  2. 使用OSG中的基本几何体
  3. 从文件中导入场景模型

使用松散封装的OpenGL绘图基元绘制几何体具有很强的灵活性,但工作量非常大,当面对大型场景时,绘制几何体将是一项非常艰巨而富有挑战的工作,因此,通常还是采用读入外部模型的方法。

osg::PrimitiveSet类封装了OpenGL的绘制基元

osg::Geometry继承自osg::Drawable,可以自由设置几何体的顶点等信息构建模型。

OSG中预定义的几何体:
osg::Shape的子类,osg::Box等。

渲染树
渲染树是一棵以StateSet和RenderLeaf为节点的树,它可以做到StateSet相同的RenderLeaf同时渲染而不用切换OpenGL状态,并且做到尽量少但在多个不同State间切换。

用户的应用程序需要在osg::StateSet中设置渲染状态。可以将StateSet关联到场景图形中的任意一个节点(Node)或关联到Drawable类。

OSG将渲染状态分成两个部分,分别为渲染属性(Attribute)和渲染模式(Mode)。渲染属性也就是控制渲染特性的状态变量,如雾的颜色或Blend融合函数都是OSG的状态属性。OSG中的渲染模式和OpenGL的状态特性几乎是一一对应的,这些特性在OpenGL中通过函数glEnable()和glDisable()进行控制。用户程序可以设置模式量以允许或者禁止某个功能,如纹理映射、灯光等。简单来说,渲染模式是指渲染的某个功能,而渲染属性是这个功能的控制变量和参数。

例子:

实现面剔除

1
2
3
osg::StateSet* state = geom->getOrCreateStateSet();
osg::CullFace* cf = new osg::CullFace(osg::CullFace::BACK);
state->setAttribute(cf);

打开雾效模式

1
2
osg::StateSet* state = geom->getOrCreateStateSet();
state->setMode(GL_FOG, osg::StateAttribute::ON);

纹理映射

创建纹理映射的步骤:

  1. 指定用户几何体的纹理坐标
  2. 创建纹理属性对象并保存纹理图形数据
  3. 为StateSet设置合适的纹理属性和模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建一个几何体对象
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
// 纹理坐标
osg::ref_ptr<osg::Vec2Array> tc = new osg::Vec2Array;
geom->setTextCoordArray(0, tc.get());
tc->push_back(osg::Vec2(0.0f, 0.0f);
tc->push_back(osg::Vec2(1.0f, 0.0f);
tc->push_back(osg::Vec2(1.0f, 1.0f);
tc->push_back(osg::Vec2(0.0f, 1.0f);
// 纹理
osg::ref_ptr<osg::Image> image = new osg::Image;
image->setFileName("tree.rgb");
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
tex->setImage(image.get());
tex->setUnRefImageDataAfterApply(true);
// 纹理状态
state->setTextureAttribute(0, tex.get());

光照
OSG将OpenGL中的glLight()作了一个light状态的类封装,用于保存灯光的模式与属性参数信息。

在osg::light类中通过apply()函数将灯光的状态参数信息应用到OpenGL的状态机中。

osg::LightSource类直接继承自osg::Group。作为一个灯光管理类,继承了osg::Group类的管理节点的接口,将灯光作为一个节点可以加入到场景图中进行渲染。

在一个场景中添加光源主要包括以下步骤:

  1. 指定场景模型的法线
  2. 允许光照并设置光照状态
  3. 指定光源属性并关联到场景图形

材质类(osg::Material)继承自osg::StateAttribute类。osg::Material封装了OpenGL的glMaterial()和glColorMaterial()指令的函数功能。

在场景中设置节点的材质属性,首先要创建一个sog::Material对象,然后设置颜色和其他参数,再关联到场景图形的StateSet中。

文件的读写

基本几何体的绘制只适用于简单的编程,当场景中需要加载一个很复杂的模型时,还是需要从外部导入。osgDB库提供了读取二维图像和三维模型的接口,同时,也管理着第三方插件系统,以实现对不同格式文件的读取。

由于OSG包含庞大的第三方插件库,所以OSG支持的文件格式也非常多,如各种三维模型、图片和视频等文件,这也是OSG的一大优势,它可以满足各行各业的需求。

文件的读取:

1
2
3
4
#include <osgDB/ReadFile>
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("cow.osg");
osg::ref_ptr<osg::Image> iamge = osgDB::readImageFile("1.rgb");

文件的保存:

1
2
3
#include <osgDB/WriteFile>
osgDB::writeNodeFile(*node, "saved.osg");

智能指针
要使用OSG的智能指针,用户的类必须派生自Referenced类。

访问器模式(参考设计模式书)
osg::NodeVisitor类是对设计模式Visitor中的设计思想的具体实现。osg::NodeVisitor类继承自osg::Reference类。

回调机制
osg::NodeCallback类