Cesium随笔:视锥绘制(上)
第一篇CesiumJS技术日记,不管技术难不难,认真归纳是个好习惯。
0.前言
最近在研究视域分析,思路:使用ShadowMap.js接口开放的阴影绘制功能,指定点光源的相关参数。然而在调试的过程中发现并没有那么简单,生成ShadowMap对象时会抛出无法在参数中找到Context对象的异常,这个Context对象在Api文档中并没有提及,这个问题在https://github.com/AnalyticalGraphicsInc/cesium/issues/4010中提及,Cozzi说不支持,索性视域分析就放一放,先将视锥绘制出来。这里只说关键的地方,primitive的加载、鼠标事件等过程就不多说了。

1.思路
方案1:确定摄像机位置->确定摄像机方向->生成新相机实体->使用lookAt(target,offset)设置相机位置姿态->获取相机视锥->绘制视锥,该方案应该是标准方法,但是调试过程中绘制的视锥总是出现在地心处,多次尝试无果(和下漫画如出一辙):

切忌钻牛角尖,尝试方案2:
方案2:确定相机位置->确定摄像机方向->生成视锥几何体->计算视锥俯仰角度->计算视锥航向角度->绘制视锥。方案2相对于前者的难点在于相机姿态的计算,原本使用myCamera.lookAt(target,offset)可以直接计算出视锥的姿态,无奈该方法绘制出错,现在只能自己算了(笔者没有找到根据两点计算姿态的函数)。
2.视锥几何体
视锥的形态和初始姿态(yaw->0,pitch->0,roll->0)如下图。

视锥构造参数如下:

frustum:视锥本体,下面详细说明,
origin:轴心,就是那个尖的位置,
orientation:决定相机看的方向,后面再详细说明,
vertexFormat:该参数和视锥绘制没有关系,保持默认即可。
frustum参数说明
我们选用PerspectiveFrustum,如下是官方doc的构造函数参数。

near和far参数作用如下图,其中虚线是辅助线,不属于绘制的视锥几何体。

fov:查看的视场角,绕z轴旋转,以弧度方式输入。
aspectRatio:垂直边和水平边的比值,如下图的aspectRatio为n。

far,near,fov,aspectRatio四个参数确定了便可以确定视锥的形状。
3.视锥姿态计算
在程序启动之后的第一件事是通过鼠标选取两个点:起点和瞄准的点,通过这两个点来计算出视锥的姿态,如下图from点便是视锥的origin,这里我们只关心航向(yaw/heading)和俯仰(pitch)两个参数,不关心横滚(roll),我们希望计算出来的航向角和俯仰角通过如下图的这种方式来表示相机瞄准的方位,下图中粉色线是视锥所表示相机所发射的射线。即航向角和俯仰角为0时射线指向x轴方向,航向角增加射线绕着z轴顺时针转动,俯仰角增加射线绕着y轴转动。
计算出的姿态换算成四元素(Quaternion),用作视锥的orientation参数。

不过视锥初始化状态发射的射线(表示的相机所发射)并不是指向x轴方向,而是指向z方向,且航向沿着x的反方向,将视锥航向角-180°,俯仰角-90°,便是视锥理想的初始姿态。

姿态角度计算
Cesium中的经纬度坐标系和笛卡尔坐标系该选谁呢?上文中提到的姿态所参考的xyz轴是以椭球地表为参考的(x指向正东,y指向正北,z垂直于地面向上),因此选择经纬度更为直观,但是只限于短距离的计算。
俯仰角计算,先计算绝对值:
let dist = getSpaceDistance(firstPos, secondPos);//获取空间距离
let h = getVerticalDistance(firstPos, secondPos);//获取垂直高度差
let pitchRadian = Math.asin(Math.abs(h / dist));
再根据h的正负判断俯仰角的正负值(仰为正,俯为负)。
航向角计算,先计算第一象限的值:

let xDist = getNorthwardDistance(firstPos,secondPos);
let yDist = getEastwardDistance(firstPos, secondPos);
let headingRadian = Math.asin(Math.abs(xDist) / Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2)));
现在判断To点的位置,结合之前视锥的初始姿态,计算出最终的航向弧度。
case 区域1:
headingRadian = (180.0+ headingRadian * Cesium.Math.DEGREES_PER_RADIAN) / Cesium.Math.DEGREES_PER_RADIAN;
case 区域2:
headingRadian = (360.0- headingRadian * Cesium.Math.DEGREES_PER_RADIAN) / Cesium.Math.DEGREES_PER_RADIAN;
case 区域3:无需操作
case 区域4:
headingRadian = (180.0- headingRadian * Cesium.Math.DEGREES_PER_RADIAN) / Cesium.Math.DEGREES_PER_RADIAN;

当然,这种方法仅仅适用于短距离和靠近赤道的地区,对于笔者的用途足够了。
4.总结
其实并不推荐这样的方法来计算视锥(不过好歹笔者调试了半天才试出来,也记录一下),后面将叙述推荐的方法。