cesium 水面、淹没 效果
水面效果
参考:
http://cesiumcn.org/topic/158.html
http://api.rivermap.cn/cesium/rivermap/map.html
https://blog.csdn.net/weixin_42496466/article/details/80747565
demo效果:
主要代码:
//绘制水面波浪效果
drawWater: function(){
this.viewer.scene.globe.depthTestAgainstTerrain = false;
var waterFace=[
130.0, 30.0, 0,
150.0, 30.0, 0,
150.0, 10.0, 0,
130.0, 10.0, 0];
var waterPrimitive = new Cesium.Primitive({
show:true,// 默认隐藏
allowPicking:false,
geometryInstances : new Cesium.GeometryInstance({
geometry : new Cesium.PolygonGeometry({
polygonHierarchy : new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArrayHeights(waterFace)),
//extrudedHeight: 0,//注释掉此属性可以只显示水面
//perPositionHeight : true//注释掉此属性水面就贴地了
})
}),
// 可以设置内置的水面shader
appearance : new Cesium.EllipsoidSurfaceAppearance({
material : new Cesium.Material({
fabric : {
type : 'Water',
uniforms : {
//baseWaterColor:new Cesium.Color(0.0, 0.0, 1.0, 0.5),
//blendColor: new Cesium.Color(0.0, 0.0, 1.0, 0.5),
//specularMap: 'gray.jpg',
//normalMap: '../assets/waterNormals.jpg',
normalMap: '本地贴图地址 或 base64',
frequency: 1000.0,
animationSpeed: 0.01,
amplitude: 10.0
}
}
}),
fragmentShaderSource:'varying vec3 v_positionMC;\nvarying vec3 v_positionEC;\nvarying vec2 v_st;\nvoid main()\n{\nczm_materialInput materialInput;\nvec3 normalEC = normalize(czm_normal3D * czm_geodeticSurfaceNormal(v_positionMC, vec3(0.0), vec3(1.0)));\n#ifdef FACE_FORWARD\nnormalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);\n#endif\nmaterialInput.s = v_st.s;\nmaterialInput.st = v_st;\nmaterialInput.str = vec3(v_st, 0.0);\nmaterialInput.normalEC = normalEC;\nmaterialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(v_positionMC, materialInput.normalEC);\nvec3 positionToEyeEC = -v_positionEC;\nmaterialInput.positionToEyeEC = positionToEyeEC;\nczm_material material = czm_getMaterial(materialInput);\n#ifdef FLAT\ngl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n#else\ngl_FragColor = czm_phong(normalize(positionToEyeEC), material);\
gl_FragColor.a=0.5;\n#endif\n}\n'//重写shader,修改水面的透明度
})
});
this.viewer.scene.primitives.add(waterPrimitive);
this.viewer.camera.flyTo({
destination : Cesium.Cartesian3.fromDegrees(140, 20, 6000000.0),
orientation : {
heading: Cesium.Math.toRadians(0.0), //默认朝北0度,顺时针方向,东是90度
pitch: Cesium.Math.toRadians(-90), //默认朝下看-90,0为水平看,
roll: Cesium.Math.toRadians(0) //默认0
}
});
}
注意这里
this.viewer.scene.globe.depthTestAgainstTerrain = false;
而淹没效果需要将其设置为 true;当其值为 true 时,水面效果 会出现缝隙,如下图所示。
贴图从参考链接中可获取,这里附上:
http://api.rivermap.cn/cesium/Build/CesiumUnminified/Assets/Textures/waterNormals.jpg
淹没效果
参考:
https://github.com/liyangis/sn_cesium
demo效果:
主要代码(包含未用到的热力图效果代码):
import Cesium from 'cesium/Source/Cesium' import HeatMap from "../modules/heatmap"; // 淹没分析 export default class SubmergenceAnalysis { constructor(viewer, isTerrain = true, height_max, height_min, step, map_type,positionsArr,speed) { this.viewer = viewer this.isTerrain = isTerrain this.handler = null this.tempEntities = [] this.polygonEntities = [] this.linePositionList = [] this.tempPoints = [] this.extrudedHeight = height_min this.height_max = height_max this.height_min = height_min this.step = step // 默认是范围图/深度图 this.map_type = map_type this.polygon_degrees = positionsArr this.speed = speed //this._initViewStatus(this.viewer) this._addDisListener() } _initViewStatus(viewer) { var scene = viewer.scene scene.globe.depthTestAgainstTerrain = true viewer.camera.flyTo({ //scene.camera.setView({ // 摄像头的位置 destination: Cesium.Cartesian3.fromDegrees(108.9, 34, 5000.0), orientation: { heading: Cesium.Math.toRadians(0.0),//默认朝北0度,顺时针方向,东是90度 pitch: Cesium.Math.toRadians(-20),//默认朝下看-90,0为水平看, roll: Cesium.Math.toRadians(0)//默认0 } }); viewer.skyAtmosphere = false } // 根据矩形范围得到行列数点坐标和高程信息 _getPoints(xmin, xmax, ymin, ymax) { const x_count = 10 const y_count = 10 let cartesians = new Array(x_count * y_count); const x_d = (xmax - xmin) / x_count for (var i = 0; i < x_count; ++i) { const start_pt = { x: xmin + i * x_d, y: ymax } const end_pt = { x: xmin + i * x_d, y: ymin } for (let j = 0; j < y_count; j++) { const offset = j / (y_count - 1); const x = Cesium.Math.lerp(start_pt.x, end_pt.x, offset); const y = Cesium.Math.lerp(start_pt.y, end_pt.y, offset); cartesians[j + i * y_count] = Cesium.Cartographic.fromDegrees(x, y); } } return cartesians } _getHeights(cartesians, extrudedHeight, callback) { var terrainProvider = new Cesium.createWorldTerrain({ requestVertexNormals: true }) // 根据地形计算某经纬度点的高度 var promise = Cesium.sampleTerrainMostDetailed(terrainProvider, cartesians); Cesium.when(promise, function (updatedPositions) { let positions = updatedPositions.filter(d => { const cartographic = d if (cartographic) { const h_d = extrudedHeight - cartographic.height return h_d > 0 } }) positions = positions.map(d => { const cartographic = d let h = extrudedHeight - cartographic.height return { x: Cesium.Math.toDegrees(cartographic.longitude), y: Cesium.Math.toDegrees(cartographic.latitude), value: h } }) if (callback) { callback(positions) } }); } _addDisListener() { let viewer = this.viewer let scene = viewer.scene let linePositionList = this.linePositionList viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK) this.handler = new Cesium.ScreenSpaceEventHandler(scene.canvas) // 绘制线 this._drawLine(linePositionList) //this.loadGrandCanyon() // 绘制面 if (this.map_type) { this._drawPoly(this.polygon_degrees) } else { // 得到插值网格 const bounds = { west: 115.8784, east: 115.9614, south: 39.9912, north: 40.0381 } const positions_cartesian = this._getPoints(bounds.east, bounds.west, bounds.south, bounds.north) this._getHeights(positions_cartesian, this.extrudedHeight, (d) => { this.heatMapObj = new HeatMap(this.viewer, d, bounds); }) } } _reDraw() { this.tempPoints = [] this.linePositionList.length = 0 this.areaPositionList.length = 0 for (let entity of this.tempEntities) { this.viewer.entities.remove(entity) } this.tempEntities = [] } _drawLine(linePositionList) { let lineStyle = { width: 2, material: Cesium.Color.CHARTREUSE } let entity = this.viewer.entities.add({ polyline: lineStyle, }) entity.polyline.positions = new Cesium.CallbackProperty(function () { return linePositionList }, false) this.polygonEntities.push(entity) } _drawPoint(point_Cartesian3) { let entity = this.viewer.entities.add({ position: point_Cartesian3, point: { pixelSize: 10, color: Cesium.Color.GOLD, // disableDepthTestDistance: Number.POSITIVE_INFINITY, // heightReference: Cesium.HeightReference.CLAMP_TO_GROUND } }) this.tempEntities.push(entity) } _drawPoly(degrees) { const that = this let entity = this.viewer.entities.add({ polygon: { hierarchy: {}, material: new Cesium.Color.fromBytes(64, 157, 253, 100), perPositionHeight: true, } }) entity.polygon.hierarchy = new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArray(degrees)) entity.polygon.extrudedHeight = new Cesium.CallbackProperty(() => that.extrudedHeight, false) this.polygonEntities.push(entity) } // 世界坐标转经纬坐标 _car3ToLatLon(cartesian) { let cartographic = Cesium.Cartographic.fromCartesian(cartesian) let longitudeString = Cesium.Math.toDegrees(cartographic.longitude) let latitudeString = Cesium.Math.toDegrees(cartographic.latitude) return { lon: longitudeString, lat: latitudeString, height: cartographic.height } } //移除整个资源 remove() { let viewer = this.viewer for (let tempEntity of this.tempEntities) { viewer.entities.remove(tempEntity) } for (let lineEntity of this.polygonEntities) { viewer.entities.remove(lineEntity) } this.handler.destroy() } start() { const that = this this.timer = window.setInterval(() => { if ((that.height_max > that.extrudedHeight) && (that.extrudedHeight >= that.height_min)) { that.extrudedHeight = that.extrudedHeight + that.step } else { that.extrudedHeight = that.height_min } if (!that.map_type) { if (this.heatMapObj) { const bounds = { west: 115.8784, east: 115.9614, south: 39.9912, north: 40.0381 } const positions_cartesian = this._getPoints(bounds.east, bounds.west, bounds.south, bounds.north) this._getHeights(positions_cartesian, this.extrudedHeight, (d) => { this.heatMapObj.update(d); }) } } },that.speed*1000) if (that.map_type) { that._drawPoly(that.polygon_degrees) } else { if (this.heatMapObj) { } } } clear() { let viewer = this.viewer if (this.timer) { window.clearInterval(this.timer) this.timer = null } this.extrudedHeight = this.height_min; if (this.heatMapObj) this.heatMapObj.show(false) for (let entity of this.polygonEntities) { viewer.entities.remove(entity) } viewer.skyAtmosphere = true; } changeMapType(type) { if (!type) { if (!this.heatMapObj) { // 得到插值网格 const bounds = { west: 115.8784, east: 115.9614, south: 39.9912, north: 40.0381 } const positions_cartesian = this._getPoints(bounds.east, bounds.west, bounds.south, bounds.north) this._getHeights(positions_cartesian, this.extrudedHeight, (d) => { this.heatMapObj = new HeatMap(this.viewer, d, bounds); }) } this.heatMapObj && this.heatMapObj.show(true) for (let entity of this.polygonEntities) { entity.show = false; } } else { this.heatMapObj.show(false) for (let entity of this.polygonEntities) { entity.show = true; } } } // 切割一部分地形 loadGrandCanyon() { var globe = this.viewer.scene.globe; const viewer = this.viewer // viewer.skyAtmosphere = false, // Pick a position at the Grand Canyon var position = Cesium.Cartographic.toCartesian(new Cesium.Cartographic.fromDegrees(115.9165534, 40.0139345, 100)); var distance = 30000.0; var boundingSphere = new Cesium.BoundingSphere(position, distance); globe.clippingPlanes = new Cesium.ClippingPlaneCollection({ modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(position), planes: [ new Cesium.ClippingPlane(new Cesium.Cartesian3(1.0, 0.0, 0.0), distance), new Cesium.ClippingPlane(new Cesium.Cartesian3(-1.0, 0.0, 0.0), distance), new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 1.0, 0.0), distance), new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, -1.0, 0.0), distance) ], unionClippingRegions: true }); globe.clippingPlanes.enabled = true; viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.5, -0.5, boundingSphere.radius * 5.0)); viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); } }
洪水淹没效果
在上一步的基础上,希望达到“洪水顺着河流向前淹没”效果
效果如下图:(注:只能在特定地区,特定观察角度,效果才会稍好一点 😅)
原理:沿河道下游方向连续绘制长条状的蓝色立方体(由长条状矩形polygon拉伸而来),并使用上文“淹没效果”不断抬升。
几点需要注意:
1、上下游落差较大,因此上下游的立方体,起始高度不同(demo里仅仅做了均匀减少一定值的操作,实际应根据该位置河道具体高度)
2、长条状立方体的南北宽度要超过河道(demo里设置为固定值,实际应根据该位置河道具体宽度设置)
3、因为是连续绘制长条状立方体,所以相邻立方体之间的连接处很明显,效果较差。
4、目前只是演示,挑了一段东西走向的河道,绘制方向仅仅是向东每次平移一个长条立方体宽度的距离,不符合实际情况。
这个demo演示了连续(根据长条状矩形polygon)绘制立方体的效果,只能作为演示。
实际应根据dem 处理获得很多个连续的polygon(非长条状,包含时间属性、高度属性),然后逐一绘制。
dem的数据处理参考:
https://blog.csdn.net/wqy248/article/details/81119550
待下一步测试。~