前言
本文在上篇文章的基础上,进一步深入探讨数据读取机制的实现细节。回顾 OpenSceneGraph (OSG),其通过 osgDB 库管理场景数据的读取和写入。常用的读取函数包括 osgDB::readNodeFile 和 osgDB::readImageFile,它们能够根据文件扩展名自动选择合适的插件进行加载。插件机制是 OSG 数据读取的核心,主要包括插件的注册、发现以及相关接口的定义(基类 osgDB::ReaderWriter 提供了这些接口的具体定义)。与此类似,VulkanSceneGraph (VSG) 也采用了插件化的数据读取机制。在 VSG 中,通过 vsg::read 函数读取数据,并由继承自 vsg::ReaderWriter 的子类负责具体的数据加载工作。
目录
- 1 数据读取
- 2 插件机制
- 3 osg转vsg
1 数据读取
auto options = vsg::Options::create();
options->fileCache = vsg::getEnv("VSG_FILE_CACHE");
options->paths = vsg::getEnvPaths("VSG_FILE_PATH");
// add OpenSceneGraph's support for reading and writing 3rd party file formats
options->add(vsg::VSG::create());
options->add(vsg::spirv::create());
options->add(osg2vsg::OSG::create());
// load VulkanSceneGraph scene graph
auto vsg_scene = vsg::read_cast<vsg::Node>(filename, options);
上述代码最后一行vsg::read_cast<vsg::Node>()会转调到vsg源码文件夹src\vsg\io下的read.cpp文件中的vsg::read(const&Path,ref_ptr<const Options>)函数,函数共包含两个逻辑:1) 当options中的sharedObjects不为空且其函数suitable传入文件路径返回true时,读取文件;2)直接读取文件,代码结构如下:
if (options && options->sharedObjects && options->sharedObjects->suitable(filename))
{
...
}
else
{
return read_file();
}
本篇文章将重点分析if函数的第二个分支,而将第一个分支暂时搁置,留待后续探讨。我们可以将第一个分支命名为“sharedObjects非空时的读操作”。以下是第二个分支的具体代码:
auto read_file = [&]() -> ref_ptr<Object> {
if (options && !options->readerWriters.empty())
{
for (auto& readerWriter : options->readerWriters)
{
auto object = readerWriter->read(filename, options);
if (object) return object;
}
return {};
}
auto ext = vsg::lowerCaseFileExtension(filename);
if (ext == ".vsga" || ext == ".vsgt" || ext == ".vsgb")
{
VSG rw;
return rw.read(filename, options);
}
else if (ext == ".spv")
{
spirv rw;
return rw.read(filename, options);
}
else if (glsl::extensionSupported(ext))
{
glsl rw;
return rw.read(filename, options);
}
else if (txt::extensionSupported(ext))
{
txt rw;
return rw.read(filename, options);
}
else
{
// no means of loading file
return {};
}
};
read_file函数采用lamda表达式,首先找到合适的读取器(readWriter)读取数据,当返回数据为空时,采用默认的读取器(VSG、spirv、glsl、txt)读取数据,否则最后返回为空。
2 插件机制
在 VulkanSceneGraph (VSG) 中,数据读取的插件机制是通过 vsg::ReaderWriter 类实现的。这个机制允许开发者扩展 VSG 的功能,通过重写read函数实现数据的读取。如上章描述所示,插件采用options传递和默认(VSG、spirv、glsl、txt)的方式读取数据。
通过给options对象添加读取osg数据的读写器,从而支持解析osgb格式的数据。
options->add(osg2vsg::OSG::create());
3 osg转vsg
osg2vsg::osg插件重写read函数,读取osgb格式的文件,本章主要关注倾斜模型对应的osgb格式(即仅包含三角网和纹理的模型)的读取。核心代码如下:
osgDB::ReaderWriter::ReadResult rr = osgDB::Registry::instance()->readObject(filename.string(), osg_options.get());
// if (!rr.success()) OSG_WARN << "Error reading file " << filename << ": " << rr.statusMessage() << std::endl;
if (!rr.validObject()) return {};
bool mapRGBtoRGBAHint = !options || options->mapRGBtoRGBAHint;
osg::ref_ptr<osg::Object> object = rr.takeObject();
if (osg::Node* osg_scene = object->asNode(); osg_scene != nullptr)
{
return osg2vsg::convert(*osg_scene, options);
}
上述代码利用osg引擎读取osgb文件,接着将osg节点转为vsg节点(osg2vsg::convert)。
下图为vsg中节点继承关系图。
接着具体分析函数osg2vsg::convert将osg::Node转换为vsg::Node的过程。
如上图所示,代码回到convert.cpp(osg2vsg v0.1.0)的127行到131行,127行创建了一个osg访问器osg2vsg::ConvertToVsg,代码130行将osg::Node转换为vsg::Node。和osg库类似,其主要重写apply(osg::PagedLOD&)和apply(osg:Geometry&geometry)函数将osg的节点转换为对应的vsg节点,osg2vsg::ConvertToVsg访问器中,将osg::PagedLOD转换为了vsg::PagedLOD,而将osg::Geometry转换为了vsg::Command,具体的转换过程放在悬疑列表中,至此将osg转vsg的过程讲解完毕!
文末:通过研读18-31行代码(参照vulkanscenegraph显示倾斜模型-优快云博客 中对研读代码的划分),本篇文章深入探讨数据读取机制的实现细节,包含数据读取、插件机制、osg转vsg三个部分,并在悬疑列表中留下两个问题,sharedObjects非空时的读操作与osg::PagedLOD转vsg::PagedLOD和osg::Geometry转vsg::Command的具体细节。
悬疑列表:sharedObjects非空时的读操作、osg::PagedLOD转vsg::PagedLOD和osg::Geometry转vsg::Command的具体细节。