24.cesuim分析之可视域分析

文章详细描述了使用GLSL在Cesium环境中实现可视域分析的过程,包括阴影贴图的计算和视口内区域着色,以及ViewShedStage和ViewAnalysis类的创建与操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可视域示意图

在这里插入图片描述

glsl材质代码

export default `
  #define USE_CUBE_MAP_SHADOW true
  uniform sampler2D colorTexture;
  uniform sampler2D depthTexture;
  varying vec2 v_textureCoordinates;
  uniform mat4 camera_projection_matrix;
  uniform mat4 camera_view_matrix;
  uniform samplerCube shadowMap_textureCube;
  uniform mat4 shadowMap_matrix;
  uniform vec4 shadowMap_lightPositionEC;
  uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness;
  uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth;
  uniform float helsing_viewDistance; 
  uniform vec4 helsing_visibleAreaColor;
  uniform vec4 helsing_invisibleAreaColor;
 
  struct zx_shadowParameters
  {
      vec3 texCoords;
      float depthBias;
      float depth;
      float nDotL;
      vec2 texelStepSize;
      float normalShadingSmooth;
      float darkness;
  };
 
  float czm_shadowVisibility(samplerCube shadowMap, zx_shadowParameters shadowParameters)
  {
      float depthBias = shadowParameters.depthBias;
      float depth = shadowParameters.depth;
      float nDotL = shadowParameters.nDotL;
      float normalShadingSmooth = shadowParameters.normalShadingSmooth;
      float darkness = shadowParameters.darkness;
      vec3 uvw = shadowParameters.texCoords;
      depth -= depthBias;
      float visibility = czm_shadowDepthCompare(shadowMap, uvw, depth);
      return czm_private_shadowVisibility(visibility, nDotL, normalShadingSmooth, darkness);
  }
 
  vec4 getPositionEC(){
      return czm_windowToEyeCoordinates(gl_FragCoord);
  }
 
  vec3 getNormalEC(){
      return vec3(1.);
  }
 
  vec4 toEye(in vec2 uv,in float depth){
      vec2 xy=vec2((uv.x*2.-1.),(uv.y*2.-1.));
      vec4 posInCamera=czm_inverseProjection*vec4(xy,depth,1.);
      posInCamera=posInCamera/posInCamera.w;
      return posInCamera;
  }
 
  vec3 pointProjectOnPlane(in vec3 planeNormal,in vec3 planeOrigin,in vec3 point){
      vec3 v01=point-planeOrigin;
      float d=dot(planeNormal,v01);
      return(point-planeNormal*d);
  }
 
  float getDepth(in vec4 depth){
      float z_window=czm_unpackDepth(depth);
      z_window=czm_reverseLogDepth(z_window);
      float n_range=czm_depthRange.near;
      float f_range=czm_depthRange.far;
      return(2.*z_window-n_range-f_range)/(f_range-n_range);
  }
 
  float shadow(in vec4 positionEC){
      vec3 normalEC=getNormalEC();
      zx_shadowParameters shadowParameters;
      shadowParameters.texelStepSize=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy;
      shadowParameters.depthBias=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z;
      shadowParameters.normalShadingSmooth=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w;
      shadowParameters.darkness=shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w;
      vec3 directionEC=positionEC.xyz-shadowMap_lightPositionEC.xyz;
      float distance=length(directionEC);
      directionEC=normalize(directionEC);
      float radius=shadowMap_lightPositionEC.w;
      if(distance>radius)
      {
          return 2.0;
      }
      vec3 directionWC=czm_inverseViewRotation*directionEC;
      shadowParameters.depth=distance/radius-0.0003;
      shadowParameters.nDotL=clamp(dot(normalEC,-directionEC),0.,1.);
      shadowParameters.texCoords=directionWC;
      float visibility=czm_shadowVisibility(shadowMap_textureCube,shadowParameters);
      return visibility;
  }
 
  bool visible(in vec4 result)
  {
      result.x/=result.w;
      result.y/=result.w;
      result.z/=result.w;
      return result.x>=-1.&&result.x<=1.
      &&result.y>=-1.&&result.y<=1.
      &&result.z>=-1.&&result.z<=1.;
  }
 
  void main(){
      // 釉色 = 结构二维(颜色纹理, 纹理坐标)
      gl_FragColor = texture2D(colorTexture, v_textureCoordinates);
      // 深度 = 获取深度(结构二维(深度纹理, 纹理坐标))
      float depth = getDepth(texture2D(depthTexture, v_textureCoordinates));
      // 视角 = (纹理坐标, 深度)
      vec4 viewPos = toEye(v_textureCoordinates, depth);
      // 世界坐标
      vec4 wordPos = czm_inverseView * viewPos;
      // 虚拟相机中坐标
      vec4 vcPos = camera_view_matrix * wordPos;
      float near = .001 * helsing_viewDistance;
      float dis = length(vcPos.xyz);
      if(dis > near && dis < helsing_viewDistance){
          // 透视投影
          vec4 posInEye = camera_projection_matrix * vcPos;
          // 可视区颜色
          // vec4 helsing_visibleAreaColor=vec4(0.,1.,0.,.5);
          // vec4 helsing_invisibleAreaColor=vec4(1.,0.,0.,.5);
          if(visible(posInEye)){
              float vis = shadow(viewPos);
              if(vis > 0.3){
                  gl_FragColor = mix(gl_FragColor,helsing_visibleAreaColor,.5);
              } else{
                  gl_FragColor = mix(gl_FragColor,helsing_invisibleAreaColor,.5);
              }
          }
      }
  }`;

可视域生成实体类ViewShedStage.js

import glsl from "./glsl";

/**
* @Author:
* @Date:
* 可视域分析。
*
* @author Helsing
* @date 2022/10/07
* @alias ViewShedStage
* @class
 * @param {Cesium.Viewer} viewer Cesium三维视窗。
 * @param {Object} options 选项。
 * @param {Cesium.Cartesian3} options.viewPosition 观测点位置。
 * @param {Cesium.Cartesian3} options.viewPositionEnd 最远观测点位置(如果设置了观测距离,这个属性可以不设置)。
 * @param {Number} options.viewDistance 观测距离(单位`米`,默认值100)。
 * @param {Number} options.viewHeading 航向角(单位`度`,默认值0)。
 * @param {Number} options.viewPitch 俯仰角(单位`度`,默认值0)。
 * @param {Number} options.horizontalViewAngle 可视域水平夹角(单位`度`,默认值90)。
 * @param {Number} options.verticalViewAngle 可视域垂直夹角(单位`度`,默认值60)。
 * @param {Cesium.Color} options.visibleAreaColor 可视区域颜色(默认值`绿色`)。
 * @param {Cesium.Color} options.invisibleAreaColor 不可视区域颜色(默认值`红色`)。
 * @param {Boolean} options.enabled 阴影贴图是否可用。
 * @param {Boolean} options.softShadows 是否启用柔和阴影。
 * @param {Boolean} options.size 每个阴影贴图的大小。
 */
export default class ViewShedStage {

    constructor(viewer, options) {
        this.viewer = viewer;
        this.viewPosition = options.viewPosition;
        this.viewPositionEnd = options.viewPositionEnd;
        this.viewDistance = this.viewPositionEnd ? Cesium.Cartesian3.distance(this.viewPosition, this.viewPositionEnd) : (options.viewDistance || 100.0);
        this.viewHeading = this.viewPositionEnd ? this.getHeading(this.viewPosition, this.viewPositionEnd) : (options.viewHeading || 0.0);
        this.viewPitch = this.viewPositionEnd ? this.getPitch(this.viewPosition, this.viewPositionEnd) : (options.viewPitch || 0.0);
        this.horizontalViewAngle = options.horizontalViewAngle || 90.0;
        this.verticalViewAngle = options.verticalViewAngle || 60.0;
        this.visibleAreaColor = options.visibleAreaColor || Cesium.Color.GREEN;
        this.invisibleAreaColor = options.invisibleAreaColor || Cesium.Color.RED;
        this.enabled = (typeof options.enabled === "boolean") ? options.enabled : true;
        this.softShadows = (typeof options.softShadows === "boolean") ? options.softShadows : true;
        this.size = options.size || 2048;

        this.update();
    }

    add() {
        this.createLightCamera();
        this.createShadowMap();
        this.createPostStage();
        // this.drawFrustumOutline();
        this.drawSketch();
    }

    update() {
        this.clear();
        this.add();
    }
    /**
     * @method 更新终点坐标,从而实时更新绘制的实体的方向和半径
     *  
     */
    updatePosition(viewPositionEnd) {
        this.viewPositionEnd = viewPositionEnd
        this.viewDistance = Cesium.Cartesian3.distance(this.viewPosition, this.viewPositionEnd) //观测距离
        this.viewHeading = this.getHeading(this.viewPosition, this.viewPositionEnd)
        this.viewPitch = this.getPitch(this.viewPosition, this.viewPositionEnd)
    }

    clear() {
        if (this.sketch) {
            this.viewer.entities.removeById(this.sketch.id);
            this.sketch = null;
        }
        // if (this.frustumOutline) {
        //     this.frustumOutline.destroy();
        //     this.frustumOutline = null;
        // }
        if (this.postStage) {
            this.viewer.scene.postProcessStages.remove(this.postStage);
            this.postStage = null;
        }
    }
    createLightCamera() {
        this.lightCamera = new Cesium.Camera(this.viewer.scene);
        this.lightCamera.position = this.viewPosition;
        // if (this.viewPositionEnd) {
        //     let direction = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(this.viewPositionEnd, this.viewPosition, new Cesium.Cartesian3()), new Cesium.Cartesian3());
        //     this.lightCamera.direction = direction; // direction是相机面向的方向
        // }
        if(this.viewDistance==0){
            this.viewDistance=1
        }
        this.lightCamera.frustum.near = this.viewDistance * 0.001;
        this.lightCamera.frustum.far = this.viewDistance;
        const hr = Cesium.Math.toRadians(this.horizontalViewAngle);
        const vr = Cesium.Math.toRadians(this.verticalViewAngle);
        const aspectRatio =
            (this.viewDistance * Math.tan(hr / 2) * 2) /
            (this.viewDistance * Math.tan(vr / 2) * 2);
        this.lightCamera.frustum.aspectRatio = aspectRatio;
        if (hr > vr) {
            this.lightCamera.frustum.fov = hr;
        } else {
            this.lightCamera.frustum.fov = vr;
        }
        this.lightCamera.setView({
            destination: this.viewPosition,
            orientation: {
                heading: Cesium.Math.toRadians(this.viewHeading || 0),
                pitch: Cesium.Math.toRadians(this.viewPitch || 0),
                roll: 0
            }
        });
    }
    createShadowMap() {
        this.shadowMap = new Cesium.ShadowMap({
            context: (this.viewer.scene).context,
            lightCamera: this.lightCamera,
            enabled: this.enabled,
            isPointLight: true,
            pointLightRadius: this.viewDistance,
            cascadesEnabled: false,
            size: this.size,
            softShadows: this.softShadows,
            normalOffset: false,
            fromLightSource: false
        });
        this.viewer.scene.shadowMap = this.shadowMap;
    }
    createPostStage() {
        const fs = glsl
        const postStage = new Cesium.PostProcessStage({
            fragmentShader: fs,
            uniforms: {
                shadowMap_textureCube: () => {
                    this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
                    return Reflect.get(this.shadowMap, "_shadowMapTexture");
                },
                shadowMap_matrix: () => {
                    this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
                    return Reflect.get(this.shadowMap, "_shadowMapMatrix");
                },
                shadowMap_lightPositionEC: () => {
                    this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
                    return Reflect.get(this.shadowMap, "_lightPositionEC");
                },
                shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness: () => {
                    this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
                    const bias = this.shadowMap._pointBias;
                    return Cesium.Cartesian4.fromElements(
                        bias.normalOffsetScale,
                        this.shadowMap._distance,
                        this.shadowMap.maximumDistance,
                        0.0,
                        new Cesium.Cartesian4()
                    );
                },
                shadowMap_texelSizeDepthBiasAndNormalShadingSmooth: () => {
                    this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
                    const bias = this.shadowMap._pointBias;
                    const scratchTexelStepSize = new Cesium.Cartesian2();
                    const texelStepSize = scratchTexelStepSize;
                    texelStepSize.x = 1.0 / this.shadowMap._textureSize.x;
                    texelStepSize.y = 1.0 / this.shadowMap._textureSize.y;

                    return Cesium.Cartesian4.fromElements(
                        texelStepSize.x,
                        texelStepSize.y,
                        bias.depthBias,
                        bias.normalShadingSmooth,
                        new Cesium.Cartesian4()
                    );
                },
                camera_projection_matrix: this.lightCamera.frustum.projectionMatrix,
                camera_view_matrix: this.lightCamera.viewMatrix,
                helsing_viewDistance: () => {
                    return this.viewDistance;
                },
                helsing_visibleAreaColor: this.visibleAreaColor,
                helsing_invisibleAreaColor: this.invisibleAreaColor,
            }
        });
        this.postStage = this.viewer.scene.postProcessStages.add(postStage);
    }
    drawFrustumOutline() {
        const scratchRight = new Cesium.Cartesian3();
        const scratchRotation = new Cesium.Matrix3();
        const scratchOrientation = new Cesium.Quaternion();
        const position = this.lightCamera.positionWC;
        const direction = this.lightCamera.directionWC;
        const up = this.lightCamera.upWC;
        let right = this.lightCamera.rightWC;
        right = Cesium.Cartesian3.negate(right, scratchRight);
        let rotation = scratchRotation;
        Cesium.Matrix3.setColumn(rotation, 0, right, rotation);
        Cesium.Matrix3.setColumn(rotation, 1, up, rotation);
        Cesium.Matrix3.setColumn(rotation, 2, direction, rotation);
        let orientation = Cesium.Quaternion.fromRotationMatrix(rotation, scratchOrientation);

        let instance = new Cesium.GeometryInstance({
            geometry: new Cesium.FrustumOutlineGeometry({
                frustum: this.lightCamera.frustum,
                origin: this.viewPosition,
                orientation: orientation
            }),
            id: Math.random().toString(36).substr(2),
            attributes: {
                color: Cesium.ColorGeometryInstanceAttribute.fromColor(
                    Cesium.Color.YELLOWGREEN//new Cesium.Color(0.0, 1.0, 0.0, 1.0)
                ),
                show: new Cesium.ShowGeometryInstanceAttribute(true)
            }
        });

        this.frustumOutline = new Cesium.Primitive({
            geometryInstances: [instance],
            appearance: new Cesium.PerInstanceColorAppearance({
                flat: true,
                translucent: false
            })
        })
         this.viewer.scene.primitives.add(this.frustumOutline);
    }

    drawSketch() {
        this.sketch = this.viewer.entities.add({
            name: 'sketch',
            position: this.viewPosition,
            orientation: Cesium.Transforms.headingPitchRollQuaternion(
                this.viewPosition,
                Cesium.HeadingPitchRoll.fromDegrees(this.viewHeading - this.horizontalViewAngle, this.viewPitch, 0.0)
            ),
            ellipsoid: {
                radii: new Cesium.Cartesian3(
                    this.viewDistance,
                    this.viewDistance,
                    this.viewDistance
                ),
                // innerRadii: new Cesium.Cartesian3(2.0, 2.0, 2.0),
                minimumClock: Cesium.Math.toRadians(-this.horizontalViewAngle / 2),
                maximumClock: Cesium.Math.toRadians(this.horizontalViewAngle / 2),
                minimumCone: Cesium.Math.toRadians(this.verticalViewAngle + 7.75),
                maximumCone: Cesium.Math.toRadians(180 - this.verticalViewAngle - 7.75),
                fill: false,
                outline: true,
                subdivisions: 256,
                stackPartitions: 64,
                slicePartitions: 64,
                outlineColor: Cesium.Color.YELLOWGREEN
            }
        });
    }
     getHeading(fromPosition, toPosition) {
        let finalPosition = new Cesium.Cartesian3();
        let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition);
        Cesium.Matrix4.inverse(matrix4, matrix4);
        Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
        Cesium.Cartesian3.normalize(finalPosition, finalPosition);
        return Cesium.Math.toDegrees(Math.atan2(finalPosition.x, finalPosition.y));
    }
    
    getPitch(fromPosition, toPosition) {
        let finalPosition = new Cesium.Cartesian3();
        let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition);
        Cesium.Matrix4.inverse(matrix4, matrix4);
        Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
        Cesium.Cartesian3.normalize(finalPosition, finalPosition);
        return Cesium.Math.toDegrees(Math.asin(finalPosition.z));
    }


}

范围绘制及生成可视域类ViewAnalysis.js


import ViewShedStage from "./ViewShedStage";
let viewer=null
let viewShed = {}
let startPosition=null     //起始坐标
let endPosition= null       //终点坐标
let viewShedArr= []        //存储可视域区域的数组
let horizontalViewAngle = 90  //视角水平张角
let verticalViewAngle = 60    //视角垂直张角
let handler=null
export const ViewAnalysis = {
    start: (_viewer) => {
        viewer=_viewer
        let i = 0
        handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
        handler.setInputAction(movement =>
        {
          i++
          if(i === 1)
          {
             startPosition = viewer.scene.pickPosition(movement.position) //鼠标点击一次获取开始坐标
            if(!startPosition) return
            viewShed = new ViewShedStage(viewer._delegate,{
              viewPosition: startPosition,
              viewPositionEnd: startPosition,
              horizontalViewAngle: horizontalViewAngle,
              verticalViewAngle: verticalViewAngle
            })
            handler.setInputAction(movement => //鼠标移动的事件
              {
                endPosition =  viewer.scene.pickPosition(movement.endPosition)
                if(!endPosition) return
                viewShed.updatePosition(endPosition)
                if(!viewShed.sketch) {
                  viewShed.drawSketch()
                }
              }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)         
          }
          if(i === 2) //鼠标点击两次获取结束坐标
          { 
            i = 0
            endPosition =  viewer.scene.pickPosition(movement.position)
            viewShed.updatePosition(endPosition)
            viewShed.update()
            handler = handler && handler.destroy()  //销毁鼠标事件
            viewShedArr.push(viewShed)
          } 
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
    },
    stop: () => {
        if(viewShed){
            viewShed.clear()
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

紫雪giser

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

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

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

打赏作者

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

抵扣说明:

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

余额充值