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) // 俯视视角
    }
});
相关推荐
何时梦醒3 分钟前
深入理解递归与快速排序 —— 从基础入门到手写实现
前端·javascript
bonechips14 分钟前
LLM 的无状态:从 HTTP 协议到对话上下文工程
前端·javascript
胡志辉15 分钟前
从 prototype 到 V8,看懂 JavaScript 原型链
前端·javascript
ping某2 小时前
专栏-null 和 undefined 到底是什么?
前端·javascript·后端
swipe5 小时前
从 0 到 1 理解 React 虚拟列表:定高、不定高与 Canvas 版本完整拆解
前端·javascript·面试
铁皮饭盒5 小时前
Bun执行python代码
前端·javascript·后端
zzzzzz3107 小时前
当甲方说'logo放大的同时再缩小一点'时,我用 AI 把这个需求做出来了
javascript·css·程序员
Hilaku7 小时前
Node.js 还能再战十年?给你一个不换引擎的理由
前端·javascript·程序员
weedsfly8 小时前
前端必知必会:从 IIFE 到 ESM,模块化到底在解决什么?
前端·javascript
渣波8 小时前
拒绝 SQL 焦虑!手把手带你用 NestJS + Prisma + DTO 写出“防弹”级后端代码
javascript·数据库·后端