一.需求背景
osgb数据是C端常用的三维模型渲染展示格式。随着B端三维模型渲染的需求场景越来越多,3dtiles格式使用的越来越多。目前常见的三维软件生成的osgb数据,需要转换为3dtiles格式的数据。在osgb数据生产过程中中,受限于硬件及效率的影响,目前在生成osgb数据时,会进行大量分块。当osgb分块数据量过大时,转换成3dtiles时,每一个分块数据就是一个顶层节点。而cesium渲染3dtiles时,顶层节点是被一次性加载的,随着顶层节点的增多,cesium渲染三维模型会变得越来越卡。因此,顶层节点合并这一需求应运而生。
二.常见的osgb格式
目前市面上主流的osgb分块规则有两种。其一是以CC为代表的按格网的方式进行的分块;其二是以大疆智图为代表的类是于空间二叉树的分块。熟悉这两种分块方式,为我们后续顶层合并策略非常重要。

cc分块策略

大疆智图分块策略
三.顶层合并实现
osgb顶层合并思路:
3.1确定根节点与子节点的关系
以cc生成的osgb格式为例:最初的分块名称为Tile_-005_-009。其中005代表第5行,009代表第9列。那么在进行根节点节点生成的时候,可以按四叉树的规则,对最初的顶层分块进行合并。如果最初的分块个数为1000,那么经过第一次合并后的顶层快就是250。大疆智图生成的osgb数据也按类似的思路进行根节点的确定。
3.2根节点生成
通过3.1确定根节点对应的子节点后,接下来需要做的就是顶点合并、纹理压缩、以及纹理坐标重映射。以cc生成的osgb格式为例:根节点由四个子节点合并组成,我们需要将四个子节点的纹理重新合并为一个新的纹理,且需要对纹理进行压缩。还有纹理坐标的合并,以及顶点的简化。
3.3重复3.1和3.2,不停的进行根节点的生成,直到根节点个数为1。
上述只是osgb顶层合并的思路。具体的代码实现过程还需要根据程序的实现做一些优化和加速。
//顶点合并,纹理重映射
void GeometryMerger::mergeVertexData(osg::Geometry* merged) {
osg::Vec3Array* mergedVerts = new osg::Vec3Array;
osg::Vec2Array* mergedUVs = new osg::Vec2Array;
for (auto geom : _geometries) {
// 1. 合并顶点坐标
auto verts = static_cast<osg::Vec3Array*>(geom->getVertexArray());
mergedVerts->insert(mergedVerts->end(), verts->begin(), verts->end());
// 2. 调整UV坐标(若启用纹理合并)
if (_mergeTexture) {
auto uvRect = _uvMap[geom]; // 获取当前几何体在图集中的UV区域:ml-citation{ref="1" data="citationList"}
osg::Vec2Array* uvs = static_cast<osg::Vec2Array*>(geom->getTexCoordArray(0));
for (auto& uv : *uvs) {
float mappedU, mappedV;
if (uvRect.rotated) {
// 旋转插入时需交换UV轴并调整方向:ml-citation{ref="2,5" data="citationList"}
mappedU = uv.y() * uvRect.h + uvRect.x; // 原V轴映射到图集U轴:ml-citation{ref="5" data="citationList"}
mappedV = (1 - uv.x()) * uvRect.w + uvRect.y; // 原U轴反向映射到图集V轴:ml-citation{ref="2" data="citationList"}
}
else {
// 常规UV映射(无旋转):ml-citation{ref="1,3" data="citationList"}
mappedU = uv.x() * uvRect.w + uvRect.x; // 横向映射到图集子区域:ml-citation{ref="3" data="citationList"}
mappedV = uv.y() * uvRect.h + uvRect.y; // 纵向映射到图集子区域:ml-citation{ref="3" data="citationList"}
}
mappedU = mappedU / _atlasSizeX;
mappedV = mappedV / _atlasSizeY;
mergedUVs->push_back(osg::Vec2(mappedU, mappedV));
}
}
}
merged->setVertexArray(mergedVerts);
merged->setTexCoordArray(0, mergedUVs);
}
//索引合并
void GeometryMerger::mergeIndices(osg::Geometry* merged) {
osg::ref_ptr<osg::DrawElementsUInt> indices = new osg::DrawElementsUInt(GL_TRIANGLES);
unsigned vertexOffset = 0;
for (auto geom : _geometries) {
osg::PrimitiveSet* ps = geom->getPrimitiveSet(0);
if (osg::DrawElements* de = dynamic_cast<osg::DrawElements*>(ps)) {
for (unsigned i = 0; i < de->getNumIndices(); ++i) {
indices->push_back(de->getElement(i) + vertexOffset);
}
}
else if (osg::DrawArrays* da = dynamic_cast<osg::DrawArrays*>(ps)) {
convertDrawArrays(da, indices, vertexOffset);
}
vertexOffset += geom->getVertexArray()->getNumElements();
}
merged->addPrimitiveSet(indices);
}
void GeometryMerger::convertDrawArrays(osg::DrawArrays* da, osg::ref_ptr<osg::DrawElementsUInt> indices, unsigned offset) {
const int mode = da->getMode();
const int first = da->getFirst();
const int count = da->getCount();
switch (mode) {
case GL_TRIANGLES:
for (int i = 0; i < count; ++i)
indices->push_back(first + i + offset);
break;
case GL_TRIANGLE_STRIP:
for (int i = 2; i < count; ++i) {
indices->push_back(first + i - 2 + offset);
indices->push_back(first + i - 1 + offset);
indices->push_back(first + i + offset);
}
break;
case GL_QUADS:
for (int i = 0; i < count; i += 4) {
indices->push_back(first + i + offset);
indices->push_back(first + i + 1 + offset);
indices->push_back(first + i + 2 + offset);
indices->push_back(first + i + offset);
indices->push_back(first + i + 2 + offset);
indices->push_back(first + i + 3 + offset);
}
break;
}
}
//纹理生成
void GeometryMerger::createTextureAtlas() {
TexturePacker packer(_atlasSizeX, _atlasSizeY, 0);
//计算纹理排列数据
int maxX = 0;
int maxY = 0;
for (auto& [tex, geom] : _textures) {
TexturePacker::UVRect rect;
if (packer.insert(tex->getImage(), rect)) {
_uvMap[geom] = rect;
int tempX = rect.x + rect.w;
if (tempX > maxX)
{
maxX = tempX;
}
int tempY = rect.y + rect.h;
if (tempY > maxY)
{
maxY = tempY;
}
}
}
_atlasSizeX = maxX;
_atlasSizeY = maxY;
//打包所有纹理
_atlasTexture->setTextureSize(_atlasSizeX, _atlasSizeY);
osg::ref_ptr<osg::Image> atlasImage = new osg::Image;
atlasImage->allocateImage(_atlasSizeX, _atlasSizeY, 1, GL_RGB, GL_UNSIGNED_BYTE);
for (auto& [tex, geom] : _textures) {
TexturePacker::UVRect rect = _uvMap[geom];
copyTextureToAtlas(atlasImage.get(), tex->getImage(), rect);
}
//if (atlasImage.valid())
//{
// const std::string outputPath = "D:/atlasImageall.jpg"; // 替换为实际路径
// bool success = osgDB::writeImageFile(*atlasImage, outputPath);
//}
// 设置图集纹理
_atlasTexture->setImage(atlasImage);
_atlasTexture->setUnRefImageDataAfterApply(true);
}
//纹理合并
void GeometryMerger::mergeMaterialStates(osg::StateSet* mergedState) {
// 1. 绑定合并后的纹理图集到通道0
mergedState->setTextureAttributeAndModes(0, _atlasTexture.get(), osg::StateAttribute::ON); // 激活纹理单元:ml-citation{ref="1" data="citationList"}
// 2. 合并漫反射颜色(取所有材质最大值)
osg::Vec4 mergedDiffuse(0, 0, 0, 0);
for (auto geom : _geometries) {
if (auto ss = geom->getStateSet()) {
if (auto mat = dynamic_cast<osg::Material*>(ss->getAttribute(osg::StateAttribute::MATERIAL))) {
mergedDiffuse = componentMax(mergedDiffuse, mat->getDiffuse(osg::Material::FRONT));
}
}
}
auto mergedMat = new osg::Material;
mergedMat->setDiffuse(osg::Material::FRONT, mergedDiffuse);
mergedState->setAttribute(mergedMat); // 设置合并后材质属性:ml-citation{ref="1" data="citationList"}
// 3. 启用透明度混合(如果存在透明材质)
if (mergedDiffuse.a() < 1.0f) {
mergedState->setMode(GL_BLEND, osg::StateAttribute::ON); // 开启透明混合:ml-citation{ref="2,3" data="citationList"}
mergedState->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); // 透明渲染排序优化:ml-citation{ref="2" data="citationList"}
}
}

2018

被折叠的 条评论
为什么被折叠?



