osgb顶层合并实现

一.需求背景

         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"}
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

nab111

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

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

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

打赏作者

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

抵扣说明:

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

余额充值