Cesium高级教程-后处理体积云-后处画一个Box

Cesium高级教程-后处理体积云-后处画一个Box

Box 盒子

前面我们说了,局部体积云一般是在一个Box内进行渲染,所以我们要先知道如何在后处理中绘制一个Box

在这里插入图片描述

首先我们回想一下使用Geometry绘制一个Box的需要哪些信息,一般需要知道Box坐标原点,然后是Box的大小信息(长、宽、高),我们先假定Box的坐标为(-75.59670696331766, 40.0387958759308, 90.62678445553416),长宽高都为20

let position=Cesium.Cartesian3.fromDegrees(-75.59670696331766, 40.0387958759308, 90.62678445553416);
let entity = new Cesium.Entity({
    position:position, 
    box:{
        dimensions: new Cesium.Cartesian3(20.0, 20.0, 20.0),
        material:Cesium.Color.BLUE,
    }
})
viewer.entities.add(entity);

在这里插入图片描述

后处理画一个 Box

在后处理中要绘制一个类型的Box,我的思路是这样的:首先以Box中心点建立一个局部坐标系,每个片元还原到世界坐标,然后转到这个局部坐标系,通过判断坐标值的大小就可以判断这个片元是否在该Box内,如果在Box内,就设置颜色为蓝色,在外不进行任何处理。

您也可以按照自己的思路先想一下是否有其他方式

我们按照思路编写代码,因为要转坐标系,首先我们通过Box的原点坐标建立一个局部坐标系

//矩阵
let transform = Cesium.Transforms.eastNorthUpToFixedFrame(position);
//逆矩阵
let inverse = Cesium.Matrix4.inverse(transform, new Cesium.Matrix4());

然后将次坐标系信息传入后处理着色器,着色器中每个片元坐标先还原为世界坐标,然后转到该坐标系下,最后进行坐标数值比较。

let shader=`
       uniform sampler2D colorTexture;
       uniform sampler2D depthTexture;
       in vec2 v_textureCoordinates;  
       uniform mat4 inverse; 
       void main(){
           out_FragColor = texture(colorTexture, v_textureCoordinates); 
           vec4 rawDepthColor = texture(czm_globeDepthTexture, v_textureCoordinates);
           float depth = czm_unpackDepth(rawDepthColor);
           if (depth == 0.0) {
             depth = 1.0;  
           }  
           vec4 eyeCoordinate4 = czm_windowToEyeCoordinates(gl_FragCoord.xy, depth); 
           vec3 eyeCoordinate3 = eyeCoordinate4.xyz/eyeCoordinate4.w; 
           vec4 worldCoordinate4 = czm_inverseView * vec4(eyeCoordinate3,1.) ;  
           ...
           if(local.x>-20.&&local.x<20.&&local.y>-20.&&local.y<20.&&local.z>-20.&&local.z<20.){
             out_FragColor=vec4(0.,0.,1.,1);
           }    
         }
 `; 
let stage = new Cesium.PostProcessStage({
  fragmentShader: shader,
  uniforms: { 
    inverse: inverse
  }
});

在代码中,我们将位于Box内的片元都设置为蓝色,运行代码看看结果

在这里插入图片描述

示例效果可到 xt3d 官网 运行查看

从运行结果中我们可以看到,虽然在Box范围内的片元被设置成了蓝色,但是并没有出现一个像上面那样立体Box,这是因为后处理就是一副画,这是一个二维的概念,所以肯定没有立体的效果。但是如果又想要立体的效果呢?是不是没有办法呢?您也可以先试想一下,先不着急看下面。

后处理立体Box

要实现立体效果,其实我们只需要将这个Box所遮蔽的片元设置为蓝色即可,这里就涉及到一个重要的知识,Box所遮蔽的片元如何求取?

在这里插入图片描述

求取方式为:相机到片元(世界坐标)的射线与Box如果有交点,那么该片元就被遮蔽,所以问题转为相机到片元的射线和Box求交,射线和Box求交有很多算法,这里采用AABB的方式,shader代码如下:

  //边界框最小值 边界框最大值         
 float2 rayBoxDst(float3 boundsMin, float3 boundsMax, 
                 //世界相机位置      光线方向倒数
                 float3 rayOrigin, float3 invRaydir) 
 {
     float3 t0 = (boundsMin - rayOrigin) * invRaydir;
     float3 t1 = (boundsMax - rayOrigin) * invRaydir;
     float3 tmin = min(t0, t1);
     float3 tmax = max(t0, t1);

     float dstA = max(max(tmin.x, tmin.y), tmin.z); //进入点
     float dstB = min(tmax.x, min(tmax.y, tmax.z)); //出去点

      ...
     return float2(dstToBox, dstInsideBox);
 }

如果 distA&&(distA<distB)则有交点,否则没有交点,加入代码测试

let shader=`
  uniform sampler2D colorTexture;
  uniform sampler2D depthTexture;
  in vec2 v_textureCoordinates;  
  uniform mat4 inverse; 
  vec4 rayBoxDst(vec3 boundsMin, vec3 boundsMax,  vec3 rayOrigin, vec3 invRaydir) 
  {
      vec3 t0 = (boundsMin - rayOrigin) * invRaydir;
      vec3 t1 = (boundsMax - rayOrigin) * invRaydir;
      vec3 tmin = min(t0, t1);
      vec3 tmax = max(t0, t1);
      float dstA = max(max(tmin.x, tmin.y), tmin.z); //进入点
      float dstB = min(tmax.x, min(tmax.y, tmax.z)); //出去点
       ...
      return vec4(dstToBox, dstInsideBox,dstA,dstB);
  } 
  void main(){
      out_FragColor = texture(colorTexture, v_textureCoordinates); 
      vec4 rawDepthColor = texture(czm_globeDepthTexture, v_textureCoordinates);
      float depth = czm_unpackDepth(rawDepthColor);
      if (depth == 0.0) {
        depth = 1.0;  
      }  
      vec4 eyeCoordinate4 = czm_windowToEyeCoordinates(gl_FragCoord.xy, depth); 
      vec3 eyeCoordinate3 = eyeCoordinate4.xyz/eyeCoordinate4.w; 
      vec4 worldCoordinate4 = czm_inverseView * vec4(eyeCoordinate3,1.) ;  
      vec3 worldCoordinate = worldCoordinate4.xyz / worldCoordinate4.w;  
      vec4 worldPos= inverse * vec4(worldCoordinate,1.);  
      vec4 cameraPos= inverse * vec4(czm_viewerPositionWC,1.);
      vec3 vDirection=worldPos.xyz-cameraPos.xyz;//方向
      vec3 rayDir = normalize( vDirection ); 
      vec3 dim= vec3(20.,20.,20.);//盒子长宽高
      vec3 box_min = vec3(0.) - dim / 2.;
      vec3 box_max = vec3(0.) + dim / 2.;
      vec4 bounds =rayBoxDst(box_min,box_max,cameraPos.xyz,1.0 / rayDir);
      ...; //盒子外   
      out_FragColor=vec4(0.,0.,1.,1.);
    }
`;

在这里插入图片描述

这样看起来Box就有立体感了

示例效果可到 xt3d 官网 运行查看

移动场景您会发现俩个问题,一是遮蔽关系,二是边界锯齿严重。遮蔽关系是因为我们没有进行任何处理,边界锯齿是因为数据精度问题,此时因为范围很小,所以会比较明显。

更多内容见 Cesium高级教程-教程简介

runtime-core.esm-bundler.js:51 [Vue warn]: Unhandled error during execution of setup function at <CesiumViewer> at <App> warn$1 @ runtime-core.esm-bundler.js:51 CesiumViewer.vue:80 Uncaught ReferenceError: throttle is not defined at setup (CesiumViewer.vue:80:24) at callWithErrorHandling (runtime-core.esm-bundler.js:199:19) at setupStatefulComponent (runtime-core.esm-bundler.js:7965:25) at setupComponent (runtime-core.esm-bundler.js:7926:36) at mountComponent (runtime-core.esm-bundler.js:5247:7) at processComponent (runtime-core.esm-bundler.js:5213:9) at patch (runtime-core.esm-bundler.js:4729:11) at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js:5358:11) at ReactiveEffect.run (reactivity.esm-bundler.js:237:19) at setupRenderEffect (runtime-core.esm-bundler.js:5486:5) 如图所示报错修改以下代码:<template> <div id="cesiumContainer" class="cesiumContainer"></div> <div ref="miniMapContainer" class="mini-map" @click="handleMiniMapClick"> <div class="location-indicator" :style="indicatorStyle"></div> <!-- 四个角折角 --> <div class="corner corner-top-left"></div> <div class="corner corner-top-right"></div> <div class="corner corner-bottom-left"></div> <div class="corner corner-bottom-right"></div> <div class="focus-effect"></div> <div class="pulse"></div> </div> </template> <script setup lang="ts"> import { onUnmounted, onMounted, ref } from "vue"; import * as Cesium from 'cesium'; // import { throttle } from "lodash"; const miniMapContainer = ref<HTMLElement>(); let viewer: Cesium.Viewer | null = null; let overviewViewer: Cesium.Viewer | null = null; // 株洲市范围常量 const ZHUZHOU_EXTENT = { west: 112.5, // 株洲市西边界 east: 114.5, // 株洲市东边界 south: 26.0, // 株洲市南边界 north: 28.0, // 株洲市北边界 }; // 视图指示器样式 const indicatorStyle = ref({ left: "50%", top: "50%", display: "block", }); // 更新指示器位置 const updateIndicatorPosition = () => { if (!viewer) return; const camera = viewer.camera; const rect = camera.computeViewRectangle(); if (!rect) return; // 计算在株洲市范围内的相对位置 const center = Cesium.Rectangle.center(rect); const lon = Cesium.Math.toDegrees(center.longitude); const lat = Cesium.Math.toDegrees(center.latitude); // 确保位置在株洲市范围内 const constrainedLon = Math.max( ZHUZHOU_EXTENT.west, Math.min(ZHUZHOU_EXTENT.east, lon) ); const constrainedLat = Math.max( ZHUZHOU_EXTENT.south, Math.min(ZHUZHOU_EXTENT.north, lat) ); const lonPercent = ((constrainedLon - ZHUZHOU_EXTENT.west) / (ZHUZHOU_EXTENT.east - ZHUZHOU_EXTENT.west)) * 100; const latPercent = 100 - ((constrainedLat - ZHUZHOU_EXTENT.south) / (ZHUZHOU_EXTENT.north - ZHUZHOU_EXTENT.south)) * 100; // 更新CSS指示器 indicatorStyle.value = { left: `${lonPercent}%`, top: `${latPercent}%`, display: "block", }; }; // 更新鹰眼地图 const updateOverview = throttle(() => { if (!viewer || !overviewViewer) return; updateIndicatorPosition(); }, 200); // 初始化主地图 const initMap = () => { Cesium.Ion.defaultAccessToken = "YOUR_CESIUM_ACCESS_TOKEN"; viewer = new Cesium.Viewer("cesiumContainer", { sceneMode: Cesium.SceneMode.SCENE3D, baseLayerPicker: false, geocoder: false, homeButton: false, timeline: false, navigationHelpButton: false, fullscreenButton: false, animation: false, infoBox: false, selectionIndicator: false }); // 加载株洲市影像图 const zhuzhouProvider = new Cesium.UrlTemplateImageryProvider({ url: "http://124.232.190.30:9000/proxy/pk1725866655224/map/zzzsyx_18/{z}/{x}/{y}.png", minimumLevel: 1, maximumLevel: 17, tilingScheme: new Cesium.WebMercatorTilingScheme(), }); viewer.imageryLayers.addImageryProvider(zhuzhouProvider); // 设置初始视图 viewer.camera.flyTo({ destination: Cesium.Rectangle.fromDegrees( ZHUZHOU_EXTENT.west, ZHUZHOU_EXTENT.south, ZHUZHOU_EXTENT.east, ZHUZHOU_EXTENT.north ), orientation: { heading: Cesium.Math.toRadians(0), pitch: Cesium.Math.toRadians(-45), roll: 0 } }); }; // 初始化鹰眼地图 const initMiniMap = () => { if (!miniMapContainer.value) return; // 创建全球底图提供器 const worldProvider = new Cesium.UrlTemplateImageryProvider({ url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", minimumLevel: 0, maximumLevel: 19, tilingScheme: new Cesium.WebMercatorTilingScheme(), }); // 鹰眼地图初始化 overviewViewer = new Cesium.Viewer(miniMapContainer.value, { sceneMode: Cesium.SceneMode.SCENE2D, baseLayerPicker: false, homeButton: false, timeline: false, navigationHelpButton: false, animation: false, scene3DOnly: true, selectionIndicator: false, infoBox: false, imageryProvider: worldProvider, terrainProvider: undefined, mapProjection: new Cesium.WebMercatorProjection(), skyBox: false, skyAtmosphere: false }); // 设置固定视野为株洲市范围 overviewViewer.camera.setView({ destination: Cesium.Rectangle.fromDegrees( ZHUZHOU_EXTENT.west, ZHUZHOU_EXTENT.south, ZHUZHOU_EXTENT.east, ZHUZHOU_EXTENT.north ), }); // 隐藏控件 const toolbar = overviewViewer.container.querySelector(".cesium-viewer-toolbar"); if (toolbar) toolbar.style.display = "none"; // 隐藏版权信息 overviewViewer.cesiumWidget.creditContainer.style.display = "none"; }; // 鹰眼地图点击理 const handleMiniMapClick = (event: MouseEvent) => { if (!miniMapContainer.value || !overviewViewer || !viewer) return; const rect = miniMapContainer.value.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; // 计算在固定范围内的相对位置 const xPercent = (x / rect.width) * 100; const yPercent = (y / rect.height) * 100; // 转换为株洲市范围内的经纬度 const lon = ZHUZHOU_EXTENT.west + (xPercent / 100) * (ZHUZHOU_EXTENT.east - ZHUZHOU_EXTENT.west); const lat = ZHUZHOU_EXTENT.north - (yPercent / 100) * (ZHUZHOU_EXTENT.north - ZHUZHOU_EXTENT.south); // 主地图飞向点击位置 viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(lon, lat, 1000000), }); }; onMounted(() => { initMap(); setTimeout(() => { initMiniMap(); // 监听主地图相机变化 if (viewer) { viewer.camera.changed.addEventListener(updateOverview); } // 初始更新 updateOverview(); }, 1000); }); onUnmounted(() => { if (viewer) { viewer.destroy(); } if (overviewViewer) { overviewViewer.destroy(); } }); </script> <style> /* 全局样式 */ html, body { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; } .cesiumContainer { width: 100%; height: 100%; position: absolute; top: 0; left: 0; } .mini-map { position: absolute; right: 20px; bottom: 20px; width: 200px; height: 150px; border: 2px solid #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); z-index: 999; cursor: pointer; overflow: hidden; } .location-indicator { position: absolute; width: 14px; height: 14px; background: #ff3e3e; border: 2px solid white; border-radius: 50%; transform: translate(-50%, -50%); box-shadow: 0 0 15px rgba(255, 62, 62, 1); z-index: 100; } /* 相机聚焦样式 - 四个角折角 */ .corner { position: absolute; width: 25px; height: 25px; z-index: 100; pointer-events: none; } .corner::before, .corner::after { content: ""; position: absolute; background: #00f2fe; box-shadow: 0 0 10px rgba(0, 242, 254, 0.8); } .corner-top-left { top: -2px; left: -2px; } .corner-top-left::before { top: 0; left: 0; width: 15px; height: 3px; } .corner-top-left::after { top: 0; left: 0; width: 3px; height: 15px; } .corner-top-right { top: -2px; right: -2px; } .corner-top-right::before { top: 0; right: 0; width: 15px; height: 3px; } .corner-top-right::after { top: 0; right: 0; width: 3px; height: 15px; } .corner-bottom-left { bottom: -2px; left: -2px; } .corner-bottom-left::before { bottom: 0; left: 0; width: 15px; height: 3px; } .corner-bottom-left::after { bottom: 0; left: 0; width: 3px; height: 15px; } .corner-bottom-right { bottom: -2px; right: -2px; } .corner-bottom-right::before { bottom: 0; right: 0; width: 15px; height: 3px; } .corner-bottom-right::after { bottom: 0; right: 0; width: 3px; height: 15px; } .focus-effect { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 98; border: 2px solid rgba(0, 242, 254, 0.2); border-radius: 5px; box-shadow: inset 0 0 30px rgba(0, 242, 254, 0.1); } .pulse { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 10px; height: 10px; border-radius: 50%; background: rgba(0, 242, 254, 0.7); box-shadow: 0 0 0 0 rgba(0, 242, 254, 0.7); animation: pulse 2s infinite; } @keyframes pulse { 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.7; } 70% { transform: translate(-50%, -50%) scale(1.5); opacity: 0; } 100% { transform: translate(-50%, -50%) scale(1); opacity: 0; } } /* 隐藏不需要的Cesium控件 */ .cesium-viewer-toolbar, .cesium-viewer-animationContainer, .cesium-viewer-timelineContainer, .cesium-viewer-bottom { display: none !important; } </style>
最新发布
07-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xt3d

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值