Cesium原生提供了箭头线的材质,效果如图1所示
图1 Cesium箭头线效果
由于遇到了改造箭头线材质的需求,因此花了一些时间对箭头线材质的原理做了一些了解。在此总结和分享,不足之处也请大家指正。
Cesium在内部做了哪些操作,使得我们通常认为的如图2所示的一根带宽度线变为如图3所示的箭头线的呢?
图2 普通带宽度的线
图3 箭头线轮廓
Cesium箭头线材质的着色器代码位于Source/Shaders/Materials/PolylineArrowMaterial.glsl下。
这里对关键代码进行分析:
(1)计算非箭头部分占比
#ifdef GL_OES_standard_derivatives
float base = 1.0 - abs(fwidth(st.s)) * 10.0 * czm_pixelRatio;
#else
float base = 0.975; // 2.5% of the line will be the arrow head
#endif
表4 计算非箭头部分的占比
表4代码中定义的变量base用于指定非箭头部分的占比,默认开启了GL_OES_standard_derivatives扩展,执行第一个分支。
fwidth(v) = abs( ddx(v) )+ abs(ddy(v))
ddx(v) = 该像素点右边的v值 - 该像素点的v值
ddy(v) = 该像素点下面的v值 - 该像素点的v值
fwidth(st.s) 表示当前像素与相邻两个像素纹理坐标s分量的差值,可以看出箭头部分的占比取决于纹理坐标值的差异大小。
(2)定义箭头斜边
vec2 center = vec2(1.0, 0.5);
float ptOnUpperLine = getPointOnLine(vec2(base, 1.0), center, st.s);
float ptOnLowerLine = getPointOnLine(vec2(base, 0.0), center, st.s);
表5-1 计算箭头斜边-定义关键点
float getPointOnLine(vec2 p0, vec2 p1, float x)
{
float slope = (p0.y - p1.y) / (p0.x - p1.x);
return slope * (x - p0.x) + p0.y;
}
表5-2 计算箭头斜边-其他片元拉到斜边上
表5-1和5-2的代码定义了箭头的斜边。给定三个点center、(base, 1.0)、(base, 0.0),假设当前正在处理的片元坐标为(st.s, st.t)。
图5-3 箭头斜边片元处理
float slope = (p0.y - p1.y) / (p0.x - p1.x); 这句代码计算的是一个正切值tanθ;
getPointOnLine函数的返回值 slope * (x - p0.x) + p0.y 就是把当前处理的片元拉到斜边上,而且还是拉到对称的斜边上。
(3)计算非箭头区域
Cesium用glsl的step函数做了一个小技巧,把箭头区域和两侧一部分区域剔除,留下s值为1的区域作为非箭头区域,其余区域s均为0。
float halfWidth = 0.15;
float s = step(0.5 - halfWidth, st.t);
s *= 1.0 - step(0.5 + halfWidth, st.t);
s *= 1.0 - step(base, st.s);
表6 非箭头充区域定义
图7 非箭头区域
(4)计算箭头区域
和步骤(3)用的技巧类似,留下t值为1的区域作为箭头区域,其余区域t值均为0。
float t = step(base, materialInput.st.s);
t *= 1.0 - step(ptOnUpperLine, st.t);
t *= step(ptOnLowerLine, st.t);
表8 箭头充区域定义
图10 箭头区域
(5)填充箭头线
这两句是最后决定箭头线着色的地方。使用glsl的mix函数,s + t 为1的区域,也就是图7和图10中s和t为1的区域被赋予颜色,其他区域都为透明。
vec4 outsideColor = vec4(0.0);
vec4 currentColor = mix(outsideColor, color, clamp(s + t, 0.0, 1.0));
图11 箭头线最终填充颜色
除此之外,箭头线还用到了抗锯齿的技术。本文主要总结箭头线的形状是如何形成的,抗锯齿相关原理后续有机会再总结和分享。