在mapbox中实现3dtiles的加载和绘制(移动端)
一、解析3dtiles数据和提取gltf模型文件
可参照这篇文章:解析3dTiles数据和提取b3dm模型文件
1.解析tileset.json文件
可以参照 cesium.js 或者 cesium-native,这两个库都是借用第三方库rapidjson进行解析。
以下是cesium-native的示例代码:
namespace { std::optional<BoundingVolume> getBoundingVolumeProperty( const rapidjson::Value& tileJson, const std::string& key); TilesSelection::UriType getTileType(const std::string& uri){ std::string::size_type pos = uri.rfind('.'); std::string strExt = uri.substr(pos == std::string::npos ? uri.length() : pos+1); TilesSelection::UriType uriType = TilesSelection::UriType::Json; if (strExt.compare("json") == 0) { uriType = TilesSelection::UriType::Json; } else if (strExt.compare("b3dm") == 0){ uriType = TilesSelection::UriType::B3dm; } else { assert(0); Log::Error("Invalid ext type."); } return uriType; } } // namespace bool LoadTileFromJson::execute( Tile& tile, const rapidjson::Value& tileJson, const math::mat4& parentTransform, TileRefine parentRefine, Tileset* pTileset) { if (!tileJson.IsObject()) { assert(0); return false; } tile.setTileset(pTileset); const std::optional<math::mat4> tileTransform = JsonHelpers::getTransformProperty(tileJson, "transform"); math::mat4 transform = parentTransform * tileTransform.value_or(math::mat4(1.0)); tile.setTransform(transform); std::optional<double> geometricError = JsonHelpers::getScalarProperty(tileJson, "geometricError"); if (!geometricError) { geometricError = tile.getNonZeroGeometricError(); Log::Error( "Tile did not contain a geometricError. " "Using half of the parent tile's geometric error.\n"); } const auto contentIt = tileJson.FindMember("content"); const auto childrenIt = tileJson.FindMember("children"); if (contentIt != tileJson.MemberEnd() && contentIt->value.IsObject()) { auto uriIt = contentIt->value.FindMember("uri"); if (uriIt == contentIt->value.MemberEnd() || !uriIt->value.IsString()) { uriIt = contentIt->value.FindMember("url"); } tile.setEmptyContentState(false); if (uriIt != contentIt->value.MemberEnd() && uriIt->value.IsString()) { std::string strUri = uriIt->value.GetString(); size_t pos = 0; if (std::string::npos != (pos = strUri.find("./")) ){ strUri.erase(pos, 2); } TileID contentUri = tile.getbaseUrl() + "/" + strUri; tile.setTileID(contentUri); tile.setType(getTileType(contentUri)); tile.setState(Tile::LoadState::Unloaded); } std::optional<BoundingVolume> contentBoundingVolume = getBoundingVolumeProperty(contentIt->value, "boundingVolume"); if (contentBoundingVolume) { tile.setContentBoundingVolume(transformBoundingVolume(transform, contentBoundingVolume.value())); } } else { TileID emptyTile = "/emptyTile-" + Tool::toString(geometricError.value()); tile.setTileID(emptyTile); tile.setEmptyContentState(true); tile.setState(Tile::LoadState::Done); } std::optional<BoundingVolume> boundingVolume = getBoundingVolumeProperty(tileJson, "boundingVolume"); if (!boundingVolume) { assert(0); Log::Error("Tile did not contain a boundingVolume\n"); return false; } tile.setBoundingVolume( transformBoundingVolume(transform, boundingVolume.value())); const math::double3 scale = math::double3( length(transform[0]), length(transform[1]), length(transform[2])); const double maxScaleComponent = std::max(scale.x, std::max(scale.y, scale.z)); tile.setGeometricError(geometricError.value() * maxScaleComponent); std::optional<BoundingVolume> viewerRequestVolume = getBoundingVolumeProperty(tileJson, "viewerRequestVolume"); if (viewerRequestVolume) { tile.setViewerRequestVolume( transformBoundingVolume(transform, viewerRequestVolume.value())); } const auto refineIt = tileJson.FindMember("refine"); if (refineIt != tileJson.MemberEnd() && refineIt->value.IsString()) { std::string refine = refineIt->value.GetString(); if (refine == "REPLACE") { tile.setRefine(TileRefine::Replace); } else if (refine == "ADD") { tile.setRefine(TileRefine::Add); } else { std::string refineUpper = refine; std::transform( refineUpper.begin(), refineUpper.end(), refineUpper.begin(), [](unsigned char c) -> unsigned char { return static_cast<unsigned char>(std::toupper(c)); }); if (refineUpper == "REPLACE" || refineUpper == "ADD") { Log::Warning( "Tile refine value %s should be uppercase: %s\n", refine.c_str(), refineUpper.c_str()); tile.setRefine( refineUpper == "REPLACE" ? TileRefine::Replace : TileRefine::Add); } else { Log::Warning( "Tile contained an unknown refine value: %s\n", refine.c_str()); } } } else { tile.setRefine(parentRefine); } if (childrenIt->value.IsArray()) { const auto& childrenJson = childrenIt->value; std::vector<Tile*>& childTiles = tile.getChildren(); for (rapidjson::SizeType i = 0; i < childrenJson.Size(); ++i) { const auto& childJson = childrenJson[i]; Tile* child = new Tile(); child->setParent(&tile); child->setbaseUrl(tile.getbaseUrl()); child->_depth = tile._depth + 1; bool result = LoadTileFromJson::execute( *child, childJson, transform, tile.getRefine(), pTileset); if (result) { Tileset* pTileset = tile.getTileset(); ++pTileset->_statistics.numberOfTilesTotal; childTiles.push_back(child); } else { Utility::Tool::deletePtr(child); } } // end for } // end if return true; } namespace { std::optional<BoundingVolume> getBoundingVolumeProperty( const rapidjson::Value& tileJson, const std::string& key) { const auto bvIt = tileJson.FindMember(key.c_str()); if (bvIt == tileJson.MemberEnd() || !bvIt->value.IsObject()) { return std::nullopt; } const auto extensionsIt = bvIt->value.FindMember("extensions"); if (extensionsIt != bvIt->value.MemberEnd() && extensionsIt->value.IsObject()) { assert(0); return std::nullopt; } const auto boxIt = bvIt->value.FindMember("box"); if (boxIt != bvIt->value.MemberEnd() && boxIt->value.IsArray() && boxIt->value.Size() >= 12) { const auto& a = boxIt->value.GetArray(); for (rapidjson::SizeType i = 0; i < 12; ++i) { if (!a[i].IsNumber()) { return std::nullopt; } } return OrientedBoundingBox( math::double3(a[0].GetDouble(), a[1].GetDouble(), a[2].GetDouble()), math::mat3( a[3].GetDouble(), a[4].GetDouble(), a[5].GetDouble(), a[6].GetDouble(), a[7].GetDouble(), a[8].GetDouble(), a[9].GetDouble(), a[10].GetDouble(), a[11].GetDouble())); } const auto regionIt = bvIt->value.FindMember("region"); if (regionIt != bvIt->value.MemberEnd() && regionIt->value.IsArray() && regionIt->value.Size() >= 6) { const auto& a = regionIt->value; for (rapidjson::SizeType i = 0; i < 6; ++i) { if (!a[i].IsNumber()) { return std::nullopt; } } assert(0); return std::nullopt; } const auto sphereIt = bvIt->value.FindMember("sphere"); if (sphereIt != bvIt->value.MemberEnd() && sphereIt->value.IsArray() && sphereIt->value.Size() >= 4) { const auto& a = sphereIt->value; for (rapidjson::SizeType i = 0; i < 4; ++i) { if (!a[i].IsNumber()) { return std::nullopt; } } return BoundingSphere( math::double3(a[0].GetDouble(), a[1].GetDouble(), a[2].GetDouble()), a[3].GetDouble()); } return std::nullopt; } } // namespace
2.解析b3dm模型文件

如上图所示,一个b3dm模型中,存储了很多信息,我们的目标是提取出glTF二进制文件,所以需要出去表头字节数:
uint32_t byteOffset = 0; std::string err; if (!tilegltf::util::getGlbByteOffset(&err, (unsigned char*)data->data(), (unsigned int)data->size(), byteOffset)){ mbgl::Log::Error(Event::Style, "Failed to get glb byte offset: %s \n", err.c_str()); assert(0); return; } uint8_t* pData = (uint8_t*)data->data() + byteOffset; // delete b3dm head byte uint32_t dataLen = static_cast<unsigned int>(data->size()) - byteOffset; std::shared_ptr<const std::string> resData = std::make_shared<const std::string>(reinterpret_cast<const char *>(pData), dataLen);
bool getGlbByteOffset(std::string *err, unsigned char *bytes, unsigned int size, unsigned int& byteOffset) { if (size < 28) { if (err) { (*err) = "Too short data size for b3dm Binary."; } return false; } if (bytes[0] == 'b' && bytes[1] == '3' && bytes[2] == 'd' && bytes[3] == 'm') { // ok } else { if (err) { (*err) = "Invalid magic."; } return false; } unsigned int version; // 4 bytes unsigned int byteLength; // 4 bytes unsigned int featureTableJSONByteLength; // 4 bytes unsigned int featureTableBinaryByteLength;// 4 bytes; unsigned int batchTableJSONByteLength; // 4 bytes unsigned int batchTableBinaryByteLength; // 4 bytes; // @todo { Endian swap for big endian machine. } memcpy(&version, bytes + 4, 4); memcpy(&byteLength, bytes + 8, 4); memcpy(&featureTableJSONByteLength, bytes + 12, 4); memcpy(&featureTableBinaryByteLength, bytes + 16, 4); memcpy(&batchTableJSONByteLength, bytes + 20, 4); memcpy(&batchTableBinaryByteLength, bytes + 24, 4); if ((byteLength != size) || (byteLength < 1) ) { if (err) { (*err) = "Invalid b3dm binary."; } return false; } byteOffset = 28 + featureTableJSONByteLength + featureTableBinaryByteLength + batchTableJSONByteLength + batchTableBinaryByteLength; return true; }
二、绘制glTF模型
1.坐标转换
3dtiles的坐标为墨卡托坐标系,单位是米,需要转换为mapbox的坐标系,单位是像素:
//米转像素 const double WEBMERCATOR_EXTENT = 20037508.3427892; double worldSizeMeter = WEBMERCATOR_EXTENT * 2.0; // 米的世界范围 double scale = Projection::worldSize(state.getScale()) / worldSizeMeter; matrix::scale(_overlayParamter.modelMat, _overlayParamter.modelMat, scale , scale , 1.0); // 转换到mapbox坐标系(原点在左上角) mat4 matMapbox; matrix::identity(matMapbox); matMapbox[12] = WEBMERCATOR_EXTENT; matMapbox[13] = WEBMERCATOR_EXTENT; matrix::scale(matMapbox, matMapbox, 1.0, -1.0, 1.0); matrix::multiply(_overlayParamter.modelMat, _overlayParamter.modelMat, matMapbox); //将模型坐标转换为世界坐标(原点在center) matrix::multiply(_overlayParamter.modelMat, _overlayParamter.modelMat, _outMat);
2.mapbox添加GL_UNSIGNED_INT索引类型
mapbox中OpenGL的绘制,默认的顶点索引类型为UShort,不支持GL_UNSIGNED_INT的索引类型,需要添加GL_UNSIGNED_INT类型:
void Context::draw(const gfx::DrawMode& drawMode, std::size_t indexOffset, std::size_t indexLength, gfx::ElementIndexDataType indexDataType) { switch (drawMode.type) { case gfx::DrawModeType::Points: #if not MBGL_USE_GLES2 // In OpenGL ES 2, the point size is set in the vertex shader. pointSize = drawMode.size; #endif // MBGL_USE_GLES2 break; case gfx::DrawModeType::Lines: case gfx::DrawModeType::LineLoop: case gfx::DrawModeType::LineStrip: lineWidth = drawMode.size; break; default: break; } auto byteSize = [](platform::GLenum value)->std::size_t{ switch (value) { case GL_UNSIGNED_BYTE: return sizeof(uint8_t); case GL_UNSIGNED_SHORT: return sizeof(uint16_t); case GL_UNSIGNED_INT: return sizeof(uint32_t); } return sizeof(uint16_t); }; platform::GLenum dataType = Enum<gfx::ElementIndexDataType>::to(indexDataType); std::size_t bytesize = byteSize(dataType); MBGL_CHECK_ERROR(glDrawElements( Enum<gfx::DrawModeType>::to(drawMode.type), static_cast<GLsizei>(indexLength), dataType, reinterpret_cast<GLvoid*>(bytesize * indexOffset))); }
gfx::ElementIndexDataType mapElementIndexDataType(int32_t type) { switch (type) { case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: return gfx::ElementIndexDataType::UShort; case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: return gfx::ElementIndexDataType::UInt; } assert(false); return gfx::ElementIndexDataType::UShort; }
三、在mapbox中添加3dtiles的layer
1.在mapbox中添加3dtiles的layer

2.mapbox添加style layer的代码流程图

3.mapbox添加render layer代码流程图
`
4.mapbox请求数据代码流程图

四、异步加载
1.在class Resource中添加3dtiles的类型
class Resource {
public:
enum Kind : uint8_t {
Unknown = 0,
Style,
Source,
Tile,
Glyphs,
SpriteImage,
SpriteJSON,
Image,
FX3dTile
};
Resource Resource::FX3dTile(const std::string& url, LoadingMethod loadingMethod) { return Resource { Resource::Kind::FX3dTile, url, loadingMethod }; }
2.网络请求
template <typename T> TileLoader<T>::TileLoader(T& tile_, const FX3dTileID& id, const TileParameters& parameters) : tile(tile_), necessity(TileNecessity::Optional), resource(Resource::FX3dtilesTile(id)), // 网络 // resource(Resource::FX3dtilesTile("file://"+id)), // 本地 fileSource(parameters.fileSource){ assert(!request); if (fileSource->supportsCacheOnlyRequests()) { // When supported, the first request is always optional, even if the TileLoader // is marked as required. That way, we can let the first optional request continue // to load when the TileLoader is later changed from required to optional. If we // started out with a required request, we'd have to cancel everything, including the // initial optional part of the request. loadFromCache(); } else if (necessity == TileNecessity::Required) { // When the file source doesn't support cache-only requests, and we definiitely need this // data, we can start out with a network request immediately. loadFromNetwork(); } else { // When the FileSource doesn't support cache-only requests, we do nothing until the // data is definitely required. } }
Resource Resource::FX3dtilesTile(const std::string& url) { return Resource { Resource::Kind::FX3dTile, url }; }
五、几何误差
1.规范中的定义




2.计算SSE
sseDenominator = 2.0 * Math.tan(0.5 * frustum._fovy);
double ViewState::computeScreenSpaceError( double geometricError, double distance) const noexcept { // Avoid divide by zero when viewer is inside the tile distance = glm::max(distance, 1e-7); const double sseDenominator = this->_sseDenominator; return (geometricError * this->_viewportSize.y) / (distance * sseDenominator); }
关于tile到相机的distance的计算:
double OrientedBoundingBox::computeDistanceSquaredToPosition( const glm::dvec3& position) const noexcept { const glm::dvec3 offset = position - this->getCenter(); const glm::dmat3& halfAxes = this->getHalfAxes(); glm::dvec3 u = halfAxes[0]; glm::dvec3 v = halfAxes[1]; glm::dvec3 w = halfAxes[2]; const double uHalf = glm::length(u); const double vHalf = glm::length(v); const double wHalf = glm::length(w); u /= uHalf; v /= vHalf; w /= wHalf; const glm::dvec3 pPrime( glm::dot(offset, u), glm::dot(offset, v), glm::dot(offset, w)); double distanceSquared = 0.0; double d; if (pPrime.x < -uHalf) { d = pPrime.x + uHalf; distanceSquared += d * d; } else if (pPrime.x > uHalf) { d = pPrime.x - uHalf; distanceSquared += d * d; } if (pPrime.y < -vHalf) { d = pPrime.y + vHalf; distanceSquared += d * d; } else if (pPrime.y > vHalf) { d = pPrime.y - vHalf; distanceSquared += d * d; } if (pPrime.z < -wHalf) { d = pPrime.z + wHalf; distanceSquared += d * d; } else if (pPrime.z > wHalf) { d = pPrime.z - wHalf; distanceSquared += d * d; } return distanceSquared; }
3.判断是否满足sse
maximumScreenSpaceError = 16.
bool GltfManager::meetsSse( const mbgl::TransformState& state, const Tile& tile, const double distance) const { const double maxScaleComponent = util::getMaxScaleComponent(_fx3dtilesParamter.mercatorToMapboxMat); const double geometricError = tile.geometricError * maxScaleComponent; // Does this tile meet the screen-space error? const double sse = computeScreenSpaceError(state, geometricError, distance); return sse < maximumScreenSpaceError; }
六、3dtiles的tileset的遍历
此部分可以参考cesium-native.

相关函数的代码实现:
FX3DTilesetTraversal::FX3DTilesetTraversal(){} FX3DTilesetTraversal::~FX3DTilesetTraversal(){} bool FX3DTilesetTraversal::selectTiles( std::shared_ptr<tilegltf::Tileset> tileset, const mbgl::TransformState& state){ if (tileset == nullptr) { return false; } tileset->clearLoadQueue(); initializeParameter(state); Tile* pRootTile = tileset->pRootTile.get(); visitTileIfNeeded(*tileset, state, 0, false, *pRootTile); tileset->sortLoadQueue(); return true; } void FX3DTilesetTraversal::initializeParameter(const mbgl::TransformState& state){ state.getProjectionMatrix(_fx3dtilesParamter.projMat); state.getViewMatrix(_fx3dtilesParamter.viewMat); // tileModelmat math::mat4 modelMat; state.getModelMatrix(modelMat); //convert to mapbox(unit:pixel) math::mat4 mercatorToMapbox; util::getMercatorToMapboxMat(mercatorToMapbox, state); _fx3dtilesParamter.modelMat = _fx3dtilesParamter.modelMat * mercatorToMapbox; _fx3dtilesParamter.modelMat = modelMat * _fx3dtilesParamter.modelMat; // cameraPos math::mat4 invViewMat; invViewMat *= inverse(_fx3dtilesParamter.viewMat); math::double4 cameraPos{0.0, 0.0, 0.0, 1.0}; cameraPos = invViewMat * cameraPos; _fx3dtilesParamter.cameraPos = cameraPos.xyz; // camFrustum math::mat4 projMat; state.getProjectionMatrix(projMat); math::mat4 invProjMat = inverse(projMat); _fx3dtilesParamter.camFrustum = fromClipMatrix(projMat, invProjMat); } void FX3DTilesetTraversal::visitTileIfNeeded( tilegltf::Tileset& tileset, const mbgl::TransformState& state, uint32_t depth, bool ancestorMeetsSse, Tile& tile) { // whether we should visit this tile bool shouldVisit = true; if (!isVisibleFromCamera(tile.boundingVolume)) { // frustum culling is enabled so we shouldn't visit this off-screen tile shouldVisit = false; } if (!shouldVisit) { return; } return this->visitTile( tileset, state, depth, ancestorMeetsSse, tile); } void FX3DTilesetTraversal::visitTile( tilegltf::Tileset& tileset, const mbgl::TransformState& state, uint32_t depth, bool ancestorMeetsSse, Tile& tile) { const double distance = std::sqrt(max( computeDistanceSquaredToBoundingVolume(tile.boundingVolume), 0.0)); // If this is a leaf tile, render it if (isLeaf(tile)) { tileset.addTileToLoadQueue(tile, distance); return; } const bool unconditionallyRefine = tile.getUnconditionallyRefine(); const bool bMeetsSse = meetsSse(state, tile, distance); bool wantToRefine = unconditionallyRefine || (!bMeetsSse && !ancestorMeetsSse); if (!wantToRefine) { if (bMeetsSse && !ancestorMeetsSse) { tileset.addTileToLoadQueue(tile, distance); } return; } // Refine if (tile.hasContent()) { if (tile.content->uriType == Content::UriType::Json) { tile.content->tileset = tileset.tilesetRequestManager->getTileset(tile.id); if (tile.content->tileset != nullptr){ Tile* pRootTile = tile.content->tileset->pRootTile.get(); this->visitTileIfNeeded( tileset, state, 0, false, *pRootTile); } else{ tileset.addTilesetToLoadQueue(tile, distance); } } else{ // B3dm loadAndRenderAdditiveRefinedTile(tileset, tile, distance); } } visitVisibleChildren(tileset, state, depth, ancestorMeetsSse, tile); return; } bool FX3DTilesetTraversal::isLeaf(Tile& tile){ if (tile.content != nullptr && tile.content->uriType == Content::UriType::B3dm) { return tile.isLeaf(); } return false; } bool FX3DTilesetTraversal::visitVisibleChildren( tilegltf::Tileset& tileset, const mbgl::TransformState& state, uint32_t depth, bool ancestorMeetsSse, Tile& tile) { for (Tile* child : tile.children) { this->visitTileIfNeeded( tileset, state, depth + 1, ancestorMeetsSse, *child); } return true; } bool FX3DTilesetTraversal::isVisibleFromCamera(const BoundingVolume& boundingVolume) { math::mat4 viewModelMat = _fx3dtilesParamter.viewMat * _fx3dtilesParamter.modelMat; const BoundingVolume& viewBoudingBox1 = boundingVolume.transformBoundingVolume(_fx3dtilesParamter.modelMat); const BoundingVolume& viewBoudingBox = boundingVolume.transformBoundingVolume(viewModelMat); bool intersect = util::intersectsOBB(_fx3dtilesParamter.camFrustum, viewBoudingBox); return intersect; } double FX3DTilesetTraversal::computeDistanceSquaredToBoundingVolume( const BoundingVolume& boundingVolume) const { const BoundingVolume& boundingVolumeTemp = boundingVolume.transformBoundingVolume(_fx3dtilesParamter.modelMat); double distance = boundingVolumeTemp.computeDistanceSquaredToPosition(_fx3dtilesParamter.cameraPos); return distance; } bool FX3DTilesetTraversal::meetsSse( const mbgl::TransformState& state, const Tile& tile, const double distance) const { const double maxScaleComponent = util::getMaxScaleComponent(_fx3dtilesParamter.modelMat); const double geometricError = tile.geometricError * maxScaleComponent; const double sse = computeScreenSpaceError(state, geometricError, distance); return sse < maximumScreenSpaceError; } double FX3DTilesetTraversal::computeScreenSpaceError( const mbgl::TransformState& state, const double geometricError, double distance) const { // Avoid divide by zero when viewer is inside the tile distance = max(distance, 1e-7); const double sseDenominator = 2.0 * std::tan(0.5 * state.getFieldOfView()); Size size = state.getSize(); double viewportHeight = size.height; return (geometricError * viewportHeight) / (distance * sseDenominator); } const Tile* FX3DTilesetTraversal::getRenderedTile(const FX3dTileID& tileID) const { auto it = renderedTiles.find(tileID); return it != renderedTiles.end() ? &it->second.get() : nullptr; } bool FX3DTilesetTraversal::loadAndRenderAdditiveRefinedTile(tilegltf::Tileset& tileset, Tile& tile, const double distance) { // If this tile uses additive refinement, we need to render this tile in // addition to its children. if (tile.refine == Tile::Refine::Add) { tileset.addTileToLoadQueue(tile, distance); } return true; }
七、缓存处理
缓存处理使用比较简单的方式,仍是按照tile的数量来确定缓存的大小。
当一个tile为请求下来是,先查看其children是否都已经加载,若全都已经加载,则使用children来cover此tile,否则使用parent.
void FX3dtilesPyramid::update(const std::vector<Immutable<style::LayerProperties>>& layers, const bool needsRendering, const bool needsRelayout, const TileParameters& parameters, const std::string& filePath, std::function<std::unique_ptr<Tile> (const FX3dTileID&, const math::mat4&)> createTile) { // If we need a relayout, abandon any cached tiles; they're now stale. if (needsRelayout) { cache.clear(); } // If we're not going to render anything, move our existing tiles into // the cache (if they're not stale) or abandon them, and return. if (!needsRendering) { if (!needsRelayout) { for (auto& entry : tiles) { // These tiles are invisible, we set optional necessity // for them and thus suppress network requests on // tiles expiration (see `OnlineFileRequest`). entry.second->setNecessity(TileNecessity::Optional); cache.add(entry.first, std::move(entry.second)); } } tiles.clear(); renderedTiles.clear(); return; } // const std::string& fileName = filePath + "/tileset.json"; // 本地 const std::string& fileName = filePath; // 网络 if(tileset == nullptr) { if (tilesetRequester == nullptr){ math::mat4 parentTransform; tilesetRequester = new lkgl::TilesetRequester(fileName, parentTransform, parameters.fileSource); tilesetRequester->load(); } if (!tilesetRequester->loaded){ return; } tileset = tilesetRequester->getTileset(); tileset->setFileSource(parameters.fileSource); } tilegltf::FX3DTilesetTraversal traversal; traversal.selectTiles(tileset, parameters.transformState); tileset->requestTilesets(); // Stores a list of all the tiles that we're definitely going to retain. There are two // kinds of tiles we need: the ideal tiles determined by the tile cover. They may not yet be in // use because they're still loading. In addition to that, we also need to retain all tiles that // we're actively using, e.g. as a replacement for tile that aren't loaded yet. std::set<FX3dTileID> retain; auto retainTileFn = [&](Tile& tile, TileNecessity necessity) -> void { if (retain.emplace(tile.fx3dtileId).second) { tile.setNecessity(necessity); } if (needsRelayout) { tile.setLayers(layers); } }; auto getTileFn = [&](const FX3dTileID& tileID) -> Tile* { auto it = tiles.find(tileID); return it == tiles.end() ? nullptr : it->second.get(); }; auto createTileFn = [&](const FX3dTileID& tileID, const math::mat4& matrix) -> Tile* { std::unique_ptr<Tile> tile = cache.pop(tileID); if (!tile) { tile = createTile(tileID, matrix); if (tile) { tile->setObserver(observer); tile->setLayers(layers); } } if (!tile) { return nullptr; } return tiles.emplace(tileID, std::move(tile)).first->second.get(); }; auto previouslyRenderedTiles = std::move(renderedTiles); auto renderTileFn = [&](const FX3dTileID& tileID, Tile& tile) { addRenderTile(tileID, tile); previouslyRenderedTiles.erase(tileID); // Still rendering this tile, no need for special fading logic. tile.markRenderedIdeal(); }; std::set<const tilegltf::Tile*> parent3dTiles; auto parent3dTileFn = [&](const tilegltf::Tile* tile) -> void { parent3dTiles.emplace(tile); }; renderedTiles.clear(); const std::vector<tilegltf::LoadRecord>& idealTiles = tileset->getTileLoadQueue(); algorithm::updateRenderablesFor3dtiles(getTileFn, createTileFn, retainTileFn, renderTileFn, parent3dTileFn, idealTiles); for (auto previouslyRenderedTile : previouslyRenderedTiles) { Tile& tile = previouslyRenderedTile.second; tile.markRenderedPreviously(); if (tile.holdForFade()) { // Since it was rendered in the last frame, we know we have it // Don't mark the tile "Required" to avoid triggering a new network request retainTileFn(tile, TileNecessity::Optional); addRenderTile(previouslyRenderedTile.first, tile); } } cache.setSize(40); // Remove stale tiles. This goes through the (sorted!) tiles map and retain set in lockstep // and removes items from tiles that don't have the corresponding key in the retain set. { auto tilesIt = tiles.begin(); auto retainIt = retain.begin(); while (tilesIt != tiles.end()) { if (retainIt == retain.end() || tilesIt->first < *retainIt) { if (!needsRelayout) { tilesIt->second->setNecessity(TileNecessity::Optional); cache.add(tilesIt->first, std::move(tilesIt->second)); } tiles.erase(tilesIt++); } else { if (!(*retainIt < tilesIt->first)) { ++tilesIt; } ++retainIt; } } } // Remove parentRenderTile's children for (auto& parent : parent3dTiles){ removeRenderedTile(parent); } // Initialize renderable tiles and update the contained layer render data. for (auto& entry : renderedTiles) { Tile& tile = entry.second; assert(tile.isRenderable()); tile.usedByRenderedLayers = false; const bool holdForFade = tile.holdForFade(); for (const auto& layerProperties : layers) { const auto* typeInfo = layerProperties->baseImpl->getTypeInfo(); if (holdForFade && typeInfo->fadingTiles == LayerTypeInfo::FadingTiles::NotRequired) { continue; } tile.usedByRenderedLayers |= tile.layerPropertiesUpdated(layerProperties); } } }
template <typename GetTileFn, typename CreateTileFn, typename RetainTileFn, typename RenderTileFn, typename Parent3dTileFn> void updateRenderablesFor3dtiles(GetTileFn getTile, CreateTileFn createTile, RetainTileFn retainTile, RenderTileFn renderTile, Parent3dTileFn parent3dTileFn, const std::vector<tilegltf::LoadRecord>& idealTiles) { std::unordered_set<FX3dTileID> checked; bool covered = true; for (auto& it : idealTiles) { auto tile = getTile(it.pTile->id); if (!tile) { tile = createTile(it.pTile->id, it.pTile->transform); if(tile == nullptr) { continue; } } // if (source has the tile and bucket is loaded) { if (tile->isRenderable()) { retainTile(*tile, TileNecessity::Required); renderTile(it.pTile->id, *tile); } else { // We are now attempting to load child and parent tiles. bool parentHasTriedOptional = tile->hasTriedCache(); bool parentIsLoaded = tile->isLoaded(); // The tile isn't loaded yet, but retain it anyway because it's an ideal tile. retainTile(*tile, TileNecessity::Required); covered = true; // Check all child tiles. for (const auto& childTile : it.pTile->children) { tile = getTile(childTile->id); if (tile && tile->isRenderable()) { // do nothing } else { // At least one child tile doesn't exist, so we are going to look for // parents as well. covered = false; break; } } if (covered) { for (const auto& childTile : it.pTile->children) { tile = getTile(childTile->id); retainTile(*tile, TileNecessity::Required); renderTile(childTile->id, *tile); } } else { const tilegltf::Tile* parent3dTile = it.pTile->getParent(); // We couldn't find child tiles that entirely cover the ideal tile. while (parent3dTile != nullptr) { const auto parentDataTileID = parent3dTile->id; if (checked.find(parentDataTileID) != checked.end()) { // Break parent tile ascent, this route has been checked by another child // tile before. break; } else { checked.emplace(parentDataTileID); } tile = getTile(parentDataTileID); if (tile) { if (!parentIsLoaded) { // We haven't completed loading the child, so we only do an optional // (cache) request in an attempt to quickly load data that we can show. retainTile(*tile, TileNecessity::Optional); } else { // Now that we've checked the child and know for sure that we can't load // it, we attempt to load the parent from the network. retainTile(*tile, TileNecessity::Required); } // Save the current values, since they're the parent of the next iteration // of the parent tile ascent loop. parentHasTriedOptional = tile->hasTriedCache(); parentIsLoaded = tile->isLoaded(); if (tile->isRenderable()) { renderTile(parentDataTileID, *tile); parent3dTileFn(parent3dTile); // Break parent tile ascent, since we found one. break; } } parent3dTile = parent3dTile->getParent(); } } // end if !cover } // end if } // end for } // end func


八、相机裁剪
3dtiles一般使用的是OBB包围盒,一般的做法是检测OBB是否都在视锥的六个平面之外。
遍历视椎体的6个面判断是否相交:
CullingResult OrientedBoundingBox::intersectPlane(const Plane& plane) const noexcept { const glm::dvec3 normal = plane.getNormal(); const glm::dmat3& halfAxes = this->getHalfAxes(); const glm::dvec3& xAxisDirectionAndHalfLength = halfAxes[0]; const glm::dvec3& yAxisDirectionAndHalfLength = halfAxes[1]; const glm::dvec3& zAxisDirectionAndHalfLength = halfAxes[2]; // plane is used as if it is its normal; the first three components are // assumed to be normalized const double radEffective = glm::abs( normal.x * xAxisDirectionAndHalfLength.x + normal.y * xAxisDirectionAndHalfLength.y + normal.z * xAxisDirectionAndHalfLength.z) + glm::abs( normal.x * yAxisDirectionAndHalfLength.x + normal.y * yAxisDirectionAndHalfLength.y + normal.z * yAxisDirectionAndHalfLength.z) + glm::abs( normal.x * zAxisDirectionAndHalfLength.x + normal.y * zAxisDirectionAndHalfLength.y + normal.z * zAxisDirectionAndHalfLength.z); const double distanceToPlane = ::glm::dot(normal, this->getCenter()) + plane.getDistance(); if (distanceToPlane <= -radEffective) { // The entire box is on the negative side of the plane normal return CullingResult::Outside; } if (distanceToPlane >= radEffective) { // The entire box is on the positive side of the plane normal return CullingResult::Inside; } return CullingResult::Intersecting; }
九、渲染
1.bucket
将模型数据上传至GPU
void FX3dtilesBucket::upload(gfx::UploadPass &uploadPass) { if (uploaded) { return; } generateGPUBuffer(uploadPass); generateTextures(uploadPass); while (mCurrentMesh<(int16_t)model->meshes.size()) { tinygltf::Mesh& mesh = model->meshes[mCurrentMesh]; while (mCurrentPrimitive<(int16_t)mesh.primitives.size()) { uploadStep(uploadPass,mCurrentMesh,mCurrentPrimitive); mCurrentPrimitive++; } mCurrentMesh++; } uploaded = true; }
void FX3dtilesBucket::uploadStep(gfx::UploadPass&uploadPass, int meshesIndex, int primitiveIndex) { const tinygltf::Mesh& mesh = model->meshes[meshesIndex]; const tinygltf::Primitive& primitive = mesh.primitives[primitiveIndex]; FX3dtilesSegment *seg = new FX3dtilesSegment(); tinygltf::Material& material = model->materials[primitive.material]; if(material.alphaMode=="OPAQUE") { Segments.emplace_back(seg); }else { translucentSegments.emplace_back(seg); } const tinygltf::Accessor& indexAccessor = model->accessors[primitive.indices]; assert(indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT); seg->indexOffset = 0; seg->indexLength = indexAccessor.count; seg->indexBufferView = indexAccessor.bufferView; std::vector<optional<gfx::AttributeBinding>> vecAttrBinding; vecAttrBinding.resize(3); for (auto attrIt : primitive.attributes) { auto& accessor = model->accessors[attrIt.second]; auto& bufferView = model->bufferViews[accessor.bufferView]; assert(accessor.bufferView < (int32_t)_gpuBufferViews.size()); auto& gpuBufferView = _gpuBufferViews[accessor.bufferView]; assert(gpuBufferView->vertexBuffer.has_value()); gfx::AttributeBinding attrBinding; attrBinding.attribute.dataType = mapAttribuiteDataType(accessor.type); attrBinding.attribute.offset = accessor.byteOffset; attrBinding.vertexStride = bufferView.byteStride; attrBinding.vertexOffset = 0; attrBinding.vertexBufferResource = &gpuBufferView->vertexBuffer.value().getResource(); int32_t index = getAttributeIndex(attrIt.first, material.pbrMetallicRoughness.baseColorTexture.texCoord); if (index != -1) { vecAttrBinding[index] = attrBinding; } } seg->attributeBindings = {vecAttrBinding[0], vecAttrBinding[2]}; // 上传图片到GPU tinygltf::Parameter ¶m = material.values.find("baseColorTexture")->second; if (param.json_double_value.size() == 0) { tinygltf::Parameter &colorParam = material.values.find("baseColorFactor")->second; seg->bHasTexture = false; if (material.name == "materialLightPole") { seg->albedo = Point3D<float>(0.028426, 0.048172, 0.132868); seg->metallic = 0.285714; seg->roughness = 0.428571; } else if (material.name == "SilveryMetal") { seg->albedo = Point3D<float>(0.168269, 0.198069, 0.246201); seg->metallic = 0.571429; seg->roughness = 0.285714; } else if (material.name == "WhitePaint") { seg->albedo = Point3D<float>(0.991102, 0.991102, 0.991102); seg->metallic = 0.571429; seg->roughness = 0.285714; } else if (material.name == "RedPaint") { seg->albedo = Point3D<float>(0.665387, 0.009134, 0.008023); seg->metallic = 0.571429; seg->roughness = 0.285714; } else if (material.name == "BluePaint") { seg->albedo = Point3D<float>(0.006049, 0.061246, 0.520996); seg->metallic = 0.428571; seg->roughness = 0.285714; } else if (material.name == "BlackPaint") { seg->albedo = Point3D<float>(0.0, 0.0, 0.0); seg->metallic = 0.571429; seg->roughness = 0.285714; } else if (material.name == "CementBlock") { seg->albedo = Point3D<float>(0.095307, 0.093059, 0.095307); seg->metallic = 0.428571; seg->roughness = 0.714286; } else if (material.name == "BrassMetal") { seg->albedo = Point3D<float>(0.226966, 0.127438, 0.036889); seg->metallic = 0.142857; seg->roughness = 0.714286; } else if (material.name == "GreenMetal") { seg->albedo = Point3D<float>(0.03125, 0.05, 0.005); seg->metallic = 0.571429; seg->roughness = 0.285714; } else if (material.name == "ParkingGround") { seg->albedo = Point3D<float>(0.285871, 0.36, 0.0954); seg->metallic = 0.0; seg->roughness = 0.857143; } else if (material.name == "materialRoadSurface") { // 路面颜色值 // seg->albedo = Point3D<float>(0.124772, 0.141263, 0.171441); seg->albedo = Point3D<float>(0.177888, 0.174647, 0.226966);//路面基础颜色 seg->metallic = 0.428571; seg->roughness = 0.714286; } else if (material.name == "materialYellowLine") { seg->albedo = Point3D<float>(0.135633, 0.082283, 0.001214); seg->metallic = 0.571429; seg->roughness = 0.285714; } else if (material.name == "materialWhiteLine") { seg->albedo = Point3D<float>(0.758376, 0.758376, 0.758376); seg->metallic = 0.285714; seg->roughness = 0.428571; } else { if (colorParam.number_array.size() > 0) { seg->albedo = Point3D<float>(colorParam.number_array[0], colorParam.number_array[1], colorParam.number_array[2]); } else { seg->albedo = Point3D<float>(1.0, 0.0, 0.0); } seg->metallic = 0.142857; seg->roughness = 0.857143; } seg->metallic = 0.0; seg->roughness = 0.9; } else { int index = param.TextureIndex(); const tinygltf::Image& image = model->images[index]; if (image.bufferView != -1) { seg->mbTexture = mTextures[index]; // 可能多个primitive共用一个image seg->loadedTexture= true; } else if (!image.uri.empty()) { //从纹理池取 seg->textureId = NormalizePath(image.uri); // TODO: 临时 // seg->textureId = NormalizePath("funan/"+image.uri); seg->mExtTexture = mTextureManager->getTexture(seg->textureId); if (!seg->mExtTexture) { seg->mExtTexture = mTextureManager->mSpaceTexture; seg->loadedTexture= false; } else { seg->loadedTexture= true; } } seg->bHasTexture = true; seg->metallic = 1.0;//有材质时默认金属度和粗糙度 seg->roughness = 1.0; } }
2.render
void FX3dtilesRender::render(PaintParameters& parameters) { assert(render3dTiles); if (parameters.pass != RenderPass::Opaque) { return; } auto& context = static_cast<gl::Context&>(parameters.context); const auto& evaluated = static_cast<const FX3dtilesLayerProperties&>(*evaluatedProperties).evaluated; FX3dtilesProgram::Binders paintAttributeData { evaluated, 0 }; const auto depthMode = parameters.depthModeFor3D(); const TransformState& state = parameters.state; math::mat4 projMat; state.getProjectionMatrix(projMat); math::mat4 viewMat; state.getViewMatrix(viewMat); math::mat4 invViewMat; invViewMat *= viewMat; math::mat4 modelMat; state.getModelMatrix(modelMat); math::mat4 mercatorToMapboxMat; tilegltf::util::getMercatorToMapboxMat(mercatorToMapboxMat, state); if (!render3dTiles->empty()) { context.setDepthMode(depthMode); context.setStencilMode(gfx::StencilMode::disabled()); context.setColorMode(parameters.colorModeForRenderPass()); context.setCullFaceMode(gfx::CullFaceMode::backCCW()); for (const Render3dTile& tile : *render3dTiles) { auto* bucket_ = tile.getBucket(*baseImpl); auto& bucket = static_cast<FX3dtilesBucket&>(*bucket_); bucket.preRender(); auto& fx3dtile_ = tile.GetDataTile(); auto& fx3dtile = static_cast<const FX3dtilesTile&>(fx3dtile_); math::mat4 tileModelMat; tileModelMat *= mercatorToMapboxMat; tileModelMat *= fx3dtile.getMatrix(); tileModelMat = modelMat * tileModelMat; for (FX3dtilesSegment* seg : bucket.Segments) { assert(seg->indexBufferView < (int32_t)bucket._gpuBufferViews.size()); auto& gpuBufferView = bucket._gpuBufferViews[seg->indexBufferView]; assert(gpuBufferView->indexBuffer.has_value()); drawTile(evaluated, paintAttributeData, parameters, seg, gpuBufferView->indexBuffer.value(), projMat, viewMat, tileModelMat); } } } }
3.shader
const char* fx3dtiles_vs() { return R"(precision highp float; attribute vec3 a_pos3f; attribute vec2 a_texture2f; uniform mat4 u_proj_mat; uniform mat4 u_view_mat; uniform mat4 u_model_mat; varying vec2 v_tex_coord; void main() { v_tex_coord = a_texture2f; vec4 loc_pos = u_model_mat * vec4(a_pos3f, 1.0); gl_Position = u_proj_mat * u_view_mat * loc_pos; } )"; } const char* fx3dtiles_fs() { return R"(precision highp float; varying vec2 v_tex_coord; uniform sampler2D u_color_map; uniform float u_is_has_texture; void main() { vec4 color = vec4(1.0, 0.0, 0.0, 1.0); if (u_is_has_texture > -1.0) { color = texture2D(u_color_map, v_tex_coord); } else { color = vec4(1.0, 0.0, 0.0, 1.0); } gl_FragColor = color; } )"; }