cesium 发光线
发光线也是一种比较常见的可视化效果,cesium 内置的材质已经提供了PolylineGlowMaterialProperty类,实现了发光效果。这种材质在线的中间部分始终会有白色线条,如果想实现纯色的发光线,就需要对这种材质进行改造。本篇通过自定义MaterialProperty提供两种发光线材质。
1. 自定义GlowLineMaterialProperty类
1.1. 自定义 GlowLineMaterialProperty 类
/*
* @Description:
* @Author: maizi
* @Date: 2024-08-16 15:06:46
* @LastEditTime: 2024-08-27 17:10:15
* @LastEditors: maizi
*/
function GlowLineMaterialProperty(options) {
options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT);
this._definitionChanged = new Cesium.Event();
this._color = undefined;
this._power = undefined;
this.color = options.color || Cesium.Color.fromBytes(0, 255, 255, 255);
this.power = options.power || 0.25;
}
Object.defineProperties(GlowLineMaterialProperty.prototype, {
isConstant: {
get: function () {
return (
Cesium.Property.isConstant(this._color)
&& Cesium.Property.isConstant(this._power)
);
},
},
definitionChanged: {
get: function () {
return this._definitionChanged;
},
},
color: Cesium.createPropertyDescriptor("color"),
power: Cesium.createPropertyDescriptor("power")
});
GlowLineMaterialProperty.prototype.getType = function (time) {
return "GlowLine";
};
GlowLineMaterialProperty.prototype.getValue = function (time, result) {
if (!Cesium.defined(result)) {
result = {};
}
result.color = Cesium.Property.getValueOrClonedDefault(this._color, time);
result.power = Cesium.Property.getValueOrClonedDefault(this._power, time);
return result;
};
GlowLineMaterialProperty.prototype.equals = function (other) {
const res = this === other || (other instanceof GlowLineMaterialProperty &&
Cesium.Property.equals(this._color, other._color) &&
Cesium.Property.equals(this._power, other._power))
return res;
};
export default GlowLineMaterialProperty;
1.2. 材质shader
uniform vec4 color;
uniform float power;
czm_material czm_getMaterial(czm_materialInput materialInput)
{
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
float glow = power / abs(st.t - 0.5) - (power / 0.5);
material.alpha = clamp(0.0, 1.0, glow);
material.diffuse = color.rgb;
return material;
}
1.3. 添加到缓存
/*
* @Description:
* @Author: maizi
* @Date: 2024-08-16 16:21:55
* @LastEditTime: 2024-08-26 18:00:42
* @LastEditors: maizi
*/
import GlowLinetMaterial from '../shader/GlowLinetMaterial.glsl'
Cesium.Material.GlowLine = 'GlowLine'
Cesium.Material._materialCache.addMaterial(
Cesium.Material.GlowLine,
{
fabric: {
type: Cesium.Material.GlowLine,
uniforms: {
color: new Cesium.Color(1.0, 0.0, 0.0, 0.7),
power: 0.25
},
source: GlowLinetMaterial,
},
translucent: function (material) {
return true
},
}
)
2. 自定义ImageGlowLineMaterialProperty类
2.1. 自定义ImageGlowLineMaterialProperty类
/*
* @Description:
* @Author: maizi
* @Date: 2024-08-16 15:06:46
* @LastEditTime: 2024-08-27 17:17:09
* @LastEditors: maizi
*/
function ImageGlowLineMaterialProperty(options) {
options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT);
this._definitionChanged = new Cesium.Event();
this._color = undefined;
this._image = undefined;
this.color = options.color || Cesium.Color.fromBytes(0, 255, 255, 255);
this.image = options.image;
}
Object.defineProperties(ImageGlowLineMaterialProperty.prototype, {
isConstant: {
get: function () {
return (
Cesium.Property.isConstant(this._color)
&& Cesium.Property.isConstant(this._image)
);
},
},
definitionChanged: {
get: function () {
return this._definitionChanged;
},
},
color: Cesium.createPropertyDescriptor("color"),
image: Cesium.createPropertyDescriptor("image")
});
ImageGlowLineMaterialProperty.prototype.getType = function (time) {
return "ImageGlowLine";
};
ImageGlowLineMaterialProperty.prototype.getValue = function (time, result) {
if (!Cesium.defined(result)) {
result = {};
}
result.color = Cesium.Property.getValueOrClonedDefault(this._color, time);
result.image = Cesium.Property.getValueOrClonedDefault(this._image, time);
return result;
};
ImageGlowLineMaterialProperty.prototype.equals = function (other) {
const res = this === other || (other instanceof ImageGlowLineMaterialProperty &&
Cesium.Property.equals(this._color, other._color) &&
Cesium.Property.equals(this._image, other._image))
return res;
};
export default ImageGlowLineMaterialProperty;
2.2. 材质shader
uniform sampler2D image;
uniform vec4 color;
czm_material czm_getMaterial(czm_materialInput materialInput){
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
vec4 colorImage = texture(image,st);
vec3 fragColor = color.rgb;
material.alpha = colorImage.a * color.a * 3.;
// 设置中间发光图片的颜色占比
if(st.t < 0.55 && st.t > 0.45) {
material.diffuse = colorImage.rgb;
} else {
material.diffuse = fragColor.rgb;
}
return material;
}
2.3. 添加到缓存
/*
* @Description:
* @Author: maizi
* @Date: 2024-08-16 16:21:55
* @LastEditTime: 2024-08-26 18:00:42
* @LastEditors: maizi
*/
import ImageGlowLinetMaterial from '../shader/ImageGlowLinetMaterial.glsl'
Cesium.Material.ImageGlowLine = 'ImageGlowLine'
Cesium.Material._materialCache.addMaterial(
Cesium.Material.ImageGlowLine,
{
fabric: {
type: Cesium.Material.ImageGlowLine,
uniforms: {
color: new Cesium.Color(1.0, 0.0, 0.0, 0.7),
image: Cesium.Material.DefaultImageId,
},
source: ImageGlowLinetMaterial,
},
translucent: function (material) {
return true
},
}
)
3. 完整示例代码
GlowLine.js
/*
* @Description:
* @Author: maizi
* @Date: 2022-05-27 11:36:22
* @LastEditTime: 2024-08-27 18:27:19
* @LastEditors: maizi
*/
const merge = require('deepmerge')
import { GlowLineMaterialProperty, ImageGlowLineMaterialProperty } from '../../materialProperty/index.js'
const defaultStyle = {
power: 0.25,
color: "#ffff00",
lineWidth: 8,
type: 0
}
class GlowLine {
constructor(viewer, coords, options = {}) {
this.viewer = viewer
this.coords = coords;
this.options = options;
this.props = this.options.props;
this.style = merge(defaultStyle, this.options.style || {});
this.entity = null;
this.material = null
this.points = []
this.init();
}
init() {
this.createMaterial();
this.entity = new Cesium.Entity({
id: Math.random().toString(36).substring(2),
type: "glow_line",
polyline: {
positions: this.getPositons(),
width: this.style.lineWidth,
material: this.material,
//clampToGround: true
}
});
}
addPoints() {
this.coords.forEach((coord) => {
const point = new Cesium.Entity({
position: Cesium.Cartesian3.fromDegrees(coord[0],coord[1],coord[2]),
point: {
color: Cesium.Color.DARKBLUE.withAlpha(.4),
pixelSize: 6,
outlineColor: Cesium.Color.YELLOW.withAlpha(.8),
outlineWidth: 4
}
});
this.viewer.entities.add(point)
this.points.push(point)
})
}
removePoints() {
this.points.forEach((point) => {
this.viewer.entities.remove(point)
})
this.points = []
}
getPositons() {
const positions = []
this.coords.forEach((coord) => {
positions.push(Cesium.Cartesian3.fromDegrees(coord[0], coord[1], coord[2]));
})
return positions
}
createMaterial() {
switch(this.style.type){
case 0:
this.material = new GlowLineMaterialProperty({
color: new Cesium.Color.fromCssColorString(this.style.color),
power:this.style.power
});
break
case 1:
this.material = new ImageGlowLineMaterialProperty({
color: new Cesium.Color.fromCssColorString(this.style.color),
image: require("@/assets/img/glow_line.png"),
});
break
}
}
updateStyle(style) {
this.style = merge(defaultStyle, style);
this.entity.polyline.width = this.style.lineWidth
this.material.color = new Cesium.Color.fromCssColorString(this.style.color)
if(this.style.type === 0) {
this.material.power = this.style.power
}
}
setSelect(enabled) {
if (enabled) {
this.addPoints()
} else {
this.removePoints()
}
}
}
export {
GlowLine
}
MapWorks.js
import GUI from 'lil-gui';
// 初始视图定位在中国
import { GlowLine } from './GlowLine'
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(90, -20, 110, 90);
let viewer = null;
let graphicLayer = null
let graphicList = []
let selectGraphic = null
let eventHandler = null
let gui = null
function initMap(container) {
viewer = new Cesium.Viewer(container, {
animation: false,
baseLayerPicker: false,
fullscreenButton: false,
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: false,
selectionIndicator: false,
timeline: false,
navigationHelpButton: false,
scene3DOnly: true,
orderIndependentTranslucency: false,
contextOptions: {
webgl: {
alpha: true
}
}
})
viewer._cesiumWidget._creditContainer.style.display = 'none'
viewer.scene.fxaa = true
viewer.scene.postProcessStages.fxaa.enabled = true
if (Cesium.FeatureDetection.supportsImageRenderingPixelated()) {
// 判断是否支持图像渲染像素化处理
viewer.resolutionScale = window.devicePixelRatio
}
// 移除默认影像
removeAll()
// 地形深度测试
viewer.scene.globe.depthTestAgainstTerrain = true
// 背景色
viewer.scene.globe.baseColor = new Cesium.Color(0.0, 0.0, 0.0, 0)
// 太阳光照
viewer.scene.globe.enableLighting = true;
// 初始化图层
initLayer()
// 初始化鼠标事件
initClickEvent()
//调试
window.viewer = viewer
}
function initGui() {
let params = {
...selectGraphic.style
}
gui = new GUI()
let layerFolder = gui.title('样式设置')
if (params.type === 0) {
layerFolder.add(params, 'power', 0, 1).step(0.01).onChange(function (value) {
selectGraphic.updateStyle(params)
})
}
layerFolder.add(params, 'lineWidth', 1, 30).step(1).onChange(function (value) {
selectGraphic.updateStyle(params)
})
layerFolder.addColor(params, 'color').onChange(function (value) {
selectGraphic.updateStyle(params)
})
}
function initLayer() {
const layerProvider = new Cesium.ArcGisMapServerImageryProvider({
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
});
viewer.imageryLayers.addImageryProvider(layerProvider);
graphicLayer = new Cesium.CustomDataSource('graphicLayer')
viewer.dataSources.add(graphicLayer)
}
function loadGrowLine(lines) {
lines.forEach(line => {
const glowLine = new GlowLine(viewer, line.points, {
style: line.style
})
graphicList.push(glowLine)
graphicLayer.entities.add(glowLine.entity)
});
viewer.flyTo(graphicLayer)
}
function initClickEvent() {
eventHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
initLeftClickEvent()
initMouseMoveEvent()
}
function initLeftClickEvent() {
eventHandler.setInputAction((e) => {
if (selectGraphic) {
selectGraphic.setSelect(false)
selectGraphic = null
}
if (gui) {
gui.destroy()
}
let pickedObj = viewer.scene.pick(e.position);
if (pickedObj && pickedObj.id) {
if (pickedObj.id.type === 'glow_line') {
selectGraphic = getGraphicById(pickedObj.id.id)
if (selectGraphic) {
selectGraphic.setSelect(true)
initGui()
}
}
}
},Cesium.ScreenSpaceEventType.LEFT_CLICK)
}
function initMouseMoveEvent() {
eventHandler.setInputAction((e) => {
const pickedObj = viewer.scene.pick(e.endPosition);
if (pickedObj && pickedObj.id) {
if (pickedObj.id.type === 'glow_line') {
// 改变鼠标状态
viewer._element.style.cursor = "";
document.body.style.cursor = "pointer";
} else {
viewer._element.style.cursor = "";
document.body.style.cursor = "default";
}
} else {
viewer._element.style.cursor = "";
document.body.style.cursor = "default";
}
},Cesium.ScreenSpaceEventType.MOUSE_MOVE)
}
function getGraphicById(id) {
let line = null
for (let i = 0; i < graphicList.length; i++) {
if (graphicList[i].entity.id === id) {
line = graphicList[i]
break
}
}
return line
}
function removeAll() {
viewer.imageryLayers.removeAll();
}
function destroy() {
viewer.entities.removeAll();
viewer.imageryLayers.removeAll();
viewer.destroy();
}
export {
initMap,
loadGrowLine,
destroy
}
GlowLine.vue
<!--
* @Description:
* @Author: maizi
* @Date: 2023-04-07 17:03:50
* @LastEditTime: 2024-08-27 17:02:06
* @LastEditors: maizi
-->
<template>
<div id="container">
</div>
</template>
<script>
import * as MapWorks from './js/MapWorks'
export default {
name: 'GlowLine',
mounted() {
this.init();
},
methods:{
init(){
let container = document.getElementById("container");
MapWorks.initMap(container)
let lines = [
{
points: [
[104.068822,30.654807,10],
[104.088822,30.654807,10],
],
style: {
type: 0
},
},
{
points: [
[104.068822,30.655807,10],
[104.088822,30.655807,10],
],
style: {
type: 1
},
},
];
MapWorks.loadGrowLine(lines)
}
},
beforeDestroy(){
//实例被销毁前调用,页面关闭、路由跳转、v-if和改变key值
MapWorks.destroy();
}
}
</script>
<style lang="scss" scoped>
#container{
width: 100%;
height: 100%;
background: rgba(7, 12, 19, 1);
overflow: hidden;
background-size: 40px 40px, 40px 40px;
background-image: linear-gradient(hsla(0, 0%, 100%, 0.05) 1px, transparent 0), linear-gradient(90deg, hsla(0, 0%, 100%, 0.05) 1px, transparent 0);
}
</style>