在三维可视化项目中,栅栏或墙体往往需要动态效果来增强视觉表现力。Cesium作为优秀的三维地球引擎,提供了强大的材质系统,但官方文档对动态材质的实现细节着墨不多。这里我将分享如何通过自定义MaterialProperty实现栅栏的流动光效。
动态材质的核心在于时间变量的控制。与静态材质不同,动态材质需要随时间变化更新外观。Cesium的材质系统底层基于WebGL着色器,这意味着我们可以通过GLSL代码直接控制渲染效果。实际开发中,我遇到过三个关键问题:
解决方案是创建一个继承自Cesium.MaterialProperty的类。这个类需要实现getValue方法,在每一帧渲染时更新uniform变量。下面这段代码展示了基础结构:
javascript复制function DynamicFenceMaterial(options) {
this._definitionChanged = new Cesium.Event();
this._color = undefined;
this._time = Date.now();
this.speed = options.speed || 1.0;
}
DynamicFenceMaterial.prototype.getValue = function(time) {
return {
color: this._color,
progress: ((Date.now() - this._time) * this.speed) % 10000 / 10000
};
};
完整的动态栅栏材质需要封装成一个独立的类。经过多次项目实践,我总结出可靠的实现方案:
javascript复制class FlowLightMaterialProperty {
constructor(options) {
options = options || {};
this._definitionChanged = new Cesium.Event();
this._color = options.color || new Cesium.Color(0, 1, 1, 0.7);
this._speed = options.speed || 1.0;
this._image = options.image || null;
this._time = Date.now();
// 材质类型唯一标识
this._materialType = `FlowLight_${Math.random().toString(36).substr(2, 9)}`;
this._setupMaterialDefinition();
}
_setupMaterialDefinition() {
const fabric = {
type: this._materialType,
uniforms: {
color: this._color,
image: this._image,
time: 0
},
source: `czm_material czm_getMaterial(czm_materialInput materialInput) {
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
// 流动光效核心算法
float glow = cos(st.s * 10.0 - time * 5.0) * 0.5 + 0.5;
vec3 emissive = color.rgb * glow * 2.0;
material.diffuse = color.rgb;
material.alpha = color.a;
material.emission = emissive;
return material;
}`
};
Cesium.Material._materialCache.addMaterial(this._materialType, {
fabric: fabric,
translucent: true
});
}
}
这个实现中有几个值得注意的技术点:
实测中发现,流动速度(speed参数)建议控制在0.5-2.0之间。值太小效果不明显,太大则会出现闪烁问题。颜色建议使用带透明度的RGBA值,这样能与场景更好融合。
先准备一组栅栏的坐标数据。我通常使用GeoJSON格式作为中间数据,再转换为Cesium需要的Cartesian3数组:
javascript复制const fencePositions = Cesium.Cartesian3.fromDegreesArray([
116.391231, 39.907423,
116.401548, 39.908127,
116.402331, 39.901455,
116.392018, 39.900812
]);
将自定义材质应用到WallGraphics:
javascript复制const flowMaterial = new FlowLightMaterialProperty({
color: Cesium.Color.CYAN.withAlpha(0.6),
speed: 1.2,
image: 'textures/glow.png'
});
viewer.entities.add({
wall: {
positions: fencePositions,
minimumHeights: [0, 0, 0, 0],
maximumHeights: [50, 50, 50, 50],
material: flowMaterial
}
});
这里有个实用技巧:minimumHeights设为0可以让栅栏看起来是从地面"生长"出来的。如果要做悬浮效果,可以设置负值。
复杂场景可能需要分段控制光效。通过修改着色器代码可以实现:
glsl复制// 在原有着色器中添加分段逻辑
float segment = floor(st.s * 3.0);
float phase = mod(time + segment * 0.3, 1.0);
float glow = smoothstep(0.3, 0.5, phase) - smoothstep(0.6, 0.8, phase);
这样会产生三段交替流动的光带,适合长距离栅栏场景。
动态材质会增加渲染负担。建议在开发时开启Cesium的性能统计:
javascript复制viewer.scene.debugShowFramesPerSecond = true;
当FPS低于30时,可以考虑以下优化措施:
我在一个智慧园区项目中,通过将speed从1.5降到1.0,帧率提升了40%。