前言
基础知识
在研究清楚如何绘制地图的线面体之后,接下来需要确定需要展示的地图区域了。
地图可以看成是一个巨型的开放世界游戏场景,因此为了便于数据存储和查找,传统的做法是将地球根据墨卡托投影转换为平面地图,再将地图分级分块进行切片,通过索引获取到对应的数据。
以OSM的地图为例,导出数据是以当前视口的大小,查询对应级别的切片得到的。Google的卫星图、地形图等也都是按照分级分块的规则进行管理。
基于视口展示
传统的地图展示方式,展示区域的确定通常是与视口绑定的,即地图切片只加载摄像机视锥体与地图所在平面相交的部分,并在摄像机移动时动态进行切片的更替。
这种方式对于查看全世界全量地图数据的场景非常合适,但对于希望使用游戏引擎构建一个更精细的世界来说,有一些不足:
- 视锥体动态计算切片的前提是,一定要保证其与地图所在平面一定有四个交点,因此摄像机的FOV(竖直方向的张开角度)不能太大,否则当摄像机俯仰角变化时,视锥体的上下两个面可能与地图所在平面平行,从而导致无法计算切片。
- 在平行之前,同样也会因为角度问题,导致计算得到的切片数量过大,无法进行加载;或因为设置了一些切片数量的限制,导致看到的世界有所缺失。
因此视椎体动态计算的方式,通常会固定一个较小的FOV,并且限制俯仰角。同时因为性能的限制,对于大俯仰角的情况,通过一些手段进行切片的数量优化。以腾讯的JS API GL为例,为了减少大俯仰角造成切片数量过大带来的性能瓶颈,采用雾化的方式将较远处的场景进行剔除,使得可以无缝衔接查看整个世界。
UE4和Unity都有能够获得视椎体的接口。以UE4为例,ULocalPlayer中存储了Viewport相关的信息,根据矩阵变换的信息可以得到存储视椎体信息的FConvexVolume。
ULocalPlayer* LocalPlayer = GetWorld()->GetFirstPlayerController()->GetLocalPlayer();
FSceneViewProjectionData ProjectionData;
LocalPlayer->GetProjectionData(LocalPlayer->ViewportClient->Viewport, eSSP_FULL, ProjectionData);
FMatrix ViewProjectionMatrix = ProjectionData.ComputeViewProjectionMatrix();
FConvexVolume ViewFrustum;
GetViewFrustumBounds(ViewFrustum, ViewProjectionMatrix, true);
FConvexVolume的Planes数组中依次存储了左右上下四个平面的方程FPlane,比较特殊的是FPlane是以Xx+Yy+Zz=W的形式存储了(X,Y,Z,W)值。同时,地图所在平面也可以使用一个方程表示,因此,视锥体与地图的一个交点就是三个平面的相交点。(以左上交点为例,将视椎体的左、上平面方程与地图所在平面方程联立,即可得到交点)
其中的联立求交,可以使用矩阵运算快速求得:
若联立有解,则矩阵可逆,那么行列式不为0可以作为判断有解的快速验证方式。当确定有解后,则可使用逆矩阵快速求解:
基于行政区划展示
基于视口展示方案理论上完全可行,但对于有高性能显卡支撑的游戏引擎来说远远不够:
- 地图至少要像GTA那样,目之所及都有元素,不能将远裁剪面如此提前。
- 摄像机需要自由度,可以随心所欲的进行移动。
因此,比较直接的想法是,如果想展示一个城市,那就一次性渲染出城市的所有数据。
城市的数据可以借助于现有的服务获取,以腾讯位置服务的WebService API为例,可以通过行政区划服务获取到对应的行政区划点串信息,依托于地图数据的切片存储形式,因此只需要确定这个行政区划点串覆盖的切片集合就可以了。
// 行政区划线点串以连续的经纬度进行表示
"polygon": [ [116.809403,39.61482,116.790175,39.610555,116.780286,39.593196....],]
根据基础知识所说,每一个切片都是一个小正方形,而行政区划点串信息代表的是一个大多边形,因此转化为使用小正方形切片去近似一个多边形的问题。
是不是听起来十分像光栅化。
因此顺着这个思路,借助于光栅化的方式求切片集合:
1、光栅化的基本单位是三角形,因此对于行政区划的多边形,先调用三角剖分算法分解为三角形的集合。
2、对于一个三角形,最经典的方式就是拆为两个更容易绘制的三角形,一个底边平行,一个顶边平行,再使用水平扫线法求得所有的切片。
具体算法可以参考这篇TriangleRasterization的文章,其中还有Bresenham算法和重心坐标算法等其他光栅化的算法。
获取到所有切片数据后,就可以进行行政区划的展示了。
基于位置的动态展示方法
借助于光栅化算法可以得到切片集合进行渲染展示,但基于行政区划的方式展示也有弊端,即CPU/GPU资源有限,对于几千平方公里的城市可能无法粗暴的直接支持。
和开放大世界游戏一样,比较合理的方式就是随着当前位置动态载入/载出场景,使得感官上构建出一个无缝衔接的大世界。
以UE4为例,Epic提供了World Composition这个利器去支持游戏开发者制作开发大世界游戏,开发者可以方便的并行编辑每个子关卡。运行时根据游戏角色所在的位置,可以异步加载/卸载子关卡,达到无缝衔接的效果。
而对于不适合使用World Composition的场景,可以退一步使用Level Streaming去进行手动管理,初步的使用方法,可以参考之前的文章,对于Level Streaming的一些初步的学习。
基于位置的动态展示方法需要做更多的额外工作去达到完美的沉浸效果,目前这部分依旧还在摸索中,希望有朝一日可以完全解决。这篇文章权当是抛砖引玉,希望给大家带来一些思考。
作者:程序员阿Tu
链接:https://zhuanlan.zhihu.com/p/353203619
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。