需求:生成好看的动态水纹理,并结合地形实现水动态淹没效果
实现思路:
1.生成水纹理:通过着色器根据海洋参数,噪声参数,扩散反射来获得合适的水纹效果。
2. 结合地形实现水动态淹没效果:将生成的水纹理应用于水面材质。然后,根据地形的高度信息,实现水的淹没效果。通过调整透明度来实现水的淹没效果。
3. 实现水动态效果:为了让水看起来更真实,添加一些动态效果,使用法线贴图来模拟水面的波动,或者使用屏幕空间反射等技术来实现水面的反射效果。
实现效果:
参数设定:
自定义着色器纹理,绘制区域范围,开始高度(地形最低点),蔓延速度,蔓延最大高度
实现代码(基于cesium1.114版本实现):
1.初始化cesium
Cesium.Ion.defaultAccessToken ="您自己的tk";
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(90, -20, 110, 90);
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画控件
homeButton: false, //是否显示home键
geocoder: false, //是否显示地名查找控件 如果设置为true,则无法查询
baseLayerPicker: true, //是否显示图层选择控件
timeline: true, //是否显示时间线控件
fullscreenButton: false, //是否全屏显示
scene3DOnly: true, //如果设置为true,则所有几何图形以3D模式绘制以节约GPU资源
infoBox: false, //是否显示点击要素之后显示的信息
sceneModePicker: false, //是否显示投影方式控件 三维/二维
navigationInstructionsInitiallyVisible: false,
navigationHelpButton: false, //是否显示帮助信息控件
selectionIndicator: false, //是否显示指示器组件
shouldAnimate: true, //加上这条可以显示粒子(火焰,喷泉)
contextOptions: { //屏幕截图必写
webgl: {
alpha: true,
depth: true,
stencil: true,
antialias: true,
premultipliedAlpha: true,
preserveDrawingBuffer: true,
failIfMajorPerformanceCaveat: true
},
allowTextureFilterAnisotropic: true
},
imageryProviderViewModels: [],
imageryProvider: false
});
//忽略默认地图
var imageryProviderViewModels = viewer.baseLayerPicker.viewModel.imageryProviderViewModels;
viewer.baseLayerPicker.viewModel.selectedImagery = imageryProviderViewModels[imageryProviderViewModels.length - 1];
//取消版权信息
viewer._cesiumWidget._creditContainer.style.display = "none"
//设置地球颜色
viewer.scene.globe.baseColor = Cesium.Color.fromCssColorString('#000d2d');
viewer.scene.globe.enableLighting = false; //光线
viewer.scene.globe.showGroundAtmosphere = true; //天气特效
viewer.scene.fxaa = false; //抗锯齿,建议关闭
viewer.scene.fog.enabled = false; //雾特效
viewer.scene.globe.depthTestAgainstTerrain = false; //开启地形深度检测
viewer.scene.debugShowFramesPerSecond = false; // 显示帧率
let scene = window.viewer.scene;
var token = '天地图token';
// 服务域名
var tdtUrl = 'https://t{s}.tianditu.gov.cn/';
// 服务负载子域
var subdomains = ['0', '1', '2', '3', '4', '5', '6', '7'];
var img_w = viewer.imageryLayers.addImageryProvider(
new Cesium.UrlTemplateImageryProvider({
url: tdtUrl + 'DataServer?T=img_w&x={x}&y={y}&l={z}&tk=' + token,
subdomains: subdomains,
tilingScheme: new Cesium.WebMercatorTilingScheme(),
maximumLevel: 18
})
);
img_w.show = true;
//初始位置设置
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(123.80411007, 31.6286, 12002180.3), //经度,纬度,视角高
});
// const terrain = new Cesium.Terrain(Cesium.CesiumTerrainProvider.fromUrl("http://data.marsgis.cn/terrain/"));
const terrain = new Cesium.Terrain(Cesium.CesiumTerrainProvider.fromUrl("自已的地形数据路径,也可以把上边的公用地形放开【但有时候会连接不上】"));
viewer.scene.setTerrain(terrain);
2.绘制淹没区域
let activeShapePoints = []
let floatingPoint = undefined
let activeShape = undefined
let handler = undefined
let isDraw = false;
let maxWaterHeight = 2000;
let minWaterHeight = 0;
let waterHeight = 0;
let waterPrimitive = undefined;
let tempEntities = [];
$("#huizhi").on('click', function() {
activeShapePoints = []
floatingPoint = undefined
activeShape = undefined
handler = undefinedisDraw = false;
maxWaterHeight = 2000;
minWaterHeight = 0;
waterHeight = 0;
waterPrimitive = undefined;
tempEntities = [];
// 开启深度检测
viewer.scene.globe.depthTestAgainstTerrain = true
handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas)
handler.setInputAction((event) => {
const earthPosition = viewer.scene.pickPosition(event.position);
if (Cesium.defined(earthPosition)) {
if (activeShapePoints.length === 0) {
floatingPoint = createPoint(earthPosition);
activeShapePoints.push(earthPosition);
const dynamicPositions = new Cesium.CallbackProperty(function() {
return new Cesium.PolygonHierarchy(activeShapePoints);
}, false);
activeShape = drawShape(dynamicPositions, Cesium.Color.fromBytes(64, 157, 253, 50));
}
activeShapePoints.push(earthPosition);
tempEntities.push(createPoint(earthPosition))
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
handler.setInputAction((event) => {
if (Cesium.defined(floatingPoint)) {
const newPosition = viewer.scene.pickPosition(event.endPosition);
if (Cesium.defined(newPosition)) {
floatingPoint.position.setValue(newPosition);
activeShapePoints.pop();
activeShapePoints.push(newPosition);
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
handler.setInputAction((event) => {
activeShapePoints.pop()
if (activeShapePoints.length < 3) return
tempEntities.push(drawPolyline(activeShapePoints))
let ploy = drawShape(activeShapePoints, Cesium.Color.fromBytes(64, 157, 253, 20))
tempEntities.push(ploy)
window.viewer.entities.remove(floatingPoint);
window.viewer.entities.remove(activeShape);
floatingPoint = undefined;
activeShape = undefined;
handler.destroy() // 关闭事件句柄
handler = null
induationAnalysis()
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
});
/**
* @author:
* @Date: 2024-09-06 16:48:43
* @note: 注意事项
* @description: 获取区域内最大最小高程
* @param {*} positions
*/
const getAreaHeight = async (positions) => {
let startP = positions[0]
let endP = positions[positions.length - 1]
if (startP.x != endP.x && startP.y != endP.y && startP.z != endP.z) positions.push(positions[0])
const tempPoints = []
for (let i = 0; i < positions.length; i++) {
var ellipsoid = window.viewer.scene.globe.ellipsoid
var cartographic = ellipsoid.cartesianToCartographic(positions[i])
var lat = Cesium.Math.toDegrees(cartographic.latitude)
var lng = Cesium.Math.toDegrees(cartographic.longitude)
tempPoints.push([lng, lat])
}
var line = turf.lineString(tempPoints)
var chunk = turf.lineChunk(line, 10, {
units: 'meters'
});
const tempArray = []
chunk.features.forEach(f => {
f.geometry.coordinates.forEach(c => {
tempArray.push(Cesium.Cartographic.fromDegrees(c[0], c[1]))
})
})
var promise = Cesium.sampleTerrainMostDetailed(window.viewer.terrainProvider, tempArray)
const updatedPositions = await Cesium.sampleTerrainMostDetailed(window.viewer.terrainProvider, tempArray) // 计算取样点的高度
let minHeight = 8888
let maxHeight = 0
// 计算取样点的最小高度和最大高度
for (let i = 0; i < updatedPositions.length; i++) {
const height = updatedPositions[i].height
console.log(height)
if (height < minHeight) {
minHeight = height
}
if (height > maxHeight) {
maxHeight = height
}
}
waterHeight = Math.ceil(minHeight)
minWaterHeight = Math.ceil(minHeight)
maxWaterHeight = Math.ceil(maxHeight)
// 禁用绘制按钮
isDraw = !isDraw
return {
minHeight,
maxHeight
}
}
/**
* @author:
* @Date: 2024-09-06 16:46:47
* @note: 注意事项
* @description: 创建点
* @param {*} worldPosition
*/
function createPoint(worldPosition) {
const point = window.viewer.entities.add({
position: worldPosition,
point: {
color: Cesium.Color.SKYBLUE,
pixelSize: 5,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
});
return point;
}
/**
* @author:
* @Date: 2024-09-06 16:46:47
* @note: 注意事项
* @description: 绘制多边形
* @param {*} positionData
* @param {*} mat
*/
function drawShape(positionData, mat) {
let shape = window.viewer.entities.add({
polygon: {
hierarchy: positionData,
material: mat,
outline: true,
outlineColor: Cesium.Color.SKYBLUE,
outlineWidth: 4,
}
});
return shape;
}
/**
* @author:
* @Date: 2024-09-06 16:46:11
* @note: 注意事项
* @description: 绘制线
* @param {*} positions
*/
function drawPolyline(positions) {
if (positions.length < 1) return
let startP = positions[0]
let endP = positions[positions.length - 1]
if (startP.x != endP.x && startP.y != endP.y && startP.z != endP.z) positions.push(positions[0])
return window.viewer.entities.add({
name: 'polyline',
polyline: {
positions: positions,
width: 2.0,
material: Cesium.Color.SKYBLUE,
clampToGround: true
}
})
}
3.淹没分析渲染
/**
* @author:
* @Date: 2024-09-06 16:45:05
* @note: 注意事项
* @description: 淹没分析
*/
var induationAnalysis = async () => {
await getAreaHeight(activeShapePoints)
let newData = [];
let positionsArr = [];
for (let i = 0; i < activeShapePoints.length; i++) {
if (i < activeShapePoints.length - 1) {
let cartographic = Cesium.Cartographic.fromCartesian(activeShapePoints[i]);
newData.push(parseFloat(Cesium.Math.toDegrees(cartographic.longitude).toFixed(6)), parseFloat(Cesium.Math.toDegrees(cartographic.latitude).toFixed(6)), 0)
if(i == 0)
{
positionsArr.push(new Cesium.Cartesian2(0, 1))
}else
{
if(i % 3 == 1)
{
positionsArr.push(new Cesium.Cartesian2(0, 0))
}else if(i % 3 ==2)
{
positionsArr.push(new Cesium.Cartesian2(1, 0))
}else if(i % 3 ==0)
{
positionsArr.push(new Cesium.Cartesian2(1, 1))
}
}
}
}
let textureCoordinates = {
positions: positionsArr,
};
console.log(newData)
let aper = new Cesium.MaterialAppearance({
material: '找网上大佬们好看的水面动态纹理,也可留言!!!',
});
//加载实体
let primitive = new WaterPrimitive({
aper: aper, //着色器
positions: newData, //范围
height: Number(minWaterHeight), //开始高度
textureCoordinates: textureCoordinates,
speedVal: 0.05, //速度
extrudedHeight: Number(minWaterHeight),
targetHeight: Number(maxWaterHeight), //最大高度
flag:true //true 涨水 false 降水
})
let primitArr = viewer.scene.primitives.add(primitive);
tempEntities.push(primitArr);
function renderLoop(timestamp){
aper.material.uniforms.iTime = timestamp/1000;
requestAnimationFrame(renderLoop);
}
renderLoop();
}
/**
* @author:
* @Date: 2024-09-06 16:44:42
* @note: 注意事项
* @description: 清除当前页面所有数据
*/
function clearAllEntities() {
positions = []
const length = tempEntities.length
for (let f = 0; f < length; f++) {
window.viewer.scene.primitives.remove(tempEntities[f])
}
tempEntities = []
activeShapePoints = [];
warningWaterHeight = 0
isDraw = !isDraw
floatingPoint = undefined
activeShape = undefined
if (handler) {
handler.destroy() // 关闭事件句柄
handler = undefined
}
}
最终效果:
cesium河流淹没效果(自定义动态水纹理)
完整代码链接:
添加链接描述
小弟不经常写文章写的不是很完美,有不足之处有多多指教。如需完整代码,请留言!!!