UE4 C++ 基于Cesium Unreal 插件的二次开发问题记录

1. 如需继承Cesium Unreal的类进行重写,需要再xxxxx.Build.cs中增加C++17的支持,添加如下一行代码即可。

CppStandard = CppStandardVersion.Cpp17;

2. Cesium Unreal 相机控制类为:AGlobeAwareDefaultPawn。

 

3. Cesium Unreal 1.0 源码整体调用流程,仅供参考。

    第三方依赖库需提前自行编译。

一、 Cesium UE4 源码记录
Cesuim 球构建流程
First step:
a. Cesium3DTileset类中BeginPlay或OnConstruction函数调用LoadTileset函数。
b. 612行根据URL是否为空,new Tileset类的对象。
c. Tileset类构造函数中开启子线程,连接网络根据Url获取json格式的数据。
d. 数据获取成功后调用Tileset类的回调函数_handleAssetResponse,解析json数据,最后调用使用解析的路径和内容调用_loadTilesetJson函数。
e. 根据解析的url获取网络json格式数据,调用回调函数_handleTilesetResponse。
f. _handleTilesetResponse函数中解析获取到的json数据,创建RootTile,调用_createTerrainTile函数,返回RootTile和生成结果。
g. 在_createTerrainTile函数中,解析json数据,获取version内容,获取数据的边界区域(bounds),获取坐标系(projection),根据不同的坐标系计算边界矩形区域(目前只支持:EPSG:4326、EPSG:3857),
    将地理坐标区域转为投影坐标区域,设置tile自身的context内容(方便子tiles请求数据),根据坐标系类型(4326-》2个,3857-》1个)创建固定数量的子tile对象,并设置子tile的context、parent、tileID、边界矩形区域(使用函数QuadtreeTilingScheme::tileToRectangle() 计算划分的子Tile边界区域)等数据。
h. 接f,_handleTilesetResponse函数返回后,会转d步骤的_loadTilesetJson函数中,走thenInMainThread分支,保存RootTile,添加Context内容,设置是否支持栅格数据。
i. 至此,此路流程结束。

Second Step:
a. Cesium3DTileset类构造函数中创建RootComponent,类型为UCesium3DTilesetRoot。
b. Cesium3DTileset类中Tick函数中,检查RootComponent的是否发生过改变,更新新的坐标转换内容。更新Georeference对象。
c. 设置_pTileset对象的参数内容。获取相机对象,获取当前的视口区域范围。
d. 调用_pTileset对象的updateView函数,更新当前视口数据。
f. 在updateView函数中,调用_visitTileIfNeeded函数计算RootTile及其子Tile是否可见和需要加载。
g. _visitTileIfNeeded函数中,先更新RootTile的内容,标记RootTile已经被运算过,计算是否在可视范围内,计算高度是否能穿透雾气等,最后调用_visitTile函数。
h. _visitTile函数中,由于根节点,会一直执行到_loadAndRenderAdditiveRefinedTile函数,在该函数中,将RootTile添加到tilesToRenderThisFrame队列中,然后调用addTileToLoadQueue
i. 在addTileToLoadQueue中,判断该节点没有加载,计算出节点权重,添加到_loadQueueMedium(中级)队列中。
j. 接h,完成_loadAndRenderAdditiveRefinedTile函数调用后,调用_visitVisibleChildrenNearToFar函数,在该函数中遍历RootTile的子节点,并逐个子节点调用_visitTileIfNeeded(g步骤)函数。
k. 子节点执行_visitTileIfNeeded函数时,更新子节点数据,标记子节点被访问过,获取子节点可视区域,调用isVisibleFromCamera计算子节点是否可见,i:不可见,则调用markTileAndChildrenNonRendered函数将盖子节点及其所有孩子节点标记为无需渲染,
    然后判断是否需要预加载,需要则将该子节点添加到_loadQueueLow(低级)队列中。ii: 可见,则调用_visitTile(步骤h)函数,该函数中,判断子节点是否为叶子节点,是叶子节点,则调用_renderLeaf函数将该子节点添加到_loadQueueMedium(中级)队列中。
    不是叶子节点,则继续往下逐个判断是否需要加载,并添加到对应队列列表中。
l. 接j,执行函数_kickDescendantsAndRenderTile,进行节点和子节点调教筛选及处理等操作,完成后退出_visitTile函数。
m. 接g,继续退出_visitTileIfNeeded函数。
n. 接f,调用_unloadCachedTiles卸载超出缓存数量的Tile,调用_processLoadQueue开启进程,使用processQueue函数加载_loadQueueHigh(高级)、_loadQueueMedium(中级)、_loadQueueLow(低级)三个队列的Tiles的数据。
o. 此处继续单独分析_loadQueueMedium(中级)中的一个Tile加载过程,在该函数中使用步骤 i 中addTileToLoadQueue函数计算出的权重对当前队列进行排序,在不超出最大加载线程数量情况下,然后逐个调用Tile自身的loadContent函数加载数据。
p. 在Tile自身的loadContent函数中,先判断当前加载状态是否为Unloaded,然后设置当前状态为ContentLoading,通过Tileset的requestTileContent函数创建http链接获取数据,使用返回的链接对象设置请求成功的Lambdas回调函数。
q. 在Lambdas回调函数中,对获取到的数据先进行正确性效验,然后取出data数据,调用FileContentFactory类的createContent函数解析数据内容。
r. 在TileContentFactory类的createContent函数中,先获取json数据类型,如果为“b3dm”,则调用Batched3DModelContent类的load函数,走构建模型分支,如果为“application/vnd.quantized-mesh”,则调用QuantizedMeshContent类的load函数,走构建地网分支。

构建地网分支:
s. 在QuantizedMeshContent的load函数中,先将数据通过parseQuantizedMesh函数解析为QuantizedMeshView结构体,包含构网所用的顶点坐标,顶点数量,三角形数量,三角网索引,法线数据,东南西北四个方向的顶点坐标等数据。
t. 继续通过tileBoundingVolume解析出当前该tile的覆盖范围BoundingRegion,继续获取网格的顶点数据等,继续解析不包含四个边框的构网数据,继续解析不包含四个边框的法线数据,继续解析边框位置的点数据。
u. 继续获取pResult的model成员变量,给model的meshes添加一个mesh,并获取新添加的mesh的引用,然后给mesh的primitives添加一个MeshPrimitive,并获取其引用primitive,再给model的buffers添加一个Buffer,并获取其引用positionBuffer,
    继续给model的bufferViews添加一个BufferView,并获取其引用positionBufferView,继续给model的accessors添加一个Accessor,并获取其引用positionAccessor,然后通过获取到的各种类型的引用给其设置需要的数据。
    最后完成数据设置后,返回pResult。
v. 接r,完成createContent的调用,继续返回pResult。
w. 接q,完成TileContentFactory类的createContent函数调用后,然后执行Tile类的generateTextureCoordinates函数,然后继续调用UnrealResourcePreparer类的prepareInLoadThread函数,此函数为虚函数,重写自父类IPrepareRendererResources
x. 在UnrealResourcePreparer类的prepareInLoadThread函数中,会调用UCesiumGltfComponent类的CreateOffGameThread函数。
y. 在UCesiumGltfComponent类的CreateOffGameThread函数中,会继续调用loadModelAnyThreadPart函数。
z. 在loadModelAnyThreadPart函数中,判断出model的nodes成员变量有值,会继续调用loadNode函数。
aa. 在loadNode函数中,计算出当前tile的nodeTransform值,获取mode的meshes中node.mesh索引的mesh, 继续调用loadMesh。
ab. 在loadMesh函数中,逐个遍历mesh的primitives成员变量,并调用loadPrimitive函数。
ac. 在loadPrimitive函数中,根据条件判断,继续调用了同名的重载函数loadPrimitive。
ad. 在重载的loadPrimitive函数中,创建FStaticMeshRenderData对象(用于构网),获取LODResources索引为的对象的引用,
ae. 通过计算转换将步骤u获取的各项数据分别填充到FStaticMeshRenderData对象的变量中,便于后续构建FStaticMeshComponent。
af. 接ad,完成loadPrimitive函数的调用后,将构建的FStaticMeshRenderData对象赋值给LoadModelResult的RenderData成员变量,并放入Vector容器中,退出函数调用。
ag. 接ab, 在loadMesh中,完成所有primitives的循环构网调用,并将结果存储std::vector<LoadModelResult>类的result中,然后完成该函数调用。
ah. 接aa, 完成loadMesh构网后,继续遍历node的children,并逐个递归调用loadNode,同样将构网结果存入std::vector<LoadModelResult>类的result中。
ai. 接z,完成loadModelAnyThreadPart的调用,并返回保存构网结果的result。
aj. 接y,完成UCesiumGltfComponent类的CreateOffGameThread函数调用,继续返回result。
ak. 接x,完成UnrealResourcePreparer类的prepareInLoadThread函数调用,并将结果转换为void*,并返回。
al. 接w,完成虚函数prepareInLoadThread的调用后,将结果保存到LoadResult结构体的pRendererResources成员变量中,然后返回该结构体。
am. 接q,完成请求成功的lambdas函数回调后,继续程序轮转,进入主线程调用thenInMainThread的设置的回调lambda函数,在该函数中,将上述步骤返回的LoadResult结构体的值存储到Tile自身的成员变量中。
后续流程和下一个分支合并一起转ba步骤。


构建OSM模型分支:
s. 在Batched3DModelContent类的load函数中,先获取B3dmHeader结构体类型的包头数据,然后根据包头解析出数据的起始结束位置,然后使用数据(数据格式为gltf二进制格式)调用GltfContent类的load函数。
t. 在GltfContent类的load函数中,调用CesiumGltf类的readModel函数,并传入gltf格式的二进制数据。
u. 在CesiumGltf类的readModel函数中,根据数据的GlbHeader类型包头中magic变量的值是否为0x46546C67来判断是否是否为二进制格式gltf数据。然后调用Reader.cpp文件中readBinaryModel函数。
v. 在readBinaryModel函数中,对数据包长度、格式、版本号等进行校验,然后获取json格式的数据体内容,调用该文件中的readJsonModel函数。
w. 在readJsonModel函数中,使用rapidjson::Reader、ModelJsonHandler、FinalJsonHandler、Dispatcher等类,将数据解析为ModelReaderResult格式并保存到result的Model成员变量中。然后返回result变量。
x. 接v,对返回的result结果进行校验,然后返回result。
y. 接u,完成readBinaryModel或readJsonModel的数据解析后,进入postprocess函数中。
z. 在postprocess函数中,调用decodeDataUrls函数解析model中images数据,调用decodeDraco函数解析model中的meshes数据。
aa. 接y,完成postprocess函数调用后,继续返回result。
ab. 接t,获取到解析完成的loadedModel数据后,将loadedModel的model对象转移给TileContentLoadResult类的model成员变量,然后返回pResult。
ac. 接s, 获取到TileContentLoadResult类的结果后,转到parseFeatureTableJsonData函数的调用,获取扩展数据“RTC_CENTER”的值,保存到model的extras变量中,key为“RTC_CENTER”。
ad. 接ac,完成parseFeatureTableJsonData函数调用后,继续返回pResult。
ae. 接r,完后load函数的调用,继续返回pResult。
af. 接q,完成TileContentFactory类的createContent函数调用后,调用Tile类的generateTextureCoordinates函数生产材质贴图用的纹理坐标。
ag. 然后继续调用UnrealResourcePreparer类重写的虚函数prepareInLoadThread,该函数中调用UCesiumGltfComponent类的CreateOffGameThread函数。在CreateOffGameThread函数中会继续调用loadModelAnyThreadPart函数。
ah. 在loadModelAnyThreadPart函数中,首先计算出坐标转换系数(此处可能会用到ac步骤的“RTC_CENTER”值),然后依次循环调用loadNode函数。
ai. 在loadNode函数中,先计算出当前节点的坐标转换系统,然后调用loadMesh函数加载自身的纹理数据,遍历子节点递归调用loadNode加载子节点的内容, 完成调用后,返回std::vector<LoadModelResult>类型的result变量。
aj. 接ah,完成loadModelAnyThreadPart函数调用,用HalfConstructedReal类型变量的成员变量loadModelResult接收返回值,然后完成CreateOffGameThread的调用,返回结果。
ak. 接ag,完成prepareInLoadThread函数调用,返回HalfConstructed类型的变量。
al. 接af,完成prepareInLoadThread的调用后,将其返回值结果保存到LoadResult结构体的pRendererResources成员变量中,然后返回该结构体。
am. 接q,完成请求成功的lambdas函数回调后,继续程序轮转,进入主线程调用thenInMainThread的设置的回调lambda函数,在该函数中,将上述步骤返回的LoadResult结构体的值存储到Tile自身的成员变量中。
后续流程和下一个分支合并一起转ba步骤。


上面两个分支在这里合二为一:
ba. ACesium3DTileset的Tick函数持续调用后,会持续进入Tileset的updateView函数,并一直持续执行上述步骤,此处接g步骤,此处会调用所有Tile的Update函数。
bb. 在Tile的Update函数中,当判断当前状态为ContentLoaded后(之前的重复调用已经加载成功),调用UnrealResourcePreparer类的虚函数prepareInMainThread。
bc. 在UnrealResourcePreparer类的虚函数prepareInMainThread中,会调用UCesiumGltfComponent类的CreateOnGameThread函数。
bd. 在UCesiumGltfComponent类的CreateOnGameThread函数中,会获取Tile保存的std::vector<LoadModelResult>数据,然后创建UCesiumGltfComponent做父组件,逐个遍历std::vector<LoadModelResult>数据,并使用每个数据调用loadModelGameThreadPart函数。
be. 在loadModelGameThreadPart函数中,先以上一步创建的UCesiumGltfComponent为父组件,创建UCesiumGltfPrimitiveComponent组件(该组件继承自UStaticMeshComponent)的对象pMesh,然后设置该组件的偏移量,标志位等参数。
      接下来继续创建UStaticMesh的对象pStaticMesh,将其设置到组件pMesh中,然后将传入的LoadModelResult中的RenderData赋值给pStaticMesh的RenderData成员变量,完成构网功能。继续使用LoadModelResult的pMaterial成员变量,创建一个材质实例pMaterial。
      然后给创建的材质实例设置相关参数。再然后将pMaterial添加到PStaticMesh的材质中。后续继续设置pStaticMesh的相关设置,设置完成后,pMesh将自身添加到上一步创建的UCesiumGltfComponent组件中,并完成注册组件。至此,loadModelGameThreadPart函数完成调用。
bf. 接bd,完成所有LoadModelResult的构建Mesh后,将父组件UCesiumGltfComponent作为返回值返回。
bg. 接bc, 完成UnrealResourcePreparer类的prepareInMainThread的调用,继续返回UCesiumGltfComponent的对象。
bh. 接bb,使用Tile的成员变量_pRendererResources保存UnrealResourcePreparer类的prepareInMainThread函数返回,该函数继续往下执行,更新_boundingVolume的值,设置当前状态为Done。完成Tile类的Update函数调用。
bi,接ba,回到ACesium3DTileset的Tick函数中,完成Tileset类的updateView调用后,用ViewUpdateResult结构体result对象接收返回值,继续往下执行,判断了返回值得数据是否正常,遍历result的tilesToNoLongerRenderThisFrame,逐个设置Tile的可见性为false。
      继续遍历result的tilesToRenderThisFrame,逐个获取Tile的_pRendererResources(就是bd步骤创建的UCesiumGltfComponent对象),将该对象添加到ACesium3DTileset的RootComponent中,然后设置可见性为true。至此完成Tick的调用。

关键信息:
1. 构网关键类 UCesiumGltfComponent
2. 构网关键函数 static void loadModelGameThreadPart( UCesiumGltfComponent* pGltf, LoadModelResult& loadResult, const glm::dmat4x4& cesiumToUnrealTransform);
3. 通用基本材质GltfMaterialWithOverlays,未加载内容时材质GltfMaterialOpacityMask
4. UCesiumGltfPrimitiveComponent 继承自 UStaticMeshComponent,是构建Cesium球的基础网格
5. IPrepareRendererResources构网关键纯虚类,他的子类有UnrealResourcePreparer
6. TileContentLoader 将加载的数据转为Model关键纯虚类,他的子类有:QuantizedMeshContent地网,Batched3DModelContent模型

  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值