2、《OSG快速入门指导》详细笔记1

1、OSG详细介绍

1.1 OSG库安装目录

OSG安装目录是在编译源码时指定的,会将相关的结果向这个目录输出,方便直接获取,设置环境变量等。
在这里插入图片描述
在bin目录中OSG提供了可以直接运行的执行程序,他们都有各自的功能。
在这里插入图片描述

1.2 osgviewer程序的使用

通过osgviewer cow.osg可以查看cow.osg这个3D模型,也可以查看图片,使用osgviewer --image xxx.png。在查看3D模型时,有多种显示模式,通过按键即可来回切换。osgviewer 中有相当一部分按键用于指定不同的显示模式,以控制读入模型的外观。以下列出了部分常用的控制命令。
1、多边形模式(Polygon mode):反复按下“W”键(小写 w)可以在线框模式,点模式和填充多边形渲染模式之间切换。
2、贴图映射(Texture mapping):按下“T”键(小写 t)可以切换显示或不显示模型的贴图。
3、光照(Lighting):按下“L”(小写 l)键决定禁止或者允许光照。
4、背面拣除(Backface culling):按下“B”(小写 b)键触发或禁止背面拣除。对于 cow,osg 中的模型而言,此选项不会改变其外观,但是它可能影响其它一些模型的外观和渲染性能。
5、全屏幕模式(Fullscreen mode):按下“F”键(小写 f)切换全屏幕渲染和窗口渲染。

除了以上的显示模式可以切换,还可以显示统计信息,循环按下S键或小写 s可以显示多种数据。
在这里插入图片描述
显示器的刷新率为 60Hz,因此每一帧将占用一秒的 1/60,即大约 16.67 毫秒。上面的显示图表说明了场景更新(update),拣选(cull)和绘制(draw)过程所花费的时间,单位是毫秒。这种反馈方式从本质上提供了分析性能问题并帮助解决程序渲染性能瓶颈的途径。
每一帧的图像都是绘制出来,其中的拣选是指选择能够在视图中看到的图像,然后将其绘制出来。此外,还有关于图元等信息数据。
在这里插入图片描述
对于一个模型的操作,比如缩放、旋转和移动等操作,我们可以将其保存下来,该行为称为动画保存
开发者往往需要反复地进行测试以便有效地调整和检测程序渲染性能。为了简化性能调整的过程,osgviewer 允许用户轻松地记录摄像机的运动序列并且进行回放。这一序列被叫做动画路径(animation path)。运行 osgviewer 时,按下Z键或小写 z将立即开始记录动画路径。此时如果使用鼠标旋转和缩放模型,OSG 都将记录摄像机运动的信息。然后,按下Shift + z键或大写 Z将停止动画路径的记录,并立即开始回放。回放的过程中,你将会看到所有被记录下来的摄像机运动路径。
使用 Esc 键退出 osgviewer,在当前目录下可以得到一些新的文件。其中有一个名为 saved_animation.path 的文件,正如其名称所示,包含了记录下来的动画路径。用户按下“Z”键(小写 z)时,osgviewer 将信息写入此文件。你可以使用如下的命令回放此动画路径的内容:
osgviewer –p saved_animation.path cow.osg

1.3 场景图形初步

场景图形采用一种自顶向下的,分层的树状数据结构来组织空间数据集,以提升渲染的效率。如下图描述了一个抽象的场景图形,其中包含了地形,奶牛和卡车的模型。渲染一个包含了地形、奶牛和卡车的场景时,场景图形采用了一个根节点与三个子节点的形式。每个子节点都包含了用于绘制对象的几何信息。
在这里插入图片描述
场景图形树结构的顶部是一个根节点。从根节点向下延伸,各个组节点中均包含了几何信息和用于控制其外观的渲染状态信息。根节点和各个组节点都可以有零个(有零个子成员的组节点事实上没有执行任何操作)或多个子成员。在场景图形的最底部,各个叶节点包含了构成场景中物体的实际几何信息。OSG 程序使用组节点来组织和排列场景中的几何体
场景图形通常包括了多种类型的节点以执行各种各样的用户功能,例如,开关节点可以设置其子节点可用或不可用,细节层次(LOD)节点可以根据观察者的距离调用不同的子节点,变换节点可以改变子节点几何体的坐标变换状态。面向对象的场景图形使用继承的机制来提供这种多样性,所有的节点类都有一个共有的基类,同时各自派生出实现特定功能的方法。
场景图形是一种中间件(middleware),这类开发软件构建于底层 API 函数之上,提供了高性能 3D 程序所需的空间数据组织能力及其它特性。
在这里插入图片描述

场景图形除了提供底层渲染 API 中具备的几何信息和状态管理功能之外,还兼具如下的附加特性和功能:
1、空间结构:场景图形所采用的树状数据结构更直观,也更符合人们一般理解中的空间事物的排布和组织结构。
2、场景拣选:使用本地 CPU 的投影剔除(frustum culling)和隐藏面剔除occlusion culling)来减少系统总体负担,其基本原理是,在最终渲染图像时忽略对不会显示的几何体的处理
3、细节层次(LOD):使用几何体包围盒计算观察者与物体的距离,使得用户可以更高效地渲染处于不同细节层次上的物体。并且,实时的,场景中进入指定观察距离的那部分对象将从磁盘中载入,而它们一旦超出这一距离时,将从内存中被移除。比如,加载地图时,随着不断拉近,看到的细节也会越来越多。
4、透明:要实现透明或半透明几何体的正确和高效的渲染,需要首先渲染所有不透明的几何体,再渲染透明几何体。而且,透明几何体必须按照深度排序并按照“从后向前”的顺序渲染。场景图形一般都会提供上述这些操作。
5、状态改动最少化:为了最大限度地提升程序性能,应该避免冗余和不必要的状态改变。场景图形会按状态对几何体进行排序以最小化状态改动,OpenSceneGraph 的状态管理工具则负责消除冗余的状态改变。
6、文件 I/O:场景图形可以高效地读写磁盘上的 3D 数据集。在将数据读入内存之后,应用程序可以方便地通过内建的场景图形数据结构操控动态3D 数据。场景图形也是一个高效的文件格式转换工具。
7、更多高性能函数:除了底层 API 提供的基础函数之外,场景图形库还提供了高效能的功能函数,例如全特性的文字支持,渲染特效的支持(例如粒子特效,阴影),渲染优化,3D 模型文件读写的支持,并支持对跨平台的输入、渲染及显示设备的访问。

几乎所有的 3D 程序都需要其中的部分特性。因此,直接使用底层 API 来构建程序的开发者不得不自行在程序中实现其中的某些性能,从而增加了研发的时间、人力及资金投入。使用现有的、支持上述特性的场景图形库,将有助于实现快速的程序开发工作。

场景图形渲染方式
为了实现动态的几何体更新,拣选,排序和高效渲染,场景图形需要提供的不仅仅是简单的绘图遍历:事实上,有三种需要遍历的操作:
1、更新(update):更新遍历(有时也称作程序遍历)允许程序修改场景图形,以实现动态场景。更新操作由程序或者场景图形中节点对应的回调函数完成。例如,在飞行模拟系统中,程序可以使用更新遍历来改变一个飞行器的位置,或者通过输入设备来实现与用户的交互。
2、拣选:在拣选遍历中,场景图形库检查场景里所有节点的包围体。如果一个叶节点在视口内,场景图形库将在最终的渲染列表中添加该节点的一个引用。此列表按照不透明体与透明体的方式排序,透明体还要按照深度再次排序。
3、绘制:在绘制遍历中(有时也称作渲染遍历),场景图形将遍历由拣选遍历过程生成的几何体列表,并调用底层 API,实现几何体的渲染。
在这里插入图片描述
渲染一个场景图形需要三个遍历过程。(a)更新遍历,修改几何体、渲染状态,或者节点参数,保证场景图形的更新对应当前帧;(b)拣选遍历,检查可见性,将几何体和状态量置入新的结构(在 OSG 中称为渲染图形,render graph)之中。(c)绘制遍历,遍历渲染图形并向图形硬件设备发出绘制指令。
三种遍历操作在每一个渲染帧中只会执行一次。但是,有些渲染特例需要将同一个场景(不同或相同部分)在多个视口中进行同步显示,例如立体化渲染和多屏显系统的实现。在这种情况下,每一帧的更新遍历将只会执行一次但拣选和绘制遍历在每个视口内均执行一次(对于简单的立体化渲染,每帧执行两次;对于多屏显系统,每块显卡每帧执行一次)。这样多处理器和多显卡的系统就可以实现并行场景图形处理。其中,拣选遍历必须为只读操作以允许多线程下的数据访问。

1.4 OSG组件

OSG 运行时文件由一系列动态链接库(或共享对象)和可执行文件组成。这些链接库可分为以下五大类:
1、OSG 核心库。它提供了基本的场景图形和渲染功能,以及 3D 图形程序所需的某些特定功能实现。
2、NodeKits。它扩展了核心 OSG 场景图形节点类的功能,以提供高级节点类型和渲染特效。
3、OSG 插件。其中包括了 2D 图像和 3D 模型文件的读写功能库。
4、互操作库。它使得 OSG 易于与其它开发环境集成,例如脚本语言 Python和 Lua。
5、不断扩展中的程序和示例集。它提供了实用的功能函数和正确使用 OSG的例子。

OSG 核心库提供了应用程序和 NodeKits 所需的功能模块。而 OSG 核心库和 NodeKits 一同组成了 OSG 的 API。OSG 核心库中的 osgDB 则通过对 OSG 插件的管理,为用户提供了 2D 和 3D 文件I/O 的接口。
在这里插入图片描述

1.4.1 osg核心库

OSG 核心库提供了用于场景图形操作的核心场景图形功能、类和方法;开发 3D 图形程序所需的某些特定功能函数和编程接口;以及 2D 和 3D 文件 I/O 的OSG 插件入口。OSG 核心库包含了以下几个链接库:
1、osg 库:osg 库包含了用于构建场景图形的场景图形节点类,用作向量和矩阵运算的类,几何体类,以及用于描述和管理渲染状态的类。osg 库中还包括 3D 图形程序所需的典型功能类,例如命令行参数解析,动画路径管理,以及错误和警告信息类。
2、osgUtil 库:osg 工具库包括的类和函数,可以用于场景图形及其内容的操作,场景图形数据统计和优化,以及渲染器的创建。它还包括了几何操作的类,例如 Delaunay 三角面片化(Delaunay triangulation),三角面片条带化(triangle stripification),纹理坐标生成等。
3、osgDB 库:此链接库包括了建立和渲染 3D 数据库的类与函数。其中包括用于 2D 和 3D 文件读写的 OSG 插件类的注册表,以及用于访问这些插件的特定功能类。osgDB 数据库分页机(database pager)可以支持大型数据段的动态读入和卸载。
4、osgViewer 库:这个库是 OSG 的 2.0 版本新增的,它包含了场景中视口及可视化内容的管理类。osgViewer 已将 OSG 集成到多种多样的视窗系统中。
5、osgGA库:用于改写界面事件库。
一、场景管理类
1、Node:Node 类是场景图形中所有节点的基类。它包含了用于场景图形遍历、拣选、程序回调,以及状态管理的方法
2、Group:Group 类是所有可分支节点的基类。它是场景图形空间组织结构
的关键类。
3、Geode:Geode 类(即 Geometry Node)相当于 OSG 中的叶节点。它
有子节点,但是包含了 osg::Drawable 对象
,而 osg::Drawable 对象中存
放了将要被渲染的几何体

4、LOD:LOD 类根据观察点与图像子节点的距离选择显示子节点。通常使
用它来创建场景中物体的多个显示层级。
5、MatrixTransform:MatrixTransform 类包含了用于实施子节点几何体空间
转换的矩阵,以实现场景对象的旋转、平移、缩放、倾斜、映射等操作。
6、Switch用布尔掩板来允许或禁止子节点的运作

二、几何体类
Geode 类是 OSG 的叶节点,它包含了渲染用的几何数据。使用以下列出的类可以实现 Geode 中几何数据的存储。
1、Drawable:Drawable 类是用于存储几何数据信息的基类,Geode 维护了一个 Drawable 的列表。Drawable 是纯虚类,无法直接实例化。用户必须实例化其派生类,如 Geometry,或者 ShapeDrawable(允许用户程序绘制预定义的几何形状,如球体、圆锥体和长方体)。该类直接继承于osg::Node节点类,与osg::Group是同一层次的节点类。
2、GeometryGeometry 类与 PrimitiveSet 类相关联,实现了对 OpenGL 顶点数组功能的高级封装。Geometry 保存顶点数组的数据,纹理坐标,颜色,以及法线数组。该类直接继承于Drawable类。
3、PrimitiveSet:PrimitiveSet 类提供了 OpenGL 顶点数组绘图命令的高层次支持。用户可以从相关的 Geometry 类中取得保存的数据,再使用这个类来指定要绘制的几何体数据的类型。
4、Vector 类(Vec2,Vec3 等):OSG 提供了预定义好的二维,三维和四维元素向量,支持 float 或者 double 类型。使用这些向量来指定顶点、颜色、法线和纹理坐标的信息。
5、Array 类(Vec2Array,Vec3Array 等):OSG 定义了一些常用的数组类型,如用于贴图纹理坐标的 Vec2Array。指定顶点数组数据时,程序首先将几何数据保存到这些数组中,然后传递至 Geometry 类对象。
三、状态管理类
OSG 提供了一种机制,用以保存场景图形所需的 OpenGL 渲染状态。在拣选遍历中,同一状态的几何体将被组合集中到一起以使状态的改变呈最小化。在绘制遍历中,状态管理代码将记录当前状态的历史轨迹,以清除冗余的渲染状态变更。和其它场景图形系统不同,OSG 允许状态与任何场景图形节点相关联,在一次遍历中,状态将呈现出某种继承关系的
1、状态集合(StateSet):OSG 在 StateSet 类中保存一组定义状态数据(模式和属性)。场景图形中的任何 osg::Node 都可以与一个 StateSet 相关联
2、模式(Modes):与 OpenGL 的函数 glEnable()和 glDisable()相似,模式用于打开或关闭 OpenGL 固定功能(fixed-function)的渲染管道,例如灯光,混合和雾效。方法 osg::StateSet::setMode()在 StateSet 中保存一个模式信息。
3、属性(Attributes):应用程序使用属性来指定状态参数,例如混和函数,材质属性,雾颜色等。方法 osg::StateSet::setAttribute()在 StateSet 中保存属性信息。
4、纹理模式和属性:纹理模式和属性可应用在 OpenGL 多重纹理的某个指定纹理单元上。应用程序必须在设定纹理模式和属性时提供纹理单元的信息,注意,和 OpenGL 不同,OSG 不存在缺省的纹理单元。StateSet类的方法setTextureMode()和setTextureAttribute()用于设定状态参量以及纹理单元信息。
5、继承标志:OSG 提供了一些标志量,用于控制场景图形遍历中的状态值。缺省情况下,子节点中的状态集合将重载父节点的状态集合,但是也可以强制父节点的状态重载子节点的状态,或者指定子节点的状态受到保护而不会被其父节点重载。
四、其他实用类
除上述类外,osg 链接库还包括了一些实用的类和工具。其中一些涉及到OSG 的内存引用计数策略(reference-counted memory scheme),这种策略可以通过清理不再引用的内存以避免内存泄露。
1、Referenced:Referenced 类是所有场景图形节点和 OSG 的许多其它对象的基类。它实现了一个用于跟踪内存使用情况的引用计数(reference count)。如果某个继承自 Referenced 的对象,其引用计数的数值到达 0,那么系统将自动调用其析构函数并清理为此对象分配的内存。
2、ref_ptr<>:模板类 ref_ptr<>为其模板内容定义了一个智能指针模板内容必须继承自 Referenced 类(或提供一个与之相同的、能实现引用计数的接口)。当对象的地址分配给 ref_ptr<>时,对象的引用计数将自动增加。同样,清除或者删去 ref_ptr 时,对象的引用计数将自动减少。
3、Object:纯虚类 Object 是 OSG 中一切需要 I/O 支持,拷贝和引用计数的对象的基类。所有的节点类,以及某些 OSG 对象均派生自 Object 类。
4、Notify:osg 库提供了一系列控制调试,警告和错误输出的函数。用户可以通过指定一个来自 NotifySeverity 枚举量的数值,设定输出的信息量。OSG 中的大部分代码模块执行时都会显示相关的信息。

1.4.2 NodeKits

NodeKits 扩展了 Nodes,Drawables 和 StateAttributes 的概念,也可以看作是OSG 内核中 osg 库的一种扩展。NodeKits 的意义远大于对 OSG 类的继承,事实上它还能够提供对.osg 的封装(一种支持对.osg 文件进行读写的 OSG 插件)。总之,NodeKit 由两部分组成:NodeKit 本身,以及针对.osg 的封装插件库。
1、osgFX 库:此类 NodeKit 提供了额外的场景图形节点,以便于特效的渲染,例如异向光照(anisotropic lighting),凹凸贴图,卡通着色等。
2、osgParticle 库:此类 NodeKit 提供了基于粒子的渲染特效,如爆炸、火焰、烟雾等。
3、osgSim 库:此类 NodeKit 提供了仿真系统中以及渲染 OpenFlight 数据库所需的特殊渲染功能,例如地形高程图,光点节点,DOF 变换节点等。
4、osgText 库:此类 NodeKit 提供了向场景中添加文字的得力工具,可以完全支持 TrueType 字体。
5、osgTerrain 库:此类 NodeKit 提供了渲染高度场数据的能力。
6、osgShadow 库:此类 NodeKit 提供了支持阴影渲染的框架结构。

1.4.3 OSG插件

OSG 的核心库提供了针对多种 2D 图形和 3D 模型文件格式的 I/O 支持。osgDB::Registry 可以自动管理插件链接库。只要提供的插件确实可用,Registry就可以找到并使用它,应用程序只需调用相应的函数来读取和写入数据文件即可。
osg 库允许用户程序采用节点到节点(node-by-node)的方式直接建立场景图形。相反的,OSG 插件允许用户程序仅仅通过编写几行代码就能够从磁盘中调用整个场景图形,或者调用部分的场景图形,然后应用程序可以将其列入整个场景图形系统中去。
OSG 的 2.0 版本支持大量常用的 2D 图形文件格式,包括.bmp,.dds,.gif,.jpeg,.pic,.png,.rgb,.tga 和.tiff。OSG 还支持用于读取电影文件的 QuickTime 插件,并有专门的插件用于读取 FreeType 类型的字体。OSG 广泛支持各种 3D 模型文件格式,其中包括 3D Studio Max(.3ds),Alias Wavefront(.obj),Carbon Graphics’ Geo(.geo),Collada(.dae),ESRI Shapefile(.shp),OpenFlight(.flt),Quake(.md2)和 Terrex TerraPage(.txp)等常见格式。
除上述标准格式以外,OSG 还定义了自身的文件格式。其中,.osg 格式是场景图形的另一种 ASCII 文本描述格式,用户可以使用文本编辑器对其进行编辑和修改;而**.ive 格式则是一种二进制格式,经过优化之后它更适合于迅速读取**。除 2D 图形和 3D 模型文件以外,OSG 插件还支持对压缩文件和文件集的 I/O操作,OSG 目前支持的压缩文件格式有常见的.tgz 和.zip,以及 OSG 特有的.osga格式

2、OSG建立场景图形核心要点

本章节主要介绍建立一个场景图形需要使用哪些功能。包括内存管理、节点、几何信息、渲染状态、文件IO和NodeKit及osgText文字等。

2.1 内存管理

OSG 提供了一种自动的“废弃物”收集系统,它使用一种名为内存引用计数器(referencecounted memory)的方式工作。所有的 OSG 场景图形节点均采用引用计数(reference count)的方式,当引用计数值减为 0 时,此对象将被自动释放。其实与c++中提供的智能指针是一样的一种思想。当我们要销毁一个结点的时候,如果它是其他节点的父节点,那么他会将其所有子节点和孙子节点全部删除。这样能够很快的释放节点。
在这里插入图片描述
OSG 的“废弃物”收集系统有以下两个组件:
1、通用的基类 osg::Referenced,所有的 OSG 节点和场景图形均继承自这一基类,它包含了一个整型的引用计数器。
2、OSG 定义的智能指针模板类 ref_ptr<>。当代码中一个 Referenced 对象指针赋予类型变量 ref_ptr<>时,Referenced 类的引用计数器会自动加 1。
如果程序要保存一个继承自 Referenced 的对象的指针,那么将指针保存在ref_ptr<>中要好过使用标准 C++指针变量。如果代码中可以始终遵循这一原则,那么当最后一个引用对象的 ref_ptr<>被弃用时,对象所占用的内存将自动释放。

2.1.1 Referenced 类

Referenced 类(命名空间:osg)实现了对内存区段的引用计数器功能。所有的 OSG 节点和场景图形数据,包括状态信息,顶点数组,法线,以及纹理坐标,均派生自 Referenced 类。因此,所有的 OSG 场景图形均可以进行内存引用计数
Referenced 类包括了三个主要组成部分:
1、保护成员整型变量_refCount,用作引用计数,在构造时被初始化为 0。
2、公有函数 ref()和 unref(),用于实现_refCount 值的增加和减少。当_refCount 为 0 时,unref()将自动释放该对象所占用的内存。
3、作为保护成员存在的虚析构函数。堆栈的创建和显示的析构均会因为析构函数受保护而被禁止,而虚函数的特性将允许用户执行子类的析构函数。
总体上来说,用户的代码基本上不需要直接调用 ref()和 unref()函数,只要使用 ref_ptr<>进行处理即可。
注意:不能够显式地释放派生自 Referenced 的对象,也不能在堆栈中构建它。

2.1.2 ref_ptr<>模板类

ref_ptr<>(命名空间:osg)用于实现一个指向 Referenced 对象的智能指针,并对其引用计数器进行管理当最后一个引用 Referenced 对象的 ref_ptr<>失去作用时,对象将确保被释放。ref_ptr<>简化了场景图形内存释放的工作,并保证当错误的调用堆栈展开时,对象也可以被正确释放。
ref_ptr<>模板类包括以下三个主要的组成部分:
1、一个私有指针_ptr,用于保存管理内存区域的地址。可以用 get()方法返回_ptr 的值。
2、为了使 ref_ptr<>可以像正常的 C++指针一样工作,重载或定义了一些方法,如 operator->()和 operator=()
3、valid()方法用于判断 ref_ptr<>是否为空,不为 NULL 时返回 TRUE。当程序将一个地址指定给 ref_ptr<>变量时,ref_ptr<>的重载函数 operator=()将会假定此地址指向一个 Referenced 派生对象,并自动调用Referenced::ref(),将引用计数值自动加一。

2.1.3 测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以,在对节点管理的时候需要使用智能指针模板来处理,不要在栈上或者堆上直接构建或者删除。
总结:
1、将派生自 Referenced 的对象赋予 ref_ptr<>变量,这一动作将自动调用Referenced::ref()并使引用计数加一。
2、如果将 ref_ptr<>变量指向其它的对象,或者将其释放,那么将调用Referenced::unref()方法,使引用计数减一。当计数器的值为 0 时,unref()自动释放对象所占用的内存空间。
3、为新的 Referenced 对象开辟内存空间时,要尽量将其赋予 ref_ptr<>,以保证 OSG 的内存管理工作正确。

2.2 叶节点(Geode)和几何信息

首先建立一个简单的场景,绘制一个四边形。

void test1() {
	// 创建一个用于保存几何信息的对象
	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));

	// 为唯一的法线创建一个数组
	// 设置法线(Normal)是为了描述几何体表面的朝向和光照效果。
	// 法线在渲染过程中对于正确计算光照、阴影和表面反射等效果非常重要。
	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());
	/*osg::ref_ptr<osg::Node> root = geode;
	if (!root.valid())
		osg::notify(osg::FATAL) << "Failed in createSceneGraph()." << endl;

	bool result = osgDB::writeNodeFile(*(root.get()), "Simple.osg");
	if (!result)
		osg::notify(osg::FATAL) << "Failed in osgDB::writeNode()." << endl;*/
	osgViewer::Viewer viewer;
	viewer.setSceneData(geode);
	viewer.run();
}

在这里插入图片描述
在这里插入图片描述

2.2.1 Geometry概述

上面的代码中仅仅执行了三个步骤:
1、创建了顶点、法线和颜色数据的数组。
2、将osg::Geometry对象实例化,并将上述数组添加到对象中。同时还添加了一个osg::DrawArrays对象以指定数据绘制的方式。
3、将osg::Geode场景图形节点实例化,并将几何信息添加到其中。

1、向量与数组类
OSG 定义了大量的类用于保存向量数据,如顶点、法线、颜色、纹理坐标等。osg::Vec3 是一个三维浮点数数组;可以用来保存顶点和法线数据osg::Vec4用于保存颜色数据;而 osg::Vec2 可以用于保存 2D 纹理坐标。除了简单的向量存储以外,这些类还提供了完整的有关长度计算、点乘和叉乘、向量加法、向量矩阵乘法的运算函数。
OSG 还定义了用于保存对象的模板数组类。数组模板的最常见用法是保存向量数据。事实上,正因为它十分常用,因此 OSG 提供了对向量数据数组的一系列类型定义:osg::Vec2Array,osg::Vec3Array 以及 osg::Vec4Array。
例子中使用 Vec3 为每个 XYZ 顶点坐标创建了独立的三元向量,然后将各个 Vec3 压入 Vec3Array。之后,采用基本相同的方式,用 Vec3Array 来保存 XYZ法线向量数据。例子中使用了 Vec4 和 Vec4Array 来保存颜色数据,因为颜色是一个四元数(红、绿、蓝、Alpha)。所有的数组类型均继承自 std::vector

osg::Vec3   // == Vec3f       float
osg::Vec3b  // singed char
osg::Vec3d	// double
osg::Vec3i	// int
osg::Vec3s	// short
osg::Vec3ub	// unsigned char
osg::Vec3ui	// unsigned int
osg::Vec3us // unsigned short
//同理,Vec3对应的有一套Vec4xx和Vec2xx 

2、Drawables类
OSG 定义了 osg::Drawable 类,用于保存要渲染的数据。Drawable 是一个无法直接实例化的虚基类。 OSG 核心库从 Drawable 派生了主要有三个子类:
osg::DrawPixels,封装了 glDrawPixels()的相关功能;osg::ShapeDrawable,提供了一些已经定义好的几何体的使用接口,如圆柱体和球;以及 osg::Geometry。例子代码中出现了 Geometry 类,它也是使用最为灵活,也最为广泛的一个子类
在这里插入图片描述
其中osg::Geometry类常用的方法:

void  setVertexArray (Array *array)  // 设置顶点数组

void  setColorArray (Array *array) 	// 设置颜色
void  setColorBinding (AttributeBinding ab)  // 设置颜色绑定方式,新版本弃用
void  setColorArray (Array *array, osg::Array::Binding binding) // 新的设置方式

void  setNormalArray (Array *array)    // 设置法线及其绑定方式
void  setNormalBinding (AttributeBinding ab) // 设置法线绑定方式,新版本弃用
void  setNormalArray (Array *array, osg::Array::Binding binding) // 新的设置方式

// 根据已将设置的各种数据添加一个图元信息,根据图元信息已设置的数据绘制对应的图形
bool  addPrimitiveSet (PrimitiveSet *primitiveset) 
 
// setVertexArray()和 setNormalArray()均使用一个 Vec3Array 指针作为输入参数,而 setColorArray()使用一个 Vec4Array 指针。

其中的setColorBinding()和setNormalBinding() - 这些方法用于设置Geometry类中颜色和法线数据的绑定方式。其输入参数为 Geometry 类的枚举量。例子中颜色绑定方式为 osg::Geometry::BIND_PER_VERTEX,即每种颜色对应一个顶点。而法线的绑定方式为osg::Geometry::BIND_OVERALL,即整个 Geometry 几何体对应唯一的一个法线数据。
枚举如下:

enum Binding
{
    BIND_UNDEFINED=-1,
    BIND_OFF=0,
    BIND_OVERALL=1,
    BIND_PER_PRIMITIVE_SET=2,
    BIND_PER_VERTEX=4
};
enum AttributeBinding
{
    BIND_OFF=0,
    BIND_OVERALL=1,
    BIND_PER_PRIMITIVE_SET=2,
    BIND_PER_VERTEX=4
};
/*
	BIND_UNDEFINED(-1):未定义绑定
						这意味着绑定方式未定义或无效。
	BIND_OFF(0):关闭绑定
						这意味着属性将不会被应用到顶点上。默认将会是没有颜色的
	BIND_OVERALL(1):整体绑定
						整个属性数组的第一个值将被应用到所有顶点上。
						这意味着所有顶点将具有相同的属性值。
	BIND_PER_PRIMITIVE_SET(2):按图元集合绑定
						属性数组中的每个值将依次应用到对应的图元集合上。
					图元集合是逻辑上相关联的图元对象,例如一组三角形形成一个图元集合。
						这意味着不同的图元集合可以使用不同的属性值。
	BIND_PER_VERTEX(4):按顶点绑定
						属性数组中的每个值将依次应用到对应的顶点上。
						这意味着每个顶点可以具有不同的属性值。
*/

在这里插入图片描述
在这里插入图片描述
addPrimitiveSet()方法允许用户程序设定OSG如何绘制Geometry类保存的几何数据。其参数是osg::PrimitiveSet类型的,该类的具体子类如图,比较常用的有osg::DrawArrays。DrawArrays表示通过一系列顶点的连续索引来描述要绘制的图元它使用固定数量的顶点进行绘制,由起始索引和绘制数量指定
在这里插入图片描述

// 构造一个DrawArrays对象,并指定图元类型、起始索引和绘制数量。
DrawArrays(PrimitiveType mode, GLint first, GLsizei count)  // 构造函数
// 1、图元类型,是一个枚举,数量众多
// 2、first参数指定了绘制的起始顶点索引。
// 3、count参数指定了要绘制的顶点数量。【例子中有四个点,这里就是4】
enum Mode		// 图元类型的枚举
{
    POINTS = GL_POINTS,                 // 绘制单独的点
    LINES = GL_LINES,                   // 依次绘制连接的线段对(每两个顶点形成一条线段)
    LINE_STRIP = GL_LINE_STRIP,         // 依次绘制连接的线段(每个顶点和前一个顶点形成一条线段)
    LINE_LOOP = GL_LINE_LOOP,           // 绘制闭合的折线(最后一个顶点和第一个顶点形成一条线段)
    TRIANGLES = GL_TRIANGLES,           // 依次绘制连接的三角形(每三个顶点形成一个三角形)
    TRIANGLE_STRIP = GL_TRIANGLE_STRIP, // 依次绘制连接的三角形带
    TRIANGLE_FAN = GL_TRIANGLE_FAN,     // 绘制由中心点和连续顶点形成的扇形三角形
    QUADS = GL_QUADS,                   // 依次绘制连接的四边形(每四个顶点形成一个四边形)
    QUAD_STRIP = GL_QUAD_STRIP,         // 依次绘制连接的四边形带
    POLYGON = GL_POLYGON,               // 绘制多边形,顶点可以超过三个,并连接为闭合的多边形
    LINES_ADJACENCY = GL_LINES_ADJACENCY,           // 使用附加线段绘制模式,用于几何着色器的输入
    LINE_STRIP_ADJACENCY = GL_LINE_STRIP_ADJACENCY, // 使用附加线段带绘制模式,用于几何着色器的输入
    TRIANGLES_ADJACENCY = GL_TRIANGLES_ADJACENCY,   // 使用附加三角形绘制模式,用于几何着色器的输入
    TRIANGLE_STRIP_ADJACENCY = GL_TRIANGLE_STRIP_ADJACENCY, // 使用附加三角形带绘制模式,用于几何着色器的输入
    PATCHES = GL_PATCHES                // 用于曲面细分的绘制模式
};

在这里插入图片描述
3、osg::Geode子节点
osg::Geode 类是 OSG 的叶节点,它用于保存几何信息以便渲染。它不再有子节点,但是可以添加Drawable几何对象。

bool  addDrawable (Drawable *drawable)   // 添加可绘制对象,例子中的Geometry就是Drawable的子类

2.3 Group组节点

OSG 的组节点,osg::Group,允许用户程序为其添加任意数量的子节点,子节点本身也可以继续分发子节点Group 作为基类,派生出了许多实用的节点类,这其中包括本节将要介绍的 osg::Transform(变换),osg::LOD(细节层次) 和osg::Switch(开关)。部分子类如下图。
在这里插入图片描述

2.3.1 Group子接口

Group 类定义了针对子节点的接口,所有派生自 Group 的节点均会继承这一接口。大多数 OSG 的节点均派生自 Group(不过 Geode 是一个例外),因此通常来说,可以假设绝大部分节点均支持子接口的特性。
Group 使用 std::vector< ref_ptr >来保存所有子节点的指针,这是一个使用 ref_ptr<>模板的 Node 类数组。正因为 Group 使用了数组,所以你可以根据索引来访问某个子节点。Group 同时还使用 ref_ptr<>来保证 OSG 的内存管理机制生效。在这里插入图片描述
常用的接口如下:

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; 
 }
2.2.2 Group父接口

Group 类从 Node 类继承了父节点管理的接口。OSG 允许节点有多个父节点。部分常用接口如下:

class Node : public Object { 
 public: 
	 typedef std::vector<Group*> ParentList; 
	 
	 // 返回父节点的列表。
	 const ParentList& getParents() const; 
	 // 返回指定索引处父节点的指针。
	 Group* getParent( unsigned int I ); 
	 // 返回父节点的数目。
	 unsigned int getNumParents() const; 
}

Node 类继承自 osg::Object。Object 类是应用程序无法直接实例化的虚基类。Object 类提供了一系列接口用于保存和获取名称指定保存数据是静态的还是动态可更改的,以及继承自 Referenced 的内存管理机制
注意,osg::Node::ParentList 是一个保存标准 C++指针的 std::vector。虽然一个 Node 保存了所有父节点的地址,但是 Node 类并不需要使用 OSG 内存管理机制来引用其父节点。当父节点被释放时,父节点会自动从子节点的相应列表中删除自己。
在这里插入图片描述

2.3.3 开关节点Switch

使用 osg::Switch 节点可以渲染或者跳过指定的子节点。Switch 类的典型用法有:根据当前渲染的负荷有选择地渲染子图形以实现渲染性能的均衡,或者在游戏的界面和层级之间有选择地切换。而在应用程序 Present3D 中,使用 Switch节点实现了连续幻灯片的显示。
Switch开关节点除了继承自父类的接口,还会根据Switch开关节点的特性重载一些接口,实现特定的功能。

// 创建Switch节点,然后可以添加子节点
osg::ref_ptr<osg::Switch> switchNode = new osg::Switch();

默认情况下,通过addChild函数添加的子节点是默认渲染的,Switch类也重载了addChild这个函数,添加第二个参数bool value,用于表示是否渲染该子节点。为了修改默认添加节点是否渲染,设计了setNewChildDefaultValue()函数用于修改。
在这里插入图片描述
在这里插入图片描述
如果需要动态修改这个value的值,让osg动态渲染或者不渲染,可以通过两种方式进行设置,分别是setValue和setChildValue函数。其中pos参数表示控制的子节点的索引。在真实的应用场景中可能需要在回调事件中进行操作更新。
在这里插入图片描述

void test() {
	// 创建Switch节点
	osg::ref_ptr<osg::Switch> switchNode = new osg::Switch();
	
	// 创建幻灯片节点
	osg::ref_ptr<osg::Node> slide1 = osgDB::readNodeFile("cow.osg");
	osg::ref_ptr<osg::Node> slide2 = osgDB::readNodeFile("glider.osg");
	// 向Switch节点添加幻灯片节点,默认渲染
	switchNode->addChild(slide1);
	switchNode->addChild(slide2);
	
	// 将第一个子节点设置为不渲染
	//switchNode->setValue(0, false);
	switchNode->setChildValue(slide1, false);
	
	// 创建Viewer
	osgViewer::Viewer viewer;
	viewer.setSceneData(switchNode);
	
	viewer.run();
}
2.3.4 细节层次节点LOD

使用osg::LOD节点可以实现不同细节层次下物体的渲染。LOD继承自Group类,因此也继承了子接口的特性。LOD 允许用户指定各个子节点的有效范围该范围包括了最大和最小值,缺省情况下其意义表示为距离当某个子节点与观察者的距离在这个节点的有效范围之内时,LOD 将显示这个节点。LOD 的子节点可以按照任何顺序存放,且不必按照细节的数量或者距离排序。
LOD细节层次节点也是继承自Group节点,所以除了有继承的接口,还有根据特有的场景重载的接口,类似于Switch,它的addChild函数也进行了重载。增加了两个参数,是距离。此外,也配有单独的设置函数,setRange函数用于设置距离。
在这里插入图片描述
在这里插入图片描述
此外,还有setRangeMode函数用于设置范围模式。要设置 LOD 节点的有效范围模式,可以调用 osg::LOD::setRangeMode(),并设置参数为 PIXEL_SIZE_ON_SCREEN,或者 DISTANCE_FROM_EYE_POINT。

enum RangeMode
{
    DISTANCE_FROM_EYE_POINT,    // 基于相机与节点之间的距离确定细节级别
    PIXEL_SIZE_ON_SCREEN        // 基于节点在屏幕上的像素大小来确定细节级别
};
void test4() {

	//根节点
	osg::ref_ptr<osg::Group> _root = new osg::Group;

	//读取两个模型文件构成节点
	osg::ref_ptr<osg::Node> node1 = osgDB::readNodeFile(BaseDir + "cow.osg");
	osg::ref_ptr<osg::Node> node2 = osgDB::readNodeFile(BaseDir + "glider.osg");

	//创建细节层次节点
	osg::ref_ptr<osg::LOD> lode1 = new osg::LOD;
	//添加孩子节点,并且设置视野范围
	lode1->setRangeMode(osg::LOD::PIXEL_SIZE_ON_SCREEN);
	lode1->addChild(node1.get(), 0.0f, 30.0f);
	lode1->addChild(node2.get(), 30.0f, 100.0f);
	
	_root->addChild(lode1.get());

	osgViewer::Viewer viewer;
	viewer.setSceneData(_root);
	viewer.run();
}

缺省条件下,LOD 计算视点到物体包络体中心点的距离。如果这样并不符合渲染要求,那么用户可以指定一个自定义的中心点。这样,用户能够更灵活的控制细节层次的显示距离。
在这里插入图片描述

enum CenterMode
{
	// 使用边界球的中心作为对象的中心,并使用节点子级的几何边界球进行计算。
	// 中心点将被视为边界球的中心,边界球是通过计算节点及其子节点的几何边界来确定的。
    USE_BOUNDING_SPHERE_CENTER, 
    // 使用用户自定义的中心作为对象的中心;边界球由用户自定义的中心和用户自定义的半径确定。
	// 用户可以通过setCenter()函数设置自定义的中心点,同时可以通过setRadius()函数设置自定义的半径。
    USER_DEFINED_CENTER, 
    // 使用用户自定义的中心作为对象的中心;边界球是由用户自定义的边界球(使用用户定义的中心和半径)和节点子级的几何边界球扩展得到的。
    // 即边界球由用户定义的边界球与节点及其子节点的几何边界球合并得到。
    UNION_OF_BOUNDING_SPHERE_AND_USER_DEFINED 
};
2.3.5 变换节点Transform

OSG 通过 osg::Transform 节点类家族来实现几何数据的变换。Transform 类继承自Group 类,它可以有多个子节点。但是 Transform 类是一个无法由应用程序实例化的虚基类。用户应当使用 osg::MatrixTransform 或osg::PositionAttitudeTransform 来替代它,这两者均继承自 Transform。它们提供了不同的变换接口。根据用户程序的需要,可以使用其中任意一个或者同时使用这两者。
在这里插入图片描述
Transform 节点允许用户指定参考系缺省情况下,参考系是相对的(osg::Transform::RELATIVE_RF)。OSG 也允许用户指定绝对参考系。
在这里插入图片描述

enum ReferenceFrame
{
	// 表示相对坐标系,子节点的坐标将相对于父节点进行变换。
	// 当设置为 RELATIVE_RF 时,子节点的坐标和变换会先与父节点的坐标和变换相乘,然后再应用到场景中。
    RELATIVE_RF, 
    // 表示绝对坐标系,子节点的坐标将相对于世界坐标系进行变换。
	// 当设置为 ABSOLUTE_RF 时,子节点的坐标和变换会直接应用到场景中,不受父节点的影响。
    ABSOLUTE_RF,
    // 类似于 ABSOLUTE_RF,不过子节点的变换还会继承视点(观察者)的变换。
	// 这意味着子节点的变换除了相对于世界坐标系之外,还会受到视点变换的影响。
    ABSOLUTE_RF_INHERIT_VIEWPOINT
};

1、osg::Matrix矩阵
osg::Matrix 类保存了一个 4×4 的矩阵,共包含了 16 个浮点数,并提供了相应的运算操作。Matrix 类不是由 Referenced 派生的,因此不能实现引用计数。通过矩阵可以实现节点的移动、缩放和旋转等变换操作。在使用Transform派生类实现节点变换时就不可避免的使用矩阵类。

// Matrix类的成员变量,维护一个4 * 4的数组
value_type _mat[4][4];   // value_type是double类型,通过typedef改名的来

在我们读取本地的一个osg三维模型时,默认情况下它的矩阵是一个单位矩阵。
在这里插入图片描述
通过makeRotate、makeTranslate和makeScale成员函数实现旋转、位移和缩放。

//实现缩放的成员函数,三种重载
void makeScale( const Vec3f& );
void makeScale( const Vec3d& );
void makeScale( value_type, value_type, value_type );

从下面的例子中可以看出一个矩阵中表示缩放的是对角线上的三个数,依次是x,y,z上的缩放程度。
在这里插入图片描述

// 实现位移,三个函数重载
void makeTranslate( const Vec3f& );
void makeTranslate( const Vec3d& );
void makeTranslate( value_type, value_type, value_type );

根据下面例子中可以看出矩阵数组中的最后一行前三列元素表示矩阵的位移距离,前面已经使用了缩放,后面继续位移,会把之前的缩放清除掉,后面会有其他API来实现连续性的操作。
在这里插入图片描述

//实现旋转的部分函数重载,基本上都是三个轴上做的旋转角度变换
void makeRotate( value_type angle, const Vec3d& axis );
void makeRotate( value_type angle, value_type x, value_type y, value_type z );
void makeRotate( const Quat& );
void makeRotate( value_type angle1, const Vec3f& axis1,
                 value_type angle2, const Vec3f& axis2,
                 value_type angle3, const Vec3f& axis3);
void makeRotate( value_type angle1, const Vec3d& axis1,
                 value_type angle2, const Vec3d& axis2,
                 value_type angle3, const Vec3d& axis3);

同理,旋转情况下这四个位置表示旋转后计算出的值,其原理可能需要了解矩阵的数学相关知识,这里和上面一样最新的makeXXX变换会覆盖之前的,相当于之前什么变换也没做。
在这里插入图片描述
除了makeXXX函数之外,还有preMultXXX和postMultXXX函数用于实现多次连续变换,实现矩阵的相乘,因为矩阵相乘顺序影响矩阵结果,所以有前乘和后乘的区别。如果不按照规定的设计来实现,是无法实现多种变换效果的。上面的make函数,多次调用只会保留最后一次的效果。

void preMult( const Matrixd& );
void postMult( const Matrixd& );

// 下面的函数是优化后的函数,可以更加直接直观
/** Optimized version of preMult(translate(v)); */
inline void preMultTranslate( const Vec3d& v );
inline void preMultTranslate( const Vec3f& v );
/** Optimized version of postMult(translate(v)); */
inline void postMultTranslate( const Vec3d& v );
inline void postMultTranslate( const Vec3f& v );

/** Optimized version of preMult(scale(v)); */
inline void preMultScale( const Vec3d& v );
inline void preMultScale( const Vec3f& v );
/** Optimized version of postMult(scale(v)); */
inline void postMultScale( const Vec3d& v );
inline void postMultScale( const Vec3f& v );

/** Optimized version of preMult(rotate(q)); */
inline void preMultRotate( const Quat& q );
/** Optimized version of postMult(rotate(q)); */
inline void postMultRotate( const Quat& q );

在OSG中如果要同时做平移、缩放和旋转操作,应该是按照平移->缩放->旋转的顺序来计算矩阵,如果顺序乱了,可能最终的结果不是自己想要的。如下例子所示:
在这里插入图片描述
为了方便操作,osg::Matrix矩阵类还提供了很多静态函数,方便设置一个变换后的矩阵,然后让其相乘即可实现多种变换同时设置的效果。这些函数都是返回一个矩阵,且是旋转、平移缩放后的矩阵,我们按照既定顺序手动将其相乘设置到空间变换节点中即可。

inline static Matrixd scale( const Vec3f& sv);
inline static Matrixd scale( const Vec3d& sv);
inline static Matrixd scale( value_type sx, value_type sy, value_type sz);
inline static Matrixd translate( const Vec3f& dv);
inline static Matrixd translate( const Vec3d& dv);
inline static Matrixd translate( value_type x, value_type y, value_type z);
inline static Matrixd rotate( const Vec3f& from, const Vec3f& to);
inline static Matrixd rotate( const Vec3d& from, const Vec3d& to);
inline static Matrixd rotate( value_type angle, value_type x, value_type y, value_type z);
inline static Matrixd rotate( value_type angle, const Vec3f& axis);
inline static Matrixd rotate( value_type angle, const Vec3d& axis);
inline static Matrixd rotate( value_type angle1, const Vec3f& axis1,
                              value_type angle2, const Vec3f& axis2,
                              value_type angle3, const Vec3f& axis3);
inline static Matrixd rotate( value_type angle1, const Vec3d& axis1,
                              value_type angle2, const Vec3d& axis2,
                              value_type angle3, const Vec3d& axis3);
inline static Matrixd rotate( const Quat& quat);

在这里插入图片描述

2、osg::MatrixTransform类
这个类是Transform基类的一个重要子类,实现空间变换,包括旋转、平移和缩放等,在上面测试中使用的就是这个类,其设置矩阵的结构是setMatrix函数,如果设置成自己变换后的矩阵,可以使用它来设置。也可以通过getMatrix函数获取它原来的变换矩阵。

 /** Set the transform's matrix.*/
 void setMatrix(const Matrix& mat) { _matrix = mat; _inverseDirty=true; dirtyBound(); }

 /** Get the matrix. */
 inline const Matrix& getMatrix() const { return _matrix; }

3、osg::PositionAttitudeTransform类
这个类是位置变换节点。主要用于表示位置和姿态(平移和旋转)。它包含了一个 osg::Vec3 类型的 Position 成员和一个 osg::Quat 类型的 Attitude 成员,当然也可以缩放,用osg::Vec3类型保存这个成员。

inline void setPosition(const Vec3d& pos) { _position = pos; dirtyBound(); }
inline const Vec3d& getPosition() const { return _position; }

inline void setAttitude(const Quat& quat) { _attitude = quat; dirtyBound(); }
inline const Quat& getAttitude() const { return _attitude; }

inline void setScale(const Vec3d& scale) { _scale = scale; dirtyBound(); }
inline const Vec3d& getScale() const { return _scale; }

在这里插入图片描述

2.4 渲染状态(Rendering State)

OSG支持绝大部分的OpenGL固定功能管线渲染,例如Alpha检验、Blending融合、剪切平面、颜色蒙版、面拣选、深度和模板检验、雾效、点和线的光栅化等等。
用户的应用程序需要在osg::StateSet中设置渲染状态。用户可以将StateSet关联到场景图形中的任意一个节点(Node),或者关联到Drawable类。【OpenGL程序的开发需要尽量使状态量的变化实现最小化,并避免冗余的状态设置,StateSet对象能够自动实现这些优化过程。】
OSG中的渲染状态主要分为渲染属性和渲染状态两部分:渲染属性(attribute)和渲染模式(mode)。渲染属性也就是控制渲染特性的状态变量,例如:雾的颜色、或者Blend融合函数都是OSG的状态属性。OSG中的渲染模式和OpenGL的状态特性几乎是一一对应的,这些特性在OpenGL中通过函数glEnable()和glDisable()进行控制。用于程序可以设置模式量以允许或者禁止某个功能,而渲染属性是这个功能的控制变量和参数。
如果要设置渲染状态的值,用户程序需要执行以下几个步骤:
1、将要设置状态的节点或者Drawable对象提供一个StateSet实例;
2、在StateSet实例中设置状态的渲染模式和渲染属性;
要从某个Node或者Drawable对象中直接获取一个StateSet实例,使用下面的方法:

// 如果已经在obj对象中存在StateSet对象,就直接返回;如果还没有,就会创建然后返回这个新创建的。
// obj是Node或者Drawable对象
osg::StateSet * state = obj->getOrCreateStateSet();
2.4.1 渲染属性(Attribute)和渲染状态(Mode)

OSG 为每个状态属性定义了不同的类,以便应用程序采用。所有的属性类均继承自 osg::StateAttribute,StateAttribute 类是一个无法直接实例化的虚基类。继承自 StateAttribute 的类有数十个之多。
OSG 将所有的属性和模式分为两大部分:纹理(texture)和非纹理(non-texture)。OSG 之所以为纹理属性的设置提供不同的接口,主要是因为纹理属性需要特别为多重纹理设置纹理单元(texture unit)
本章节只讨论非纹理状态的设置。
第一节:设置渲染属性Attribute
如果要设置一项属性,首先将要修改的属性类实例化。设置该类的数值,然后用 osg::StateSet::setAttribute()将其关联到 StateSet。

void setAttribute(StateAttribute *attribute, StateAttribute::OverrideValue value=StateAttribute::OFF);
// 1、第一个参数是指渲染属性类对象,继承自StateAttribute虚基类,有数十之多的子类
// 2、第二个参数的类型是一个枚举Values。这个参数的存在使得开发者可以在设置渲染状态属性时选择是否要覆盖父节点的相同类型属性,从而实现更灵活的状态管理。
// 默认情况下,即不传递第二个参数时, value 被设置为 StateAttribute::OFF。
enum Values
{
    /** 意味着设置到StateSet对象中的属性不会生效.*/
    OFF          = 0x0,
    /** 表示关联的GLMode被启用,但是Override(覆盖)是被禁用的。这意味着此状态由当前节点有效,不会覆盖父节点相同类型的属性。.*/
    ON           = 0x1,
    /** 表示Override(覆盖)是启用的,这意味着此状态会覆盖父节点相同类型的属性.*/
    OVERRIDE     = 0x2,
    /** 表示保护状态。这意味着此状态被启用,防止父节点和子节点的相同类型属性对其产生覆盖效果。*/
    PROTECTED    = 0x4,
    /** 表示从上级继承状态。这意味着此状态的值将从上级节点继承,而不是在当前节点显式设置。*/
    INHERIT      = 0x8
};

举例:实现背面剔除
面剔除需要使用渲染属性类osg::CullFace。

// 获取变量 geom 的 StateSet 指针。
osg::StateSet* state = geom->getOrCreateStateSet(); 
// 创建并添加 CullFace 属性类。其中的枚举是指剔除背面,默认构造函数也是剔除背面
osg::CullFace* cf = new osg::CullFace( osg::CullFace::BACK ); 
state->setAttribute( cf );

所有的这些属性类都继承于osg::StateAttribute虚基类。渲染属性相关的子类都有各自特有的函数实现数值的设置。osg::CullFace用于实现面剔除,其构造函数中的枚举是指将背面剔除,可以减少渲染,提升性能。枚举的定义如下:

enum Mode {
    FRONT = GL_FRONT,
    BACK = GL_BACK,
    FRONT_AND_BACK = GL_FRONT_AND_BACK
};

在这里插入图片描述
与大多数 OSG 对象相同,StateAttribute 也是继承自 Referenced。当你将派生自 StateAttribute 的属性对象实例化并将其关联到 StateSet 时,StateSet 将对这个属性实施引用计数,不用担心它所占用的内存如何释放的问题。

第二节:设置渲染状态Mode
用户可以使用 osg::StateSet::setMode()允许或禁止某种模式
渲染模式和渲染属性之间有一定的关系,因为它们都涉及到OpenGL状态在实际使用中,通常需要同时设置渲染模式和渲染属性,以实现期望的渲染效果
渲染属性通常是用来影响具体物体的外观,而渲染模式用于控制一些全局性的OpenGL状态,例如深度测试、剔除面等
一些渲染属性可能依赖于特定的渲染模式设置,例如启用深度测试可能会影响光照计算的结果。

void setMode(StateAttribute::GLMode mode, StateAttribute::GLModeValue value);
// 这个函数用于设置渲染模式
// 1、第一个参数:表示要设置的OpenGL状态。例如,GL_DEPTH_TEST 表示深度测试。有几百个,在GL.h头文件中
// 2、第二个参数:同setAttribute函数的第二个参数。

第三节:同时设置渲染属性和模式
OSG 提供了一个简单的可以同时设置属性和模式的单一函数接口在许多情况下,属性和模式之间都存在显著的关系。例如,CullFace 属性的对应模式为GL_CULL_FACE。如果要将某个属性关联到一个 StateSet,同时打开其对应模式的许可,那么可以使用 osg::StateSet::setAttributeAndModes()方法。下面的代码段将关联 Blend 融合检验的属性,同时许可颜色融合模式。
在实际使用中,可以考虑使用这个函数来设置渲染属性模式,更加便利。

// 创建一个 BlendFunc 属性。
osg::BlendFunc* bf = new osg::BlendFunc(); 
// 关联 BlendFunc 并许可颜色融合模式
state->setAttributeAndMode( bf ); 
void setAttributeAndModes(StateAttribute *attribute, StateAttribute::GLModeValue value=StateAttribute::ON);
// 该函数的目的是将指定的渲染属性(attribute)添加到当前 StateSet 中,并设置关联的OpenGL状态(GLMode's)的值为指定的值。
// 1、第一个参数是渲染属性对象
// 2、第二个参数是与渲染属性对象对应的渲染模式的开关,与setMode的第二个参数一致,默认打开

综合案例

void test15() {

	// 创建根节点
	osg::ref_ptr<osg::Group> root = new osg::Group;

	// 创建几何节点
	osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;

	// 创建顶点数组
	osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
	vertices->push_back(osg::Vec3(-1.0, 0.0, 0.0));
	vertices->push_back(osg::Vec3(1.0, 0.0, 0.0));

	// 将顶点数组设置给几何节点
	geometry->setVertexArray(vertices.get());

	// 创建颜色数组
	osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
	colors->push_back(osg::Vec4(1.0, 0.0, 0.0, 1.0)); // 红色
	//colors->push_back(osg::Vec4(0.0, 0.0, 1.0, 1.0)); // 蓝色

	// 将颜色数组设置给几何节点。通过Drawable对象设置颜色
	//geometry->setColorArray(colors.get());
	//geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);

	// 创建绘制元素(这里绘制线段)
	osg::ref_ptr<osg::DrawElementsUInt> indices = new osg::DrawElementsUInt(GL_LINES, 2);
	indices->push_back(0);
	indices->push_back(1);

	// 将绘制元素设置给几何节点
	geometry->addPrimitiveSet(indices.get());

	// 创建 Geode 节点
	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	geode->addDrawable(geometry.get());

	// 设置线段的宽度(通过设置渲染属性实现线宽设置)
	osg::ref_ptr<osg::LineWidth> lineWidth = new osg::LineWidth;
	lineWidth->setWidth(5.0); // 设置线段宽度为 5.0

	// 设置线段的颜色(这里通过渲染属性设置材质,从而设置颜色)
	osg::ref_ptr<osg::Material> material = new osg::Material;
	material->setColorMode(osg::Material::DIFFUSE);
	material->setDiffuse(osg::Material::FRONT, osg::Vec4(0.0, 0.0, 1.0, 1.0)); // 蓝色

	// 将渲染属性添加到 Geode 的状态集合中
	geode->getOrCreateStateSet()->setAttributeAndModes(lineWidth.get(), osg::StateAttribute::ON);
	geode->getOrCreateStateSet()->setAttributeAndModes(material.get(), osg::StateAttribute::ON);

	// 将 Geode 添加到根节点
	root->addChild(geode.get());

	// 创建查看器
	osgViewer::Viewer viewer;

	// 设置场景数据并运行查看器
	viewer.setSceneData(root.get());
	viewer.run();
}
2.4.2 状态继承

当你设置节点的渲染状态时,这个状态将被赋予当前的节点及其子节点。如果子节点对同一个渲染状态设置了不同的属性参数,那么新的子节点状态参数将会覆盖原有的。换句话说,缺省情况下子节点可以改变自身的某个状态参数,或者继承父节点的同一个状态
在这里插入图片描述
这种继承的特性在许多情况下都非常实用。但是有时候渲染可能需要更多特性。假设场景图形中有一个包含了实体多边形几何体的节点。如果要以线框模式来渲染场景图形,程序就需要覆盖这种多边形渲染模式状态,不论它出现在什么位置。
OSG 允许用户根据场景图形中任意位置的渲染属性和模式需求,而单独改变原有的状态继承特性。用户可以选择以下这几种枚举形式【上面的enum Values枚举】:

osg::StateAttribute::OVERRIDE
如果你将一个渲染属性和模式设置为OVERRIDE,那么所有的子节点都将继承这一属性或模式,子节点对它们更改将会无效。

osg::StateAttribute::PROTECTED
这种形式可以视为 OVERRIDE 的一个例外。凡是设置为 PROTECTED 的渲染属性或模式,均不会受到父节点的影响。

osg::StateAttribute::INHERIT
这种模式强制子节点继承父节点的渲染状态。其效果是子节点的渲染状态被解除,而使用父节点的状态替代。

对这些参数进行位或叠加的操作,然后再作为setAttribute(),setMode()和 setAttributeAndModes()的第二个参数输入。
比如:使用 PROTECTED 参量可以保证父节点的渲染状态不会覆盖子节点的渲染状态。举例来说,你可能创建了一个发光的场景,其中包含有使用亮度照明的光源几何体。如果其父节点禁用了光照,那么光源几何体的渲染将会出错。这个时候,对光源几何体的 GL_LIGHTING 渲染状态使用 PROTECTED,就可以保证它依然可用。

void test16() {

	// 创建根节点
	osg::ref_ptr<osg::Group> root = new osg::Group;

	// 创建一个红色的球体
	osg::ref_ptr<osg::Geode> redGeode = new osg::Geode;
	osg::ref_ptr<osg::ShapeDrawable> redSphere = new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(0.0, 0.0, 0.0), 1.0));
	redSphere->setColor(osg::Vec4(1.0, 0.0, 0.0, 1.0));
	redGeode->addDrawable(redSphere.get());

	// 创建一个绿色的球体
	osg::ref_ptr<osg::Geode> greenGeode = new osg::Geode;
	osg::ref_ptr<osg::ShapeDrawable> greenSphere = new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(3.0, 0.0, 0.0), 1.0));
	greenSphere->setColor(osg::Vec4(0.0, 1.0, 0.0, 1.0));
	greenGeode->addDrawable(greenSphere.get());

	// 设置根节点的渲染状态
	osg::ref_ptr<osg::StateSet> rootStateSet = root->getOrCreateStateSet();
	// 关闭光照,并且子节点设置无效果
	rootStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); 

	// 将红色球体添加到根节点
	root->addChild(redGeode.get());

	// 将绿色球体添加到根节点,并设置不继承父节点状态
	osg::ref_ptr<osg::StateSet> greenGeodeStateSet = greenGeode->getOrCreateStateSet();
	greenGeodeStateSet->setMode(GL_LIGHTING, osg::StateAttribute::ON); // 打开光照,当前无效果

	root->addChild(greenGeode.get());

	// 创建查看器
	osgViewer::Viewer viewer;

	// 设置场景数据并运行查看器
	viewer.setSceneData(root.get());
	viewer.run();
}

效果图中,绿色球并没有阴影,显然没有光照效果。
在这里插入图片描述

void test16() {

	// 创建根节点
	osg::ref_ptr<osg::Group> root = new osg::Group;

	// 创建一个红色的球体
	osg::ref_ptr<osg::Geode> redGeode = new osg::Geode;
	osg::ref_ptr<osg::ShapeDrawable> redSphere = new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(0.0, 0.0, 0.0), 1.0));
	redSphere->setColor(osg::Vec4(1.0, 0.0, 0.0, 1.0));
	redGeode->addDrawable(redSphere.get());

	// 创建一个绿色的球体
	osg::ref_ptr<osg::Geode> greenGeode = new osg::Geode;
	osg::ref_ptr<osg::ShapeDrawable> greenSphere = new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(3.0, 0.0, 0.0), 1.0));
	greenSphere->setColor(osg::Vec4(0.0, 1.0, 0.0, 1.0));
	greenGeode->addDrawable(greenSphere.get());

	// 设置根节点的渲染状态
	osg::ref_ptr<osg::StateSet> rootStateSet = root->getOrCreateStateSet();
	// 关闭光照,默认情况下,子节点可以对同一个属性或者模式进行自定义的修改
	rootStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); 

	// 将红色球体添加到根节点
	root->addChild(redGeode.get());

	// 将绿色球体添加到根节点,并设置不继承父节点状态
	osg::ref_ptr<osg::StateSet> greenGeodeStateSet = greenGeode->getOrCreateStateSet();
	greenGeodeStateSet->setMode(GL_LIGHTING, osg::StateAttribute::ON); // 打开光照,有效果

	root->addChild(greenGeode.get());

	// 创建查看器
	osgViewer::Viewer viewer;

	// 设置场景数据并运行查看器
	viewer.setSceneData(root.get());
	viewer.run();
}

如效果图,绿球因为光照原因,有阴影效果。显然设置成功。
在这里插入图片描述

void test16() {

	// 创建根节点
	osg::ref_ptr<osg::Group> root = new osg::Group;

	// 创建一个红色的球体
	osg::ref_ptr<osg::Geode> redGeode = new osg::Geode;
	osg::ref_ptr<osg::ShapeDrawable> redSphere = new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(0.0, 0.0, 0.0), 1.0));
	redSphere->setColor(osg::Vec4(1.0, 0.0, 0.0, 1.0));
	redGeode->addDrawable(redSphere.get());

	// 创建一个绿色的球体
	osg::ref_ptr<osg::Geode> greenGeode = new osg::Geode;
	osg::ref_ptr<osg::ShapeDrawable> greenSphere = new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(3.0, 0.0, 0.0), 1.0));
	greenSphere->setColor(osg::Vec4(0.0, 1.0, 0.0, 1.0));
	greenGeode->addDrawable(greenSphere.get());

	// 设置根节点的渲染状态
	osg::ref_ptr<osg::StateSet> rootStateSet = root->getOrCreateStateSet();
	// 关闭光照,子节点可以对同一个属性或者模式不可修改,只能继承,子节点修改无效果
	rootStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); 

	// 将红色球体添加到根节点
	root->addChild(redGeode.get());

	// 将绿色球体添加到根节点,并设置不继承父节点状态
	osg::ref_ptr<osg::StateSet> greenGeodeStateSet = greenGeode->getOrCreateStateSet();
	// 凡是设置为 PROTECTED 的渲染属性或模式,均不会受到父节点的影响。
	// 打开光照,有效果。虽然父节点设置了OVERRIDE枚举值,但是子节点设置了PROTECTED枚举值,所以有效果
	greenGeodeStateSet->setMode(GL_LIGHTING, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED); 

	root->addChild(greenGeode.get());

	// 创建查看器
	osgViewer::Viewer viewer;

	// 设置场景数据并运行查看器
	viewer.setSceneData(root.get());
	viewer.run();
}

在这里插入图片描述

2.4.3 纹理映射

OSG 全面支持 OpenGL 的纹理映射机制。为了在程序中实现基本的纹理映射功能,用户的代码需要遵循以下的步骤:
1、指定用户几何体的纹理坐标。
2、创建纹理属性对象并保存纹理图形数据。
3、为 StateSet 设置合适的纹理属性和模式。

根据以上三个步骤,可以实现纹理映射,这里将具体说明每个步骤。
1、纹理坐标
在之前介绍了 Geometry 对象中有关设置顶点,法线,颜色数据的接口。Geometry 类同时还允许用户程序指定一个或多个纹理坐标数据数组。当指定了纹理坐标之后,还要指定相应的纹理单元,OSG 使用纹理单元的值来实现多重纹理
在这里插入图片描述
下面是创建一个 osg::Vec2Array 数组,用于保存纹理坐标,同时将其关联到 Geometry 实例的纹理单元 0。如果要对单一的 Geometry 设置多个纹理,只需要将多个纹理坐标数组关联到 Geometry,并针对不同的数组指定不同的纹理单元即可。

// 创建一个 Geometry 几何体对象。
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry; 
// 创建一个 Vec2Array 对象以保存纹理单元 0 的纹理坐标,
// 并将其关联到 geom。
osg::ref_ptr<osg::Vec2Array> tc = new osg::Vec2Array; 
geom->setTexCoordArray( 0, tc.get() ); 
tc->push_back( osg::Vec2( 0.f, 0.f ) ); 
tc->push_back( osg::Vec2( 1.f, 0.f ) ); 
tc->push_back( osg::Vec2( 1.f, 1.f ) ); 
tc->push_back( osg::Vec2( 0.f, 1.f ) );

纹理坐标是与几何体上的顶点相关联的二维坐标,用于确定纹理在几何体表面上的映射关系。这些坐标并不是真实的物理坐标,而是在纹理坐标空间中的坐标纹理坐标总是绑定到每个顶点的。
纹理坐标通常以二维坐标(或三维,对应于3D纹理映射)表示,其范围通常在 [0, 1] 之间。这个范围表示纹理的整个区域。纹理坐标 (0, 0) 对应于纹理的左下角,(1, 1) 对应于右上角。通过在几何体的每个顶点上指定纹理坐标,可以将纹理贴图映射到几何体的表面上。
在上述的代码片段中,通过 osg::Vec2 类型的纹理坐标数组 tc,定义了一个矩形区域的四个顶点的纹理坐标。这些纹理坐标最终用于指定纹理在几何体表面的映射方式。
总之,纹理坐标是一种用于描述纹理在几何体上如何映射的坐标系统,不是真实的物理坐标,而是规范化到 [0, 1] 范围的坐标

2、读取图像
用户程序可以使用两个类来实现基本的 2D 纹理映射:osg::Texture2D 和osg::ImageTexture2D 属于 StateAttribute 的派生类(渲染属性),用于管理 OpenGL 纹理对象,而 Image 用于管理图像像素数据。如果要使用 2D 图像文件作为纹理映射的图形,只要将文件名赋给 Image 对象并将 Image 关联到 Texture2D 即可。下面的代码段将文件 sun.tif 作为纹理映射的图形。

#include <osg/Texture2D> 
#include <osg/Image> 
... 
osg::StateSet* state = node->getOrCreateStateSet(); 
// 读取纹理图像。
osg::ref_ptr<osg::Image> image = new osg::Image; 
image->setFileName( "sun.tif" ); 
// 将图像关联到 Texture2D 对象
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D; 
tex->setImage( image.get() ); 

Texture2D 属性设置完毕后,可以将其关联到 StateSet。

3、纹理状态
用户程序可以使用纹理状态函数接口为每个纹理单元指定渲染状态。纹理状态函数接口与非纹理状态的接口十 分类似。用户可以使用osg::StateSet::setTextureAttribute() 将一个纹理属性关联到 StateSet 对象。setTextureAttribute() 的第一个参数是纹理单元,第二个参数是继承自StateAttribute 类的一种纹理属性。合法的纹理属性类共有六种,其中包括五种纹理类型(osg::Texture1D,osg::Texture2D,osg::Texture3D,osg::TextureCubeMap和 osg::TextureRectangle),另一个类用于纹理坐标的生成(osg::TexGen)。

// 创建一个 Texture2D 属性。
Osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D; 
// ... 
// 关联纹理属性到纹理单元 0。
state->setTextureAttribute( 0, tex.get() );

与此类似,用户可以调用 **osg::StateSet::setTextureMode()**来设置材质渲染模式。这个方法与 setMode()方法类似。用户可以使用 setTextureMode()来设置以下模式:GL_TEXTURE_1D,GL_TEXTURE_2D,GL_TEXTURE_3D,GL_TEXTURE_CUBE_MAP,GL_TEXTURE_RECTANGLE,GL_TEXTURE_GEN_Q,GL_TEXTURE_GEN_R,GL_TEXTURE_GEN_S,以及 GL_TEXTURE_GEN_T。
与 setTextureAttribute()相似,setTextureMode()的第一个参数表示纹理单元。
下面的代码段将禁止纹理单元 1 的 2D 纹理映射。

state->setTextureMode( 1, GL_TEXTURE_2D, osg::StateAttribute::OFF ); 

用户也可以使用 **osg::StateSet::setTextureAttributesAndModes()**来关联纹理渲染属性到 StateSet,同时允许相应的纹理模式。如果属性是一个 TexGen对象,那么 setTextureAttributesAndModes() 将设置相应的坐标生成模式GL_TEXTURE_GEN_Q , GL_TEXTURE_GEN_R , GL_TEXTURE_GEN_S 和GL_TEXTURE_GEN_T。对于其它纹理属性来说,这一模式是隐含的。
例如,下面的代码中,由于第二个参数传入了一个 Texture2D 对象作为纹理属性,setTextureAttributesAndModes()将允许 GL_TEXTURE_2D 模式。

// 创建一个 Texture2D 属性对象。
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
// 在纹理单元 0 上,关联 2D 纹理属性并许可 GL_TEXTURE_2D 模式。
state->setTextureAttributeAndModes( 0, tex );

setTextureAttributeAndModes()的第三个参数缺省值为 ON,即允许纹理渲染模式。与 setAttributeAndModes()类似,可以对这个参数使用位或操作,以修改纹理属性的继承特性,包括 OVERRIDE,PROTECTED 和 INHERIT。你还可以通过修改 setTextureMode()和 setTextureAttribute()的第三个参数来指定这个继承标志
在这里插入图片描述

// 该函数用于设置纹理单元(unit)的属性,即关联指定纹理单元的StateAttribute对象。
// 第一个参数 unit 表示纹理单元的索引,通常从0开始。
// 第二个参数 attribute 是StateAttribute的子类,通常是osg::Texture2D等。
// 第三个参数 value 表示设置是否覆盖父节点的状态,默认是StateAttribute::OFF,即不覆盖。
void setTextureAttribute(unsigned int unit,StateAttribute *attribute, StateAttribute::OverrideValue value=StateAttribute::OFF);
 
 // 该函数用于设置纹理单元(unit)的渲染模式,即指定纹理单元的OpenGL渲染模式。
 // 第一个参数 unit 表示纹理单元的索引。
 // 第二个参数 mode 表示OpenGL渲染模式,例如 GL_TEXTURE_2D 表示2D纹理。
 // 第三个参数 value 表示渲染模式的值,可以是 StateAttribute::ON、StateAttribute::OFF 等。
void setTextureMode(unsigned int unit,StateAttribute::GLMode mode, StateAttribute::GLModeValue value);
 
 // 该函数是前两个函数的结合,用于设置纹理单元(unit)的属性和渲染模式。
 // 第一个参数 unit 表示纹理单元的索引。
 // 第二个参数 attribute 是StateAttribute的子类,通常是osg::Texture2D等。
 // 第三个参数 value 表示设置是否覆盖父节点的状态,默认是StateAttribute::ON,即覆盖。
void setTextureAttributeAndModes(unsigned int unit,StateAttribute *attribute, StateAttribute::GLModeValue value=StateAttribute::ON);
void test17()
{  // 创建四边形然后添加一个纹理
	// 创建正方形的顶点坐标
	osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
	vertices->push_back(osg::Vec3(-0.5, -0.5, 0.0)); // 左下角
	vertices->push_back(osg::Vec3(0.5, -0.5, 0.0));  // 右下角
	vertices->push_back(osg::Vec3(0.5, 0.5, 0.0));   // 右上角
	vertices->push_back(osg::Vec3(-0.5, 0.5, 0.0));  // 左上角

	// 创建正方形的颜色
	osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
	colors->push_back(osg::Vec4(1.0, 1.0, 1.0, 1.0)); // 白色

	// 创建正方形的纹理坐标
	osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
	texcoords->push_back(osg::Vec2(0.0, 0.0));
	texcoords->push_back(osg::Vec2(1.0, 0.0));
	texcoords->push_back(osg::Vec2(1.0, 1.0));
	texcoords->push_back(osg::Vec2(0.0, 1.0));

	// 加载纹理图像
	osg::ref_ptr<osg::Image> image = osgDB::readImageFile("Images/blueFlowers.png");
	if (!image)
	{
		osg::notify(osg::FATAL) << "Failed to load texture image." << std::endl;
		return ;
	}

	// 创建纹理对象并设置图片
	osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
	texture->setImage(image.get());

	// 创建 Geometry 对象
	osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;
	geometry->setVertexArray(vertices.get());
	geometry->setColorArray(colors.get(), osg::Array::BIND_OVERALL);
	geometry->setTexCoordArray(0, texcoords.get());
	// 绘制四边形
	geometry->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));

	// 创建 Geode 对象并将 Geometry 加入其中
	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	geode->addDrawable(geometry.get());

	// 设置纹理
	geode->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get());

	// 创建查看器
	osgViewer::Viewer viewer;

	// 设置场景数据并运行查看器
	viewer.setSceneData(geode.get());
	viewer.run();
}
2.4.4 光照

OSG 全面支持 OpenGL 的光照特性,包括材质属性(material property),光照属性(light property)和光照模型(lighting model)。与 OpenGL 相似,OSG 中的光源也是不可见的,而非渲染一个灯泡或其他自然形状。同样,光源会创建着色效果,但是并不创建阴影——osgShadow 可以用来创建阴影。
如果要在用户程序中使用光照,需要遵循下面的步骤:
1、指定几何体法线。
2、允许光照并设置光照状态。
3、指定光源属性并关联到场景图形。
4、指定表面材质属性。

1、法线
只有几何体数据中设有单位长度法线时,才可以实现正确的光照。 在大多数 3D API 中,法线数据必须单位化以得到正确的结果。注意缩放变换的动作会改变法线的长度。如果你的 Geometry 对象中已经包含了单位长度的法线数组,但是光照的计算结果过于明亮或过于暗淡,那么这一现象可能是缩放变换造成的。最有效的解决方案是在 StateSet 中允许法线的重放缩模式

osg::StateSet* state = geode->setOrCreateStateSet(); 
state->setMode( GL_RESCALE_NORMAL, osg::StateAttribute::ON );

与 OpenGL 中相同,这一特性可以保证法线在均匀放缩变换时仍然保持单位长度。如果场景中的放缩变换是非均匀的,那么可以允许法线归一化模式,以保证法线为单位长度。

osg::StateSet* state = geode->setOrCreateStateSet(); 
state->setMode( GL_NORMALIZE, osg::StateAttribute::ON ); 

由于要进行法线的重新放缩,归一化模式往往会耗费大量的时间。编程时要尽量避免。

理解法线是理解三维图形渲染中光照效果的关键。法线是指垂直于表面的向量,它们描述了表面的方向。在光照计算中,法线告诉渲染引擎光线如何与表面相交,从而确定表面的亮度和颜色。
举个例子,想象一个平坦的墙面。在每个点上,都有一个垂直于墙面的向量,这就是法线。当光线照射到墙面上时,光线与法线的夹角决定了墙面上的某一点是如何受光的。夹角越小,光线越垂直于表面,表面越亮;夹角越大,光线越平行于表面,表面越暗。
在计算机图形学中,每个三角形都有自己的法线。这些法线可以通过各种方法计算,例如通过平均顶点法线或在建模软件中手动指定。在渲染过程中,这些法线与光线的方向结合起来,确定了最终的颜色和明暗效果。

2、光照状态
要在 OSG 中获得光照效果,你需要允许光照并至少允许一个光源。程序osgviewer 在缺省情况下就是这样做的,它在根节点的 StateSet 中已经设置了相应的模式。可以在自己的程序中进行相同的设置。
下面的代码段用于允许光照并为根节点的 StateSet 允许两个光源(GL_LIGHT0 和 GL_LIGHT1)

// 这段代码的作用是在 OSG 中启用光照效果,并且启用了两个光源,GL_LIGHT0 和 GL_LIGHT1。
// 启用光源后,需要设置光源的位置、颜色、强度等属性,以便正确地计算场景中几何体的光照效果。

// 获取或创建根节点的状态集合(StateSet)
osg::StateSet* state = root->getOrCreateStateSet(); 

// 启用光照(GL_LIGHTING),表示将光照效果应用到场景中的几何体上
state->setMode(GL_LIGHTING, osg::StateAttribute::ON); 

// 启用光源0(GL_LIGHT0),即第一个光源。在光照计算中,光源的位置和属性会影响场景中的几何体的光照效果
state->setMode(GL_LIGHT0, osg::StateAttribute::ON); 

// 启用光源1(GL_LIGHT1),即第二个光源。在需要多个光源进行光照计算时,可以启用多个光源。
state->setMode(GL_LIGHT1, osg::StateAttribute::ON); 

OSG 还提供了从 StateAttribute 派生的 osg::LightModel 属性,用以控制全局的环境颜色,局部视图,双面光照,以及分离镜面颜色(separate specular color)等 OpenGL 特性。

3、光源
要在场景中添加一个光源,可以创建一个 osg::Light 对象以定义光源参数。然后将 Light 添加到一个 osg::LightSource 节点中,并将 LightSource 节点添加到场景图形。LightSource 是一个包含了唯一的 Light 定义的高效的组节点。而由Light 定义的光源将对整个场景产生影响。
在这里插入图片描述

OSG 支持最多八个光源,从 GL_LIGHT0 到 GL_LIGHT7,这与你的 OpenGL版本也有关系。可以使用前述的 setMode()方法来允许这些光源。如果要把一个 Light 对象与 OpenGL 的光源联系起来,可以使用设置光的位置数的方法。
例如要把一个 Light 对象与 GL_LIGHT2 相关联,则设置位置数为 2,默认缺省时为0

// 创建一个 Light 对象来控制 GL_LIGHT2 的参数。
osg::ref_ptr<osg::Light> light = new osg::Light;  
light->setLightNum( 2 ); 

用户程序可以使用其方法设置光的环境色,散射颜色,镜面反射颜色。用户可以创建点光,直线光或者锥光,也可以指定衰减参数,使得光的密度根据距离的不同逐渐削减。下面的代码创建了一个 Light 对象并设置了一些常用的参数。

// 这段代码创建了一个光源对象,并设置了其环境光、漫反射、镜面反射、位置、方向以及聚光灯的光锥角度。
// 这些属性的设置将影响到光源在场景中的光照效果。

// 创建一个 osg::Light 对象的引用指针
osg::ref_ptr<osg::Light> light = new osg::Light; 

// 设置环境光分量(Ambient),环境光是在没有明确来源的情况下由周围物体反射的光线
light->setAmbient(osg::Vec4(.1f, .1f, .1f, 1.f));

// 设置漫反射分量(Diffuse),漫反射是光线撞击表面后,从表面反射的光线,用于表示表面的基本颜色
light->setDiffuse(osg::Vec4(.8f, .8f, .8f, 1.f));

// 设置镜面反射分量(Specular),镜面反射是从光源直接反射到观察者的光线,用于表示光泽或高光部分
light->setSpecular(osg::Vec4(.8f, .8f, .8f, 1.f));

// 设置光源位置
light->setPosition(osg::Vec3(0.f, 0.f, 0.f)); // 光源的位置,这里设置为 (0, 0, 0)

// 设置光源方向
light->setDirection(osg::Vec3(1.f, 0.f, 0.f)); // 光源的方向,这里设置为 (1, 0, 0)

// 设置聚光灯的光锥角度,这个角度决定了聚光灯的光束范围
light->setSpotCutoff(25.f); // 光锥角度,这里设置为 25 度

要添加 Light 对象到场景中,首先要创建一个 LightSource 节点,将 Light 添加到 LightSource 中,并将 LightSource 关联到场景图形中灯光的位置可以由LightSource 节点在场景图形中的位置决定。OSG 根据当前 LightSource 节点的变换状态来改变灯光的位置。
OSG 程序开发师往往将 LightSource 关联为 MatrixTransform 的子节点,以控制灯光的位置,如下面的代码所示:

// 创建灯光并设置其属性。
osg::ref_ptr<osg::Light> light = new osg::Light; 
// ... 
// 创建 MatrixTransform 节点,以决定灯光的位置。
osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform; 
osg::Matrix m; 
m.makeTranslate( osg::Vec3( -3.f, 2.f, 5.f ) ); 
mt->setMatrix( m ); 

// 添加 Light 对象到 LightSource。
// 然后添加 LightSource 和 MatrixTransform 到场景图形。
osg::ref_ptr<osg::LightSource> ls = new osg::LightSource; 
parent->addChild( mt.get() ); 
mt->addChild( ls.get() ); 
ls->setLight( light.get() ); 

OSG 在缺省情况下根据 LightSource 的当前变换位置来确定灯光的位置也可以通过设置 LightSource 的参考系来禁止这一特性。下面的代码将使 OSG 忽略 LightSource 的当前变换,而是把灯光的位置作为一个绝对值来看待。

osg::ref_ptr<osg::LightSource> ls = new osg::LightSource; 
ls->setReferenceFrame( osg::LightSource::ABSOLUTE_RF ); 

// 设置光源的参考框架:setReferenceFrame() 方法用于设置光源节点的参考框架。
// 在这里,osg::LightSource::ABSOLUTE_RF 表示使用绝对参考框架。
// 绝对参考框架意味着光源节点的位置和方向将使用全局坐标系进行描述,而不是相对于其父节点的坐标系。
// 相对参考框架(RELATIVE_RF):如果选择相对参考框架,则光源节点的位置和方向将相对于其父节点进行描述。
// 这种设置通常用于将光源附加到移动的物体上,以使光源随着物体的移动而移动。

4、材质属性
状态属性类 osg::Material 封装了 OpenGL 的 glMaterial()和 glColorMaterial()指令的函数功能。要在用户应用程序中设置材质属性,首先要创建一个 Material对象,设置颜色和其它参数,然后关联到场景图形的 StateSet 中。
Material 类允许用户设置全局、散射、镜面反射和放射材质的颜色,以及镜面和高光指数参数。 Material 类定义了枚举量 FRONT , BACK 和FRONT_AND_BACK,以便用户程序为几何体的正面和背面的设置材质属性。
举例来说,下面的代码为一个几何图元的正面设置了散射颜色,镜面反射颜色和镜面指数的参数。

// 获取或创建节点的状态集合(StateSet)
osg::StateSet* state = node->getOrCreateStateSet(); 

// 创建一个 osg::Material 对象的引用指针
osg::ref_ptr<osg::Material> mat = new osg::Material; 

// 设置材质的漫反射分量(Diffuse),表示物体表面对入射光的散射
mat->setDiffuse(osg::Material::FRONT, osg::Vec4(.2f, .9f, .9f, 1.f));

// 设置材质的镜面反射分量(Specular),表示物体表面对光线的反射
mat->setSpecular(osg::Material::FRONT, osg::Vec4(1.f, 1.f, 1.f, 1.f));

// 设置材质的高光强度(Shininess),表示物体表面的光泽程度,数值越大表示光泽越强
mat->setShininess(osg::Material::FRONT, 96.f);

// 将材质属性设置到节点的状态集合中
state->setAttribute(mat.get());

与 OpenGL 类似,此处镜面指数的值必须在 1.0 到 128.0 之间,除非用户指定了拓宽这一范围的方法。
在进行很多 OpenGL 的操作时,直接设置材质属性可能会过于耗费资源。而一种名为颜色跟踪材质(color material)的特性,可以允许用户程序通过改变当前颜色的方法,自动改变某一特定的材质属性。在许多情形下,这一操作比直接修改材质属性的效率要更高,还能加强光照场景和无光照场景的联系,并满足应用程序对材质的需要。要允许颜色跟踪材质的特性,需要调用 Material::setColorMode()方法。
Material 类为之定义了枚举量 AMBIENT,DIFFUSE,SPECULAR,EMISSION,AMBIENT_AND_DIFFUSE 以及 OFF。缺省情况下,颜色跟踪模式被设置为 OFF,颜色跟踪材质被禁止。如果用户程序设置颜色跟踪模式为其它的值,那么 OSG将为特定的材质属性开启颜色跟踪材质特性,此时主颜色的改变将会改变相应的材质属性。
下面的代码段将允许颜色跟踪材质,此时几何体正面的环境材质和散射材质颜色将自动跟踪当前颜色的改变而改变。

// 获取或创建节点的状态集合(StateSet)
osg::StateSet* state = node->getOrCreateStateSet(); 

// 创建一个 osg::Material 对象的引用指针
osg::ref_ptr<osg::Material> mat = new osg::Material; 

// 设置材质颜色模式为 AMBIENT_AND_DIFFUSE,即同时影响环境光和漫反射光
mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);

// 将设置好的材质属性应用到节点的状态集合中
state->setAttribute(mat.get());

注意:根据颜色跟踪模式的取值不同,Material 类会自动允许或禁止GL_COLOR_MATERIAL。因此用户程序不需要调用 setAttributeAndModes()来允许或禁止相关的模式值。

在 OpenSceneGraph (OSG) 中,光照效果的实现涉及到几个重要的类,包括 LightLightSourceLightModel。以下是它们的作用和功能:

  1. Light
    - Light 类表示一个光源对象,用于描述光源的属性,例如位置、颜色、强度等。
    - 通过设置 Light 对象的属性,可以控制光源对场景中几何体的照射效果。
    - 一个 Light 对象通常被关联到一个 LightSource 对象中,以便将光源添加到场景图中。
  2. LightSource
    - LightSource 类表示一个光源节点,用于在场景中添加光源。
    - LightSource 节点可以包含一个或多个 Light 对象,从而实现多个光源的效果。
    - 通过将 Light 对象添加到 LightSource 节点中,可以在场景中创建并控制多个光源,以实现更复杂的光照效果。
  3. LightModel
    - LightModel 类用于设置全局光照模型的属性。
    - 全局光照模型影响整个场景中的光照计算,包括环境光、漫反射光和镜面反射光等。
    - 可以通过设置 LightModel 对象的属性,如环境光颜色、全局镜面反射指数等,来调整整个场景的光照效果。
    综上所述,Light 类用于描述单个光源的属性,LightSource 类用于在场景中添加光源节点,而 LightModel 类用于设置全局光照模型的属性。这些类共同作用于 OpenSceneGraph 中的光照系统,帮助开发者实现各种光照效果。

下面有一个综合测试案例,涉及到了Light、LightSource和LightModel类:

void test18() {

	// 创建场景根节点
	osg::ref_ptr<osg::Group> root = new osg::Group;

	// 创建一个立方体几何体
	osg::ref_ptr<osg::Geode> cubeGeode = new osg::Geode;
	osg::ref_ptr<osg::ShapeDrawable> cubeDrawable = new osg::ShapeDrawable(new osg::Box(osg::Vec3(2.0f, 0.0f, 0.0f), 1.0f));
	cubeGeode->addDrawable(cubeDrawable);

	// 创建一个球体几何体
	osg::ref_ptr<osg::Geode> sphereGeode = new osg::Geode;
	osg::ref_ptr<osg::ShapeDrawable> sphereDrawable = new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(-2.0f, 0.0f, 0.0f), 1.0f));
	sphereGeode->addDrawable(sphereDrawable);

	// 将几何体添加到场景根节点中
	root->addChild(cubeGeode);
	root->addChild(sphereGeode);

	// 创建两个光源
	osg::ref_ptr<osg::Light> light1 = new osg::Light;
	light1->setPosition(osg::Vec4(5.0f, 5.0f, 5.0f, 1.0f));
	osg::ref_ptr<osg::LightSource> lightSource1 = new osg::LightSource;
	lightSource1->setLight(light1);

	osg::ref_ptr<osg::Light> light2 = new osg::Light;
	light2->setPosition(osg::Vec4(-5.0f, -5.0f, -5.0f, 1.0f));
	osg::ref_ptr<osg::LightSource> lightSource2 = new osg::LightSource;
	lightSource2->setLight(light2);

	// 将光源节点添加到场景根节点中
	root->addChild(lightSource1);
	root->addChild(lightSource2);

	// 设置全局光照模型
	osg::ref_ptr<osg::LightModel> lightModel = new osg::LightModel;
	lightModel->setAmbientIntensity(osg::Vec4(0.2f, 0.2f, 0.2f, 1.0f)); // 设置环境光强度
	root->getOrCreateStateSet()->setAttribute(lightModel);

	// 创建查看器
	osgViewer::Viewer viewer;
	viewer.setSceneData(root);

	// 运行查看器
	viewer.run();
}

2.5 文件IO

大多数应用程序需要手动编程来创建一些几何对象。但是,应用程序通常需要从文件中读取并显示大的,复杂的模型。这就需要使用一个函数来读取文件中的模型,并返回预设的场景图形信息。
osgDB 库提供了用户程序读取和写入 2D 图像和 3D 模型文件的函数接口。osgDB 库还负责管理 OSG 的插件系统,以实现对不同文件格式的支持。
在读取模型文件时,使用.osg 插件将场景图形写入一个.osg 文件。光照示例中使用.osg 插件从名为 cow.osg的文件中读取了子图形信息,而纹理映射示例中使用.png 插件读取纹理图像。但是,前面的文字并没有介绍文件 I/O 功能的用法。本节将更详细地阐述插件的使用,以便用户可以在自己的程序中高效地使用它们。本文将介绍读取和写入文件的函数接口,OSG 搜索文件的方式,以及 OSG 选择相应的插件以读取文件的方式

2.5.1 接口

osgDB 提供了文件 I/O 的函数接口,从而将插件系统完全隐藏在用户程序之下。用户需要两个 osgDB 的头文件来定义这个接口:

#include <osgDB/ReadFile> 
#include <osgDB/WriteFile> 

用户程序如果要使用 osgDB 的文件 I/O 功能,可以在源代码中包含这两个头文件。在其中的 osgDB 命名空间里定义了一系列用于实现文件 I/O 的函数。
在这里插入图片描述

1、读取文件
使用函数 osgDB::readNodeFile()和 osgDB::readImageFile()来读取 3D 模型和2D 图像文件。

osg::Node* osgDB::readNodeFile( const std::string& filename ); 
osg::Image* osgDB::readImageFile( const std::string& filename ); 

用户可以使用 readNodeFile()读取 3D 模型文件。OSG 将根据文件的扩展名来识别文件类型,并使用相应的插件转换文件到场景图形中。readNodeFile()返回一个指向场景图形节点的指针。同样,readImageFile()可以读取 2D 图像文件并返回指向 Image 对象的指针。文件名参数中可以包含绝对路径或者相对路径。如果用户指定了绝对路径,OSG 将到指定的位置去搜索文件。
如果因为某些原因,OSG 不能读取文件,那么这两个函数都会返回 NULL指针

2、写入文件
使用函数 osgDB::writeNodeFile()和 osgDB::writeImageFile()将数据写入到 3D模型或 2D 图像文件中。

bool osgDB::writeNodeFile( const osg::Node& node,  const std::string& filename ); 
bool osgDB::writeImageFile( const osg::Image& image,  const std::string& filename );

如果因为某些原因 OSG 不能写入文件,上述函数将返回 false。

2.6 NodeKit和osgText

OSG 提供了丰富的设计功能,不过,开发者还是经常需要从 OSG 的核心节点类中派生出自己的特定节点。而这些派生的功能往往并不属于 OSG 核心的部分,而是更适合作为辅助的模块库存在。所谓 NodeKit 就是指一种开发集成库,它扩展自 OSG 核心功能的特定节点类(Node),渲染属性类(StateAttribute)和绘图类(Drawable),并且使用 osg 包装类(wrapper)对这些新的功能类提供.osg文件格式的支持。本节将说明osgText的使用。

2.6.1 osgText组件

osgText 库定义了一个命名空间,osgText。在这个命名空间中有一些十分实用的字体加载和文字渲染类。
osgText 库的核心组件是 osgText::Text 类。Text 继承自 Drawable,因此用户程序应当使用 addDrawable()方法把 Text 实例添加到 Geode 中(与添加 Geometry实例的方法相同)。Text 可用于显示一个任意长度的字符串。因此,用户程序可以为每个将要显示的字符串创建一个相应的 Text 对象。
osgText 库的另一个核心组件是 osgText::Font 类。osgText 的函数可以根据字体文件的名称来创建 Font 对象。Font 类使用 FreeType 插件来读取字体文件。用户程序将 Font 对象和 Text 对象相关联时,Font 将创建一个用于绘制字符串图形的纹理贴图。在渲染时,Text 将使用与该图形相符的纹理坐标,为文本中的每一个字符绘制一个已添加纹理的四边形。osgText 库还定义了一个 String 类,以支持多字节字符(multibyte)和各类文字编码。

2.6.2 使用osgText

Text 和 Font 对象的定义分别位于两个头文件中。下面的代码演示了在程序中包含它们的方法。

#include <osgText/Font> 
#include <osgText/Text> 

要在程序中使用 osgText,用户通常要遵循下面三个步骤
1、如果要使用一种字体显示多行文字,只需要创建一个 Font 对象,然后在Text 对象间共享即可。
2、为每一段要显示的字符串建立一个 Text 对象。指定对齐方式,文字方向,位置和大小参数。将步骤 1 中创建的 Font 对象关联到新的 Text 对象中。
3、使用 addDrawable()函数将 Text 对象添加到 Geode 节点。用户可以向一个Geode 添加多个 Text 对象,或者根据自己的需要创建多个 Geode 节点。将 Geode 节点作为场景图形的子节点加入。
下面的代码演示了使用Courier New TrueType字体文件cour.ttf创建一个Font对象的方法:

osg::ref_ptr<osgText::Font> font = 
 osgText::readFontFile( "fonts/cour.ttf" ); 

函数 osgText::readFontFile()可以方便地使用 OSG 的 FreeType 插件来加载字体文件。它使用 osgDB 库来查找文件。如果 readFontFile()函数不能找到指定的字体,或者指定的文件无效,那么它将返回 NULL
下面的代码用于创建一个 Text 对象,关联字体,并设置要显示的文字。

osg::ref_ptr<osgText::Text> text = new osgText::Text; 
text->setFont( font.get() ); 
text->setText( “Display this message.);

Text::setText()可以使用 std::string 作为输入参数,不过它使用 osgText::String来实现多字符编码(multibyte encodings)的支持。osgText::String 的一些非显式构造函数可以接收 std::string 或者字符串常量。在上面的代码中,调用 setText()时使用的字符串常量参数将被转换成 osgText::String,这是在运行时完成的。
在这里插入图片描述
如果使用 readFontFile()加载字体失败,且用户程序在运行的系统上无法找到任何可用的字体,那么不必调用 Text::setFont()。这样 Text 会使用缺省且保证可用的字体来代替
Text 有数种方法来控制字的大小,外观,方向和位置。下面的部分将讨论如何使用这些参数。
1、坐标位置
Text 与 Geometry 相似,都可以在拣选(cull)和绘制(draw)遍历中变换自己的坐标位置。缺省情况下,对象坐标的位置在其原点。用户可以使用Text::setPosition()方法改变这一数值,此方法的输入参数为一个 Vec3 变量

// 在(10.0,0.0,1.0)绘制文字。
text->setPosition( osg::Vec3( 10.f, 0.f, 1.f ) );

坐标位置本身并不能完全决定文字最后出现的位置。Text 将变换的位置,方向和对齐方式这些参量结合到一起,来确定文字渲染的实际区域。
2、文字方向
方向决定了渲染文字在 3D 空间中的朝向。用户可以使用Text::setAxisAlignment() 方法来设置文字的位置,其输入参数为Text::AxisAlignment 的枚举量。如果要创建一个一直朝向视口的广告牌形式的文字,可以使用 Text::SCREEN。

text->setAxisAlignment( osgText::Text::SCREEN );

或者,你也可以让文字沿着轴对齐的平面水平放置。缺省的文字方向为Text::XY_PLANE,即,文字朝向 Z 轴水平放置在 XY 平面上。

text->setAxisAlignment( osgText::Text::XY_PLANE ); 
enum AxisAlignment
{
	// 在 XY 平面上对齐。这意味着物体或图形的主要方向与 XY 平面平行,朝向与 Z 轴垂直。
    XY_PLANE,
    // 在 XY 平面上对齐,但是反向。物体或图形的主要方向与 XY 平面平行,但朝向与 Z 轴正半轴相反,即物体或图形的底部位于 XY 平面上。
    REVERSED_XY_PLANE,
    // 在 XZ 平面上对齐。物体或图形的主要方向与 XZ 平面平行,朝向与 Y 轴垂直。
    XZ_PLANE,
    // 与XZ_PLANE朝向相反
    REVERSED_XZ_PLANE,
    // 同上
    YZ_PLANE,
    REVERSED_YZ_PLANE,
    // 相对于屏幕或视口对齐。通常用于指定屏幕上的 UI 元素的方向。
    // 朝向与屏幕或视口的法线垂直,即垂直于观察者的方向。
    SCREEN,
    // 用户自定义的旋转方向。可以根据需要进行自定义旋转,
    // 可以指定任意的旋转角度和轴向,以实现特定的对齐要求。
    USER_DEFINED_ROTATION
};

3、对齐方式
此处的对齐方式与字处理软件中文字对齐方式,或者电子表格中单元对齐的概念相似。它决定了渲染文字相对于其坐标位置(使用 setPosition()函数)的水平和竖直对齐方式,Text 定义了枚举类型 Text::AlignmentType,其中的枚举量可用于确定文字的水平和竖直对齐方式。缺省选项是 Text::LEFT_BASE_LINE,即沿着文字的左边界水平放置,沿着文字的底线竖直放置。下图演示了各种对齐方式下,文字相对于自身坐标位置的差异。枚举值表示左右和上下的对齐方式,由下划线分割。

text->setAlignment( osgText::Text::CENTER_TOP );

在这里插入图片描述

enum AlignmentType
{
    LEFT_TOP,
    LEFT_CENTER,
    LEFT_BOTTOM,

    CENTER_TOP,
    CENTER_CENTER,
    CENTER_BOTTOM,

    RIGHT_TOP,
    RIGHT_CENTER,
    RIGHT_BOTTOM,

    LEFT_BASE_LINE,
    CENTER_BASE_LINE,
    RIGHT_BASE_LINE,

    LEFT_BOTTOM_BASE_LINE,
    CENTER_BOTTOM_BASE_LINE,
    RIGHT_BOTTOM_BASE_LINE,

    BASE_LINE = LEFT_BASE_LINE /// default.

};

4、文字尺寸
缺省的字符高度为 32 个物体坐标单元。而字符的宽度可变,由字体的性质决定。Text 服务根据 Font 对象中保存的信息,按照正确的比率渲染文字。
改变缺省的文字高度,可以调用 Text::setCharacterSize(),下面的代码将字符的高度改变为一个物体坐标单元。

text->setCharacterSize( 1.0f );

缺省情况下,Text 将 setCharacterSize()传入的参数视为物体的实际大小。不过 Text 也允许用户指定字符在屏幕上显示的大小,而不是物体的真实坐标。使用 Text::setCharacterSizeMode()方法可以指定使用屏幕坐标。

text->setCharacterSizeMode( osgText::Text::SCREEN_COORDS );

/// CharacterSizeMode 枚举定义了文本字符在渲染时的不同大小模式。
enum CharacterSizeMode
{
	/// 根据对象坐标系进行缩放。文本字符的大小将与场景中的对象的大小相对应。
    OBJECT_COORDS,   // 默认
    /// 根据屏幕坐标进行缩放,以确保文本字符在屏幕上的大小保持恒定。
    /// 无论对象在场景中的距离如何变化,文本字符始终保持屏幕上的固定大小。
    SCREEN_COORDS, 
    /// 根据对象坐标系进行缩放,但同时受屏幕坐标的限制。
    /// 文本字符的大小将自动受限于字体的高度,以保持文本字符在屏幕上可读性的同时,
    /// 也保持了其在对象坐标系中的大小,避免在远距离观察时文本字符变得过于显眼。
    OBJECT_COORDS_WITH_MAXIMUM_SCREEN_SIZE_CAPPED_BY_FONT_HEIGHT 
};

将字符高度的设定模式改为屏幕坐标后,Text 将根据视角,适当放缩文字几何体以保持它在屏幕上的恒定尺寸。

5、分辨率
用户程序通常需要改变字体纹理贴图的图形分辨率,以避免文字出现模糊。缺省情况下,osgText 为每个图形分配了 32×32 个像素元。要改变这个数值的话,可以使用 **Text::setFontResolution()**方法。下面的代码增加了字体的分辨率,osgText将因此为每个图形分配 128×128 个像素元。

text->setFontResolution( 128, 128 );

如果多个分辨率不同的 Text 文字对象共享一个 Font 对象,且不同的文本中包含了同样的字符,那么字体纹理贴图中将包含这些字符的多个冗余的纹理拷贝,它们的分辨率是各不不同的。
注意,字体分辨率的上升也会使得硬件资源的需求,譬如显卡纹理的内存上升。因此用户应当在文字的生成结果可以接受的前提下,选用最小的字体分辨率。

6、颜色
缺省情况下,Text 使用白色来绘制文字。用户可以使用 **Text::setColor()**方法来改变这一缺省值。要设置颜色的话,可以向 setColor()指定一个 osg::Vec4 的RGBA 颜色值。下面的代码将 Text 渲染文字的颜色改为蓝色。

// 设置文字颜色为蓝色。
text->setColor( osg::Vec4( 0.f, 0.f, 1.f, 1.f ) );

测试案例:

void test19() {

	// 创建场景根节点
	osg::ref_ptr<osg::Group> root = new osg::Group;

	// 创建一个用于保存几何信息的对象
	osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;

	// 1、创建四个顶点的数组
	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));

	// 2、创建四种颜色的数组
	osg::ref_ptr<osg::Vec4Array> c = new osg::Vec4Array;
	geom->setColorArray(c.get(), osg::Array::Binding::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));	// 白色

	// 3、为唯一的法线创建一个数组
	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));

	// 4、由保存的数据绘制四个顶点的多边形
	geom->addPrimitiveSet(
		new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));

	// 创建一个 Geode 并将四边形的几何体添加进去
	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	geode->addDrawable(geom);

	// 创建四个文字对象并设置文字内容、位置和颜色
	osg::ref_ptr<osgText::Text> text1 = new osgText::Text;
	text1->setText("Top Left");
	text1->setCharacterSizeMode(osgText::Text::SCREEN_COORDS);
	text1->setCharacterSize(12.f);
	text1->setPosition(osg::Vec3(-1.0f, 0.0f, 1.0f));
	text1->setColor(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
	text1->setAxisAlignment(osgText::Text::SCREEN);

	osg::ref_ptr<osgText::Text> text2 = new osgText::Text;
	text2->setText("Top Right");
	text2->setCharacterSizeMode(osgText::Text::SCREEN_COORDS);
	text2->setCharacterSize(12.f);
	text2->setPosition(osg::Vec3(1.0f, 0.0f, 1.0f));
	text2->setColor(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); 
	text2->setAxisAlignment(osgText::Text::SCREEN);

	osg::ref_ptr<osgText::Text> text3 = new osgText::Text;
	text3->setText("Bottom Right");
	text3->setCharacterSizeMode(osgText::Text::SCREEN_COORDS);
	text3->setCharacterSize(12.f);
	text3->setPosition(osg::Vec3(1.0f, 0.0f, -1.0f));
	text3->setColor(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
	text3->setAxisAlignment(osgText::Text::SCREEN);

	osg::ref_ptr<osgText::Text> text4 = new osgText::Text;
	text4->setText("Bottom Left");
	text4->setCharacterSizeMode(osgText::Text::SCREEN_COORDS);
	text4->setCharacterSize(12.f);
	text4->setPosition(osg::Vec3(-1.0f, 0.0f, -1.0f));
	text4->setColor(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
	text4->setAxisAlignment(osgText::Text::SCREEN);

	// 将文字对象添加到 Geode 中
	osg::ref_ptr<osg::Geode> textGeode = new osg::Geode;
	textGeode->addDrawable(text1);
	textGeode->addDrawable(text2);
	textGeode->addDrawable(text3);
	textGeode->addDrawable(text4);

	// 将 Geode 添加到场景根节点中
	root->addChild(geode);
	root->addChild(textGeode);

	// 创建查看器
	osgViewer::Viewer viewer;
	viewer.setSceneData(root);

	// 运行查看器
	viewer.run();

}

在这里插入图片描述

  • 18
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: OSG快速入门指南是一份PDF文件,旨在帮助初学者快速了解和入门使用OSG(OpenSceneGraph)。以下是关于该指南的300字中文回答: OSG快速入门指南是一本非常实用的指南,对于想要快速学习和使用OSG的人来说确实是不可或缺的。在这份指南中,你将会找到一些简单易懂的教程、实例和步骤,帮助你逐步掌握OSG的基本概念和操作。无论你是从事三维图形开发,还是希望创建虚拟现实应用程序,这本指南都将为你提供很多有益的信息。 在OSG快速入门指南中,你将会学到如何安装和配置OSG环境,以及如何使用OSG库和工具来创建、渲染和操作三维场景。指南中会介绍OSG中的基本概念,例如节点、场景图、相机和渲染器等,并教你如何使用这些概念来构建和控制你的场景。此外,该指南还会涉及一些高级主题,如材质和光照效果、动画和碰撞检测等,帮助你进一步优化和增强你的应用程序。 除了简明扼要的教程外,OSG快速入门指南还包括一些实用的实例代码和示例项目,你可以借鉴和参考这些例子来加深对OSG的理解和掌握。此外,该指南还提供了有关最佳实践和常见问题解答的内容,这对于初学者来说非常有帮助。 总而言之,OSG快速入门指南是一本简单易懂、实用性强的指南,它将帮助你快速入门并熟练掌握OSG的基本概念和技术。不论你是新手还是有一定经验的开发者,这本指南都将为你提供很多有益的信息和实用的技巧,让你能够更好地利用OSG来开发出令人惊叹的三维应用程序。 ### 回答2: osg快速入门指南是一本介绍OpenSceneGraph(OSG)的PDF文档。OpenSceneGraph是一个强大的开源场景图形库,用于快速开发图形应用程序。该指南旨在帮助读者快速掌握OSG的基本概念和使用方法。 该指南首先介绍了OSG及其主要特性,如高性能渲染引擎、可移植性和开放性。然后,它逐步引导读者了解OSG的安装和配置过程,包括编译和链接所需的库文件。 接下来,指南介绍了OSG的核心概念,如场景图、节点和状态集。它详细解释了如何创建和管理这些元素,并说明它们在渲染中的作用。 然后,指南通过简单的示例演示了OSG的用法。它展示了如何加载和显示3D模型、应用光照和材质以及实现用户交互。这些示例提供了实际的编码示例,帮助读者理解OSG的实际应用。 最后,指南提供了一些实用的技巧和技巧,帮助读者更好地利用OSG的功能。它介绍了如何优化渲染性能、处理大规模场景以及与其他图形库和工具集成等。 总之,osg快速入门指南是一本适合初学者的实用教程,帮助他们快速掌握OSG的基本知识和技巧。通过阅读这本指南,读者将能够开始使用OSG开发出高性能的图形应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VVPU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值