<think>
我们将在Cesium中通过自定义材质着色器为线条内部添加方向箭头。核心思路是在片段着色器中根据纹理坐标计算箭头图案,并叠加到原始线条颜色上。
### 实现原理
1. **坐标系转换**:在片段着色器中获取线条的纹理坐标`st`(沿线条方向为s轴,垂直方向为t轴)
2. **箭头定位**:沿s轴等间距重复箭头图案
3. **形状生成**:利用距离场技术生成三角形箭头
### 关键步骤
#### 1. 定义箭头形状函数
在片段着色器中创建函数,判断当前点是否在箭头区域内:
```glsl
// 输入:当前点st坐标,箭头宽度width,箭头高度height
bool isInArrow(vec2 st, float width, float height){
// 箭头底部从0.5-width/2到0.5+width/2
// 箭头顶部尖点位于(0.5, 1.0)
// 计算到箭头左边界的有符号距离
float left = st.x - (0.5 - width/2.0);
// 计算到箭头右边界的有符号距离
float right = (0.5 + width/2.0) - st.x;
// 计算到箭头底部的距离
float bottom = st.y;
// 箭头顶部斜边:y方向从0到1,x方向从两侧向中心收缩
float slope = (1.0 - st.y) / height; // height控制箭头尖度
float leftEdge = st.x - (0.5 - width/2.0 + slope * width/2.0);
float rightEdge = (0.5 + width/2.0 - slope * width/2.0) - st.y;
// 判断点是否在箭头区域内
return (left >= 0.0 && right >= 0.0 && bottom >= 0.0 &&
leftEdge >= 0.0 && rightEdge >= 0.0);
}
```
#### 2. 在片段着色器中集成
```glsl
czm_material czm_getMaterial(czm_materialInput materialInput){
czm_material material = czm_getDefaultMaterial(materialInput);
// 获取纹理坐标(沿线条方向为st.s,垂直方向为st.t)
vec2 st = materialInput.st;
// 原始线条颜色(示例为蓝色)
vec4 lineColor = vec4(0.0, 0.5, 1.0, 1.0);
// 箭头参数
float arrowWidth = 0.3; // 箭头底部宽度(归一化值)
float arrowHeight = 0.3; // 箭头高度(归一化值)
float spacing = 0.5; // 箭头间距(占整个线条的比例)
// 计算重复的箭头位置(沿s轴重复)
float arrowPos = fract(st.s / spacing);
// 创建局部坐标(将箭头定位在当前段)
vec2 arrowSt = vec2(arrowPos, st.t);
// 检查当前点是否在箭头内
if (isInArrow(arrowSt, arrowWidth, arrowHeight)) {
material.diffuse = vec3(1.0, 0.0, 0.0); // 箭头颜色(红色)
material.alpha = 1.0;
} else {
material.diffuse = lineColor.rgb;
material.alpha = lineColor.a;
}
return material;
}
```
#### 3. 优化箭头显示(抗锯齿)
```glsl
// 改进的isInArrow函数(添加距离场和抗锯齿)
float arrowSDF(vec2 st, float width, float height){
// 计算到箭头各边界的距离
vec2 center = vec2(0.5, 0.0);
vec2 tip = vec2(0.5, 1.0);
// 计算箭头底部两个角点
vec2 bottomLeft = vec2(0.5 - width/2.0, 0.0);
vec2 bottomRight = vec2(0.5 + width/2.0, 0.0);
// 计算到两侧斜边的距离
float d1 = distanceToLine(st, bottomLeft, tip);
float d2 = distanceToLine(st, bottomRight, tip);
// 计算到底部的距离
float d3 = st.y; // 到y=0的距离
// 取最小距离作为SDF
return min(d3, min(d1, d2));
}
// 在czm_getMaterial中使用SDF
float sdf = arrowSDF(arrowSt, arrowWidth, arrowHeight);
float antialias_range = 0.01; // 抗锯齿范围
float arrowFactor = 1.0 - smoothstep(0.0, antialias_range, sdf);
// 混合箭头颜色
vec4 finalColor = mix(lineColor, vec4(1.0,0.0,0.0,1.0), arrowFactor);
material.diffuse = finalColor.rgb;
material.alpha = finalColor.a;
```
### 完整示例代码
```glsl
// 自定义材质:带方向箭头的线条
czm_material czm_getMaterial(czm_materialInput materialInput){
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
// 基础线条颜色(蓝色)
vec4 lineColor = vec4(0.0, 0.5, 1.0, 1.0);
// 箭头参数
float arrowWidth = 0.3;
float arrowHeight = 0.3;
float spacing = 0.5;
float arrowPos = fract(st.s / spacing);
vec2 arrowSt = vec2(arrowPos, st.t);
// 计算箭头SDF(有向距离场)
float sdf = arrowSDF(arrowSt, arrowWidth, arrowHeight);
// 抗锯齿渲染
float antialias = 0.01;
float arrowFactor = 1.0 - smoothstep(0.0, antialias, sdf);
// 混合颜色
vec4 finalColor = mix(lineColor, vec4(1.0, 0.0, 0.0, 1.0), arrowFactor);
material.diffuse = finalColor.rgb;
material.alpha = finalColor.a;
return material;
}
// 计算点到线段的距离
float distanceToLine(vec2 p, vec2 a, vec2 b){
vec2 pa = p - a;
vec2 ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h);
}
// 箭头SDF函数
float arrowSDF(vec2 st, float width, float height){
// 箭头底部两个角点
vec2 bottomLeft = vec2(0.5 - width/2.0, 0.0);
vec2 bottomRight = vec2(0.5 + width/2.0, 0.0);
vec2 tip = vec2(0.5, height); // 箭头尖点
// 计算到三条边的距离
float d1 = distanceToLine(st, bottomLeft, tip);
float d2 = distanceToLine(st, bottomRight, tip);
float d3 = st.y; // 底部边界(y=0)
// 返回最小距离(内部为负值)
return min(d3, min(d1, d2));
}
```
### 使用注意事项
1. **参数调整**:根据实际线条宽度调整`arrowWidth`和`arrowHeight`
2. **方向控制**:若需要反向箭头,可修改`tip`坐标的y值为负
3. **性能优化**:复杂线条建议使用预生成箭头纹理替代实时SDF计算
> **技术要点**:通过有向距离场(SDF)生成几何图形可实现任意分辨率下的清晰显示,结合`smoothstep`函数实现抗锯齿边缘[^2]。
§§相关问题§§
1. 如何在Cesium中实现动态移动的箭头效果(如流动方向指示)?
2. 如何优化复杂SDF计算的性能?
3. Cesium中如何为3D管线(如管道)添加方向标记?
</think>
### 在Cesium着色器中为线条添加方向箭头的实现方法
在Cesium中为线条内部添加方向箭头需要巧妙利用纹理坐标和数学计算,以下是完整实现方案:
#### 核心原理
```glsl
// 关键算法框架
czm_material czm_getMaterial(czm_materialInput i) {
czm_material m = czm_getDefaultMaterial(i);
vec2 st = i.st; // 获取纹理坐标
// 箭头生成逻辑
if (shouldRenderArrow(st)) {
m.diffuse = arrowColor;
} else {
m.diffuse = lineColor;
}
return m;
}
```
#### 完整实现代码
```glsl
// 自定义材质:带方向箭头的线条
czm_material czm_getMaterial(czm_materialInput materialInput) {
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
// 线条基础颜色
vec4 lineColor = vec4(0.0, 0.5, 1.0, 1.0); // 蓝色线条
vec4 arrowColor = vec4(1.0, 0.0, 0.0, 1.0); // 红色箭头
// 箭头参数
float arrowSpacing = 0.2; // 箭头间距(0-1)
float arrowSize = 0.1; // 箭头大小
float arrowTip = 0.9; // 箭头尖端位置
// 计算箭头位置 (沿s轴重复)
float arrowPos = fract(st.s / arrowSpacing);
// 仅在箭头区域绘制
if (arrowPos > arrowTip - arrowSize && arrowPos < arrowTip) {
// 计算归一化坐标
float normalizedPos = (arrowPos - (arrowTip - arrowSize)) / arrowSize;
// 箭头形状计算:等腰三角形
float tipPosition = 0.5; // 箭头尖端在中心
float arrowWidth = 0.5 * (1.0 - normalizedPos); // 箭头宽度渐变
// 判断是否在箭头区域内 (y方向在[-arrowWidth, arrowWidth]之间)
if (abs(st.t - 0.5) < arrowWidth && st.t > 0.5 - arrowWidth) {
material.diffuse = arrowColor.rgb;
material.alpha = arrowColor.a;
return material;
}
}
// 绘制线条主体
material.diffuse = lineColor.rgb;
material.alpha = lineColor.a;
return material;
}
```
#### 关键参数说明
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `arrowSpacing` | `float` | 0.2 | 箭头间距(沿线条方向) |
| `arrowSize` | `float` | 0.1 | 箭头大小 |
| `arrowTip` | `float` | 0.9 | 箭头尖端位置 |
| `lineColor` | `vec4` | 蓝色 | 线条基础颜色 |
| `arrowColor` | `vec4` | 红色 | 箭头颜色 |
#### 动态箭头方向调整
要使箭头方向适应线条走向,需结合线条角度:
```glsl
// 添加顶点着色器输出
varying float v_polylineAngle; // 从顶点着色器传递线条角度
// 在片段着色器中
float angle = v_polylineAngle;
mat2 rotation = mat2(cos(angle), -sin(angle),
sin(angle), cos(angle));
// 旋转坐标使箭头对齐线条方向
vec2 rotatedSt = rotation * (st - vec2(0.5)) + vec2(0.5);
```
#### 使用示例
```javascript
// 创建带箭头的线条材质
const arrowLineMaterial = new Cesium.Material({
fabric: {
type: 'ArrowLine',
source: shaderSource, // 上面的GLSL代码
uniforms: {
arrowSpacing: 0.3,
arrowSize: 0.15
}
}
});
// 应用到图元
const polyline = viewer.entities.add({
polyline: {
positions: Cesium.Cartesian3.fromDegreesArray([...]),
width: 10,
material: arrowLineMaterial
}
});
```
#### 优化技巧
1. **动画效果**:添加时间变量创建流动箭头
```glsl
float movingArrow = fract(st.s / arrowSpacing + czm_frameNumber * 0.01);
```
2. **动态尺寸**:根据缩放级别调整箭头大小
```glsl
float screenSize = czm_metersPerPixel * arrowScale;
```
3. **距离衰减**:远处减少箭头密度
```glsl
float density = clamp(1.0 / (czm_metersPerPixel * 10.0), 0.1, 1.0);
```
> **提示**:在Sandcastle中使用`viewer.scene.debugShowFramesPerSecond = true`监控性能影响[^1]