基本几何体的绘制只适用于简单的编程,当场景中需要加载一个很复杂的模型时,还是需要从外部导入。osgDB 库
提供了读取二维图像和三维模型的接口,同时,也管理着第三方插件系统,以实现对不同格式文件的读取。
1、OSG 支持的文件格式
由于 OSG 包含庞大的第三方插件库,所以 OSG 支持的文件格式也非常多,如各种三维模型、图
片和视频等文件,这也是 OSG 的一大优势,它可以满足各行各业的需求。
1.1 三维模型文件格式
OSG支持的三维模型文件格式类型如下表所示。
1.2 打包及网络传输格式
OSG支持的打包及网络传输文件格式类型如下表所示。
1.3 字体文件格式
OSG支持的字体文件格式类型如下表所示。
1.4 伪插件文件格式
OSG支持的伪插件文件格式类型如下表所示。
2、.osg 文件和.ive 文件
OpenSceneGraph 支持如下两种本地文件格式:
.osg OpenSceneGraph native ascii format
.ive OpenSceneGraph native binary format。
2.1 .osg 文件
.osg 文件格式是标准的 ASCII 文本,它并不是一种可以实现高效存储和读取的数据结构,但它是一种极好的调试工具。由于它是一种文本文件,所以清晰可读。在应用程序开发时,如果遇到了意料不到的渲染结果,可以将渲染结果保存为.osg 文件,然后对其进行手动的修改从而纠正错误,这对实际调试程序来说是非常方便的。
2.2 .ive 文件
为了加快模型导入和显示的速度,每个视景驱动软件都有自己的二进制格式,如 Vega 的 FST、Vega Prime 的 VSB 和 Performer 的 PFB 等。OSG 里面的就是 IVE,IVE 就是为了性能而生的,它把场景树存成二进制文件。所以在分发应用程序时,可以把模型转为 IVE,并把纹理打包进去(osgconvmyfile.flt——compressed myfile.ive),这样既可以达到保护自己劳动成果的目的,还能提高应用程序的性能。
3、文件读写的流程
osgDB 库允许用户程序加载、使用和写入 3D 数据库,它采用插件管理的架构,可以支持大量常见的 2D 图形和 3D 模型文件格式。osgDB 负责维护插件的信息注册表,并负责检查将要被载入的 OSG插件接口的合法性。OSG 可以支持自己的文件格式。.osg 文件是对场景图形的一种无格式 ASCII 码文本描述,而.osga文件是一组.osg 文件的有序集合。osgDB 库包含了以上文件格式的支持代码。另外,OSG 还支持一种二进制的.ive 格式。
由于大型的 3D 地形数据库通常是多段数据块的组合体。因此,应用程序从文件中读取各部分数据库信息时,需要在不干扰当前渲染的前提下以后台线程的方式进行,osgDB::DatabasePager 提供了这样的功能。
3.1 文件的读取与保存
3.1.1 文件的读取
文件读取的简单代码如下:
#include<osgDB/ReadFile>
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("cow.osg");
osg::ref_ptr<osg::Image> image = osgDB::readImageFile("lz.rgb");
上面代码中,函数类的参数可以包含绝对路径或相对路径。如果是绝对路径,OSG 会去指定的位置搜索该文件;如果是相对路径,就要复杂一点了,它会从数据路径列表中来搜索文件。在本章会提到环境变量 OSG_FILE_PATH,这个是在 OSG 中预先定义的。在用户的应用程序中,除了设置环境变量以外,还可以手动添加数据文件路径列表。有如下两种方式:
(1)第一种方式的程序代码如下:
osgDB::FilePathList pathList = osgDB::getDataFilePathList();
pathList.push_back("…");
pathList.push_back("…");
osgDB::setDataFilePathList(pathList);
(2)第二种方式的程序代码如下:
osgDB::Registry::instance()->getDataFilePathList().push_back(newpath );
osgDB::Registry 是一个单态类(singleton),因此,要调用函数 getDataFilePathList(),就需要使用单态类的实例。osgDB::FilePathList 就是一个简单的 std::deque< std::string >。
文件读取失败的原因可能很多,读者可以通过设置调试信息 NOTIFY 来确定,当再次读取时,就会提示相关错误或者警告信息。很多时候可能是因为找不到插件或者文件不存在造成的,这也要根据输出信息来具体确定到底是什么错误。
3.1.2.文件的保存
文件保存的简单代码如下:
#include <osgDB/WriteFile>
osgDB::writeNodeFile(*node, "saved.osg");
文件保存失败的原因也很多。读者可以通过设置调试信息 NOTIFY 来确定,当再次保存时,就会提示相关错误或者警告信息。很多时候可能是因为找不到插件或者该扩展名不支持造成的,这也要根据输出信息具体来确定到底是什么错误。需要注意的是,当 OSG 保存文件时,如果本地有同名文件,会覆盖并不会给出任何警告的信息,所以在保存时需要自行检查本地文件是否存在同名的。
3.2 文件读写进度
面对海量数据时,读取可能需要耗费很长的时间,这时有必要显示正在读取的数据量和读取进度等信息。下面先介绍 C++流缓冲的一些知识。众所周知,C++标准库的 iostream 提供了如下 3 种形式的缓冲:
不带缓冲区,这样与 fwrite 等操作一致。
空间自动管理的缓冲区,下一个可写位置总是为末边界+1。
外置的缓冲区,如定义局部字符串数组,并将此区域传递给 iostream。
在带缓冲区的情况下,iostream 本身不具体负责其相关功能,而把此任务交给了 basic_streambuf及其派生类,常见的如 basic_filebuf(用于文件操作的缓冲)和 basic_stringbuf(用于字符串操作的缓冲区)。basic_iostream 的两大分支 basic_istream 和 basic_ostream 都是 basic_streambuf 的友元类,而basic_iostream 包含了一个 basic_streambuf 的派生类的实例,该实例可以通过 basic_iostream 的成员函数 rdbuf()来获取,并通过构造函数传入。我们经常使用的流操作符“<<”实际上是通过调用 basic_streambuf 的相关操作实现的。
要控制标准输入/输出流的缓冲区行为,必须生成自己的 basic_streambuf 派生类,并生成一个该类的对象,传递给标准输入/输出流。由于基类 basic_streambuf 的很多功能没有具体实现,为了方便操作,我们需要从 basic_filebuf 或basic_stringbuf 派生。在第 6.2.5 节的示例中,显示了读取一个.osg 文件的读取进度。前面已经讲到,.osg 文件是一种 ASCII 文件,因此,.osg 支持数据流操作。在 OSG 支持的文件中,只有.osg、.ive 及一些图像文件支持流操作,都可以用数据流操作来显示读取进度。
所有的 osgDB 插件都可以使用 osgDB::readNodeFile()、osgDB::writeNodeFile()和 osgDB::readImageFile()之类的函数来执行文件的读取和写入操作,其中一些插件还允许使用标准数据流来执行一些低层级的操作,如从用户自定义的数据流中读写数据。由于 C++标准模板库(STL)的便利性,用户可以编写自定义的数据流缓存类来跟踪 uflow()方法,从而获取当前读入的字节数。用户定义了正确的数据流缓存类(如例子程序所示)后,就可以按照如下的步骤来读取文件:
(1)给出文件名,并搜索用于读取该文件的插件,也就是调用 osgDB::Registry::instance()->getReaderWriterForExtension(),传递文件扩展名作为该函数的输入参数,并保存返回的 osgDB::ReaderWriter对象。
(2)创建自定义数据流缓存类的实例,将文件名传递给类的构造函数。此时函数将按照类似于standard std::basic_filebuf<>的方法打开文件。
(3)通过自定义类的实例创建 std::istream 对象。
(4)调用步骤(1)中获取的 ReaderWriter 对象的 readNode()方法,并将步骤(3)创建的 istream对象作为其输入参数。此时,OSG 将读入文件,同时调用自定义类的 uflow()方法,该方法将负责显示读入字节的进度信息。
3.1.2.读取进度示例
#include <windows.h>
#include <iostream>
#include <fstream>
#include <osgViewer/Viewer>
#include <osgDB/ReaderWriter>
#include <osgDB/ReadFile>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgDB/Registry>
#include <osgUtil/Optimizer>#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")class CRenderingThread : public OpenThreads::Thread
{
public:CRenderingThread(osgDB::ifstream* fin) :_fin(fin){fin->seekg(0, std::ifstream::end);_length = fin->tellg();fin->seekg(0, std::ifstream::beg);};virtual ~CRenderingThread() {};virtual void run(){int pos = _fin->tellg();int nProgres = 0;std::cout << nProgres << "%%" << std::endl;while (pos < _length){pos = _fin->tellg();int nTemp = (int)(100.0 * pos / _length);if (nTemp > nProgres && nTemp%5 == 0){std::cout << nTemp << "%%" << std::endl;nProgres = nTemp;}}std::cout << "读取完成!" << std::endl;};protected:osgDB::ifstream* _fin;int _length;
};class ReadFileCB : public osgDB::Registry::ReadFileCallback
{
public:virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& file, const osgDB::ReaderWriter::Options* opt){//第一步是获取OSG、IVE ReaderWriterstd::string ext = osgDB::getLowerCaseFileExtension(file);osgDB::ReaderWriter* rw = osgDB::Registry::instance()->getReaderWriterForExtension(ext);if (!rw){return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;}std::string fileName = osgDB::findDataFile(file, opt);if (fileName.empty()){return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;}osgDB::ifstream istream(fileName.c_str(), std::ios::in | std::ios::binary);CRenderingThread crt(&istream);crt.startThread();if (istream){std::cout << "正在读取模型文件,进度如下:" << std::endl;osgDB::ReaderWriter::ReadResult rr = rw->readNode(istream);while (crt.isRunning()) {}return rr;}return osgDB::ReaderWriter::ReadResult::ERROR_IN_READING_FILE;}
};int main()
{osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;osgDB::Registry::instance()->setReadFileCallback(new ReadFileCB);viewer->setSceneData(osgDB::readNodeFile("ceep.ive"));viewer->setUpViewInWindow(600, 600, 1000, 800);return viewer->run();
}
3.3 OSG中文文件名及中文路径问题
在读写文件时,经常会遇到这样的情况,如果文件名包含中文路径就会导致无法读取或读取失败。因为我们使用的是 unicode 编码位模式,而 OSG 的库函数只支持 ANSI 编码或多字节编码。其实这是符合 C 语言规范的,因为 C 标准并不支持 unicode,只是很多 C 的实现将宽字符用 unicode 的位模式表示。这时我们需要通过 setlocale 函数将 unicode 编码的宽字符转换成一种可以支持的编码。Setlocale 函数的语法格式如下:
char * setlocale ( int category, const char * locale );
该函数的返回值是字符串,函数种类是操作系统与环境。该函数用来配置地域的信息,设置当前程序使用的本地化信息。参数 category 可以设置为如下数值。
LC_ALL:包括所有选项的功能。
LC_COLLATE:配置字符串比较,PHP 目前尚未实作出来本项。
LC_CTYPE:配置字符类别及转换,如全变大写 strtoupper()。
LC_MONETARY:配置金融货币,PHP 目前尚未实作。
LC_NUMERIC:配置小数点后的位数。
LC_TIME:配置时间日期格式,与 strftime()合用。
而参数 locale 若是空字符串,则会使用系统环境变量 locale。若 locale 为 0(NULL),则不会改变地域化配置,返回当前的地域值,若系统尚未实作则返回 false。
只需要做如下设置就可以实现编码格式的转换:
setlocale( LC_ALL, "Chinese-simplified" );
setlocale( LC_ALL, "chs" );
setlocale( LC_ALL, "ZHI" );
setlocale( LC_ALL, ".936" );
关于字符编码转换,其实有很多好用的开源工具包可用,如著名的 iconv。平时积累编码方面的知识对学习 OSG 是非常有帮助的,而且在程序开发时计算机编码也是一个非常重要的方面。
//创建一个节点,读取牛的模型
osg::ref_ptr<osg::Node> node1 = new osg::Node();
/*很关键的一个函数,用于识别 unicode 中文字符 */
setlocale( LC_ALL, "chs" );
node1 = osgDB::readNodeFile("牛.ive");