Cesium-鼠标传入着色器中并进行交互

Sandcastle | CesiumJS

以上连接可以直接打开sandcastle直接运行。

方法展示**"如何将鼠标点击的 3D 坐标转化为 2D UV 坐标,并传递给着色器(Shader)实现动态水波扩散/水源添加效果",基于Cesium 1.140 API。本示例采用 自定义材质(Fabric Material)+ GLSL 着色器 的方式,完美复刻了交互式 GPGPU 的核心数据流:鼠标捕获 →经纬度转 UV →JS 传递 Uniforms →着色器渲染水波。

以下是完整代码:

javascript 复制代码
import * as Cesium from "cesium";

const viewer = new Cesium.Viewer("cesiumContainer");

// 2. 定义我们的流体模拟/水面的经纬度边界
const bounds = {
    west: 113.0,
    south: 22.0,
    east: 113.1,
    north: 22.1
};
const rectangle = Cesium.Rectangle.fromDegrees(bounds.west, bounds.south, bounds.east, bounds.north);

// 3. 核心:基于 GLSL 编写的交互式着色器材质
const waterMaterial = new Cesium.Material({
    fabric: {
        type: 'WaterInteraction',
        uniforms: {
            u_clickUV: new Cesium.Cartesian2(-1.0, -1.0), // 鼠标所在的 UV 坐标
            u_radius: 0.0,                                // 水源扩散半径
            u_waterColor: new Cesium.Color(0.0, 0.4, 0.8, 0.6),  // 基础水面颜色
            u_sourceColor: new Cesium.Color(0.0, 1.0, 1.0, 1.0)  // 水源中心高亮颜色
        },
        source: `
            // 片元着色器 (Fragment Shader)
            czm_material czm_getMaterial(czm_materialInput materialInput) {
                czm_material material = czm_getDefaultMaterial(materialInput);
                
                // st 就是当前像素的 UV 坐标 (0.0~1.0)
                vec2 st = materialInput.st;

                // 1. 计算当前像素与鼠标点击中心点的距离
                float dist = distance(st, u_clickUV);
                
                // 2. 根据鼠标点击的状态 (u_radius) 计算影响范围
                float intensity = 0.0;
                if(dist < u_radius) {
                    // smoothstep 让中心最强,边缘平滑过渡到 0
                    intensity = smoothstep(u_radius, 0.0, dist);
                }

                // 3. 结合时间 (czm_frameNumber) 生成动态扩散的水波纹理理
                // sin 函数造波浪,乘上 intensity 让波浪只在鼠标点击的范围内显示
                float wave = sin(dist * 150.0 - czm_frameNumber * 0.2) * 0.5 + 0.5;
                intensity = intensity * wave;

                // 4. 将基础水色和水波颜色混合
                vec4 finalColor = mix(u_waterColor, u_sourceColor, intensity);

                material.diffuse = finalColor.rgb;
                material.alpha = finalColor.a;
                return material;
            }
        `
    }
});

// 4. 将水面几何体与着色器材质绑定到场景中
const waterPrimitive = viewer.scene.primitives.add(new Cesium.Primitive({
    geometryInstances: new Cesium.GeometryInstance({
        geometry: new Cesium.RectangleGeometry({
            rectangle: rectangle,
            vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT
        })
    }),
    appearance: new Cesium.EllipsoidSurfaceAppearance({
        material: waterMaterial
    })
}));

// ==========================================
// 5. JavaScript 事件交互:捕捉鼠标并传递数据到着色器
// ==========================================
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);

// 用于记录交互状态的 JS 变量
let isClicking = false;
let currentUV = new Cesium.Cartesian2(-1.0, -1.0);

// 提取公共方法:将屏幕坐标转为 UV
function updateUVFromPosition(position) {
    const ray = viewer.camera.getPickRay(position);
    const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
    
    if (cartesian) {
        // 3D 笛卡尔转经纬度 (弧度)
        const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
        const lon = Cesium.Math.toDegrees(cartographic.longitude);
        const lat = Cesium.Math.toDegrees(cartographic.latitude);
        
        // 关键算法:将经纬度映射到水面的 UV 坐标 (0.0 ~ 1.0)
        const u = (lon - bounds.west) / (bounds.east - bounds.west);
        const v = (lat - bounds.south) / (bounds.north - bounds.south);
        
        // 确保点击在水面范围内
        if (u >= 0.0 && u <= 1.0 && v >= 0.0 && v <= 1.0) {
            currentUV.x = u;
            currentUV.y = v;
            return true;
        }
    }
    return false;
}

// 鼠标左键按下:开始注水
handler.setInputAction(function(movement) {
    if(updateUVFromPosition(movement.position)) {
        isClicking = true;
    }
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);

// 鼠标移动(拖拽):移动水管位置
handler.setInputAction(function(movement) {
    if (!isClicking) return;
    updateUVFromPosition(movement.endPosition);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

// 鼠标左键抬起:停止注水
handler.setInputAction(function() {
    isClicking = false;
}, Cesium.ScreenSpaceEventType.LEFT_UP);

// ==========================================
// 6. 渲染循环回调:动态更新着色器变量 (Uniforms)
// ==========================================
let radius = 0.0;
viewer.scene.preUpdate.addEventListener(function() {
    const material = waterPrimitive.appearance.material;
    
    if (isClicking) {
        // 如果正在按住鼠标:慢慢扩大注水波纹半径(模拟水量累积),最大半径为 0.2 (UV单位)
        radius = Math.min(radius + 0.003, 0.2);
        
        // 将 JS 运算好的 UV 和 Radius 塞给 GLSL 的 Uniform 变量
        material.uniforms.u_clickUV = currentUV;
        material.uniforms.u_radius = radius;
    } else {
        // 松开鼠标后:波纹逐渐衰减消散
        radius = Math.max(radius - 0.005, 0.0);
        material.uniforms.u_radius = radius;
    }
});

// 7. 将相机飞到模拟水域的正上方
viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(113.05, 21.95, 12000.0),
    orientation: {
        heading: Cesium.Math.toRadians(0.0),
        pitch: Cesium.Math.toRadians(-60.0) // 俯视视角
    }
});
相关推荐
往上跑山1 小时前
指南Ignition动态执行前的事情
javascript
鱼羽生生1 小时前
Brainfly: 类型系统构建 Brainfuck 编译器
javascript
谷谷地图下载器1 小时前
全球、台湾省的无水印·街景数据(离线数据),专为可视化项目定制,支持国产化
javascript·c++·3d·arcgis·sqlite
万少2 小时前
如果你要自动化操作浏览器,Kimi-WebBridge可能适合你
前端·javascript·后端
韩曙亮2 小时前
【错误记录】flutter attach 附加设备 执行报错 ( 附加设备注意事项 )
android·javascript·flutter·flutter attach
gCode Teacher 格码致知2 小时前
Javascript提高:冒泡和捕获的典型案例-由Deepseek产生
前端·javascript
蒟蒻星球住民2 小时前
web应用技术作业01
前端·javascript·firefox
吃阿茶搽4 小时前
源码剖析:Standard组件架构与底层实现原理
javascript