Cesium着色器的创意和方法(五——Polyline)
不接受反驳,线段在三维里面的渲染比多边形的渲染更复杂。因为线是没有宽度的,并且还需要时时刻刻向着你。
首先看下Cesium的线段的一些效果。这条线段非常宽(20个像素),有两个需要留意观察的。一是线段并非实时面对我,到了某个角度会重新调整朝向。二是远处跟地形还有点叠加冲突。
20241107_224526
1、线段是矩形
这个道理很多人都理解的。当前WebGL并不能凭空添加未知的点,因此需要用线段去模拟带宽度的线。
当然也不能直接是固定的矩形,因为这条线段还需要随着视野远近不断的改变,使之保持视觉上的一致。因此,线段的VertexArray(GeometryAttributes)跟普通的矩形不一样,它存储了position、prevPosition、nextPosition、expandAndWidth等数据。其中prevPosition和nextPosition是为了动态计算而进行存储的。
线段在渲染的顶点数是 顶点数*4-4。即一条线段(2个顶点)共计4个渲染顶点,两条线段(3个顶点)共计8个渲染顶点。亦即不考虑节点处的共享顶点情况。
2、expandAndWidth
它的存储数量跟渲染顶点数量一致,记录每个渲染顶点的朝向,或者说序号。对每个顶点它存储两个值,第一个值交替记录-1或者1,第二个值存储正负线宽(±lineWidth)。具体情况看下图。
3、 getPolylineWindowCoordinates
顾名思义,这个函数是要计算线段在屏幕上的位置。在这个函数中,Cesium会对每段线段进行空间判断,看看是否会被截断或丢弃。
这部分代码比较晦涩难懂。需要首先理解的是点投影到平面的公式,即
vec4 positionEC = czm_translateRelativeToEye(position.xyz, new vec3(0f));
vec4 positionPEC = czm_modelViewRelativeToEye * positionEC;
vec4 positionWC = czm_eyeToWindowCoordinates(positionPEC);
这里第一行是把全球空间笛卡尔位置转换到摄像头的相对位置,第二行是调整旋转角度,是一个View矩阵,第三行是把坐标转换为屏幕坐标,其中positionWC.xy是屏幕像素的位置。
其中EC为EyeCoordinate的简写,PEC是ProjectEyeCoordinate的简写,WC是windowCoordinate的简写。
Cesium判断线段被截断或丢弃的代码是clipLineSegmentToNearPlane函数,这个函数首先判断线段是否在近裁剪平面之前,方法是判断 czm_currentFrustum.x + positionPEC .z是否大于0。如果小于0,则表示不会被近裁剪面裁剪,反之会被截断或“灯下黑”。
czm_currentFrustum.x是近裁剪面的距离,为正值,Cesium在距离地面很远的情况,这个值并不是0.1,而可能是几万的值。
positionPEC .z是已经跟摄像头观察矩阵一致的坐标,因为Z轴代表正视方向。
4、线段宽度
第二部分谈论了线段宽度的传递,第三部分讨论了线段顶点在屏幕的坐标。设置线段宽度并不是像素级±宽度,而是需要利用线段前后节点的方向。因此计算方法可以简单的理解为
(positionWCPre-positionWCNext)的垂直向量*线段宽度
当然具体过程又复杂了,包括首尾节点的处理情况、垂直向量为Zero、顶点重叠等异常情况。
5、测试工具
glsl代码理解是很费神的一件事情,因为缺乏调试器。不过我为此做了一个小工具,采用C#来动态调试glsl。在开源项目里面,后面单独写篇文章介绍该程序的使用。ReprojectWebMercator: 仿照Cesium 的ReprojectWebMercator,可视化测试。