/* * @Author : yuan * @Date : 2025-08-16 17:01:46 * @LastEditors : yuan * @LastEditTime : 2025-09-26 16:27:20 * @FilePath : \src\utils\cesium\frustum\CesiumVideoFrustum.js * @Description : * Copyright 2025 OBKoro1, All Rights Reserved. * 2025-08-16 17:01:46 */ import * as Cesium from 'cesium' var videoShed3dShader = ` uniform float ztzf_frustum_opacity; uniform sampler2D ztzf_frustum_videoTexture; uniform sampler2D ztzf_frustum_maskTexture; uniform vec4 ztzf_frustum_hiddenAreaColor; uniform sampler2D shadowMap_texture; uniform mat4 shadowMap_matrix; uniform vec4 shadowMap_lightPositionEC; uniform vec4 shadowMap_texelSizeDepthBias; uniform vec4 shadowMap_normalOffsetScale; uniform bool ztzf_frustum_flipx; uniform bool ztzf_frustum_flipy; // 新增边框参数 uniform vec4 ztzf_frustum_borderColor; // 边框颜色(RGBA) uniform float ztzf_frustum_borderWidth; // 边框宽度(0.0-1.0) uniform sampler2D colorTexture; uniform sampler2D depthTexture; in vec2 v_textureCoordinates; vec4 toEye(in vec2 uv, in float depth) { vec2 xy = vec2((uv.x * 2.0 - 1.0), (uv.y * 2.0 - 1.0)); vec4 posInCamera = czm_inverseProjection * vec4(xy, depth, 1.0); posInCamera = posInCamera / posInCamera.w; return posInCamera; } float getDepthMars3D(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.0 * z_window - n_range - f_range) / (f_range - n_range); } float _czm_sampleShadowMap(sampler2D shadowMap, vec2 uv) { return texture(shadowMap, uv).r; } float _czm_shadowDepthCompare(sampler2D shadowMap, vec2 uv, float depth) { return step(depth, _czm_sampleShadowMap(shadowMap, uv)); } float _czm_shadowVisibility(sampler2D shadowMap, czm_shadowParameters shadowParameters) { float depthBias = shadowParameters.depthBias; float depth = shadowParameters.depth; float nDotL = shadowParameters.nDotL; float normalShadingSmooth = shadowParameters.normalShadingSmooth; float darkness = shadowParameters.darkness; vec2 uv = shadowParameters.texCoords; depth -= depthBias; vec2 texelStepSize = shadowParameters.texelStepSize; float radius = 1.0; float dx0 = -texelStepSize.x * radius; float dy0 = -texelStepSize.y * radius; float dx1 = texelStepSize.x * radius; float dy1 = texelStepSize.y * radius; float visibility = (_czm_shadowDepthCompare(shadowMap, uv, depth) + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, dy0), depth) + _czm_shadowDepthCompare(shadowMap, uv + vec2(0.0, dy0), depth) + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, dy0), depth) + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, 0.0), depth) + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, 0.0), depth) + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, dy1), depth) + _czm_shadowDepthCompare(shadowMap, uv + vec2(0.0, dy1), depth) + _czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, dy1), depth)) * (1.0 / 9.0); return visibility; } 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 ptm(vec3 pt) { return sqrt(pt.x * pt.x + pt.y * pt.y + pt.z * pt.z); } void main() { const float PI = 3.141592653589793; vec4 color = texture(colorTexture, v_textureCoordinates); vec4 currD = texture(depthTexture, v_textureCoordinates); float depth = getDepthMars3D(currD); vec4 positionEC = toEye(v_textureCoordinates, depth); vec3 normalEC = vec3(1.0); czm_shadowParameters shadowParameters; shadowParameters.texelStepSize = shadowMap_texelSizeDepthBias.xy; shadowParameters.depthBias = shadowMap_texelSizeDepthBias.z; shadowParameters.normalShadingSmooth = shadowMap_texelSizeDepthBias.w; shadowParameters.darkness = shadowMap_normalOffsetScale.w; shadowParameters.depthBias *= max(depth * 0.01, 1.0); vec3 directionEC = normalize(positionEC.xyz - shadowMap_lightPositionEC.xyz); float nDotL = clamp(dot(normalEC, -directionEC), 0.0, 1.0); vec4 shadowPosition = shadowMap_matrix * positionEC; shadowPosition /= shadowPosition.w; if(any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))) { out_FragColor = color; return; } shadowParameters.texCoords = shadowPosition.xy; shadowParameters.depth = shadowPosition.z; shadowParameters.nDotL = nDotL; float visibility = _czm_shadowVisibility(shadowMap_texture, shadowParameters); //视频投射 if(visibility == 1.0) { if(ztzf_frustum_flipx){ shadowPosition.x = shadowPosition.x + (0.5 - shadowPosition.x) * 2.0; } if(ztzf_frustum_flipy){ shadowPosition.y = shadowPosition.y + (0.5 - shadowPosition.y) * 2.0; } vec4 videoColor = texture(ztzf_frustum_videoTexture, shadowPosition.xy); vec4 maskColor = texture(ztzf_frustum_maskTexture, shadowPosition.xy); videoColor *= maskColor; // 计算到边缘的距离 float distToEdge = min( min(shadowPosition.x, 1.0 - shadowPosition.x), min(shadowPosition.y, 1.0 - shadowPosition.y) ); // 混合视频颜色和背景 vec4 finalColor = mix(color, vec4(videoColor.xyz, 1.0), ztzf_frustum_opacity * videoColor.a); // 添加边框效果 if(distToEdge < ztzf_frustum_borderWidth && videoColor.a > 0.0) { float borderFactor = smoothstep(0.0, ztzf_frustum_borderWidth, distToEdge); finalColor = mix(ztzf_frustum_borderColor, finalColor, borderFactor); } out_FragColor = finalColor; } else { if(abs(shadowPosition.z - 0.0) < 0.01) { return; } out_FragColor = vec4(mix(color.rgb, ztzf_frustum_hiddenAreaColor.rgb, ztzf_frustum_hiddenAreaColor.a), ztzf_frustum_hiddenAreaColor.a); } } ` class CesiumVideoFrustum { constructor(viewer, param) { this.param = param var option = this._initCameraParam() if (option || (option = {}), this.viewer = viewer, this._cameraPosition = option.cameraPosition, this._position = option.position, this.type = option.type, this._alpha = option.alpha || 0, this.element = option.element, this.color = option.color, this._debugFrustum = Cesium.defaultValue(option.debugFrustum, !0), this._aspectRatio = option.aspectRatio || this._getWinWidHei(), this._camerafov = option.fov || Cesium.Math.toDegrees(this.viewer.scene.camera.frustum.fov), this._isShadowMap = option.isShadowMap || false, this.texture = option.texture || new Cesium.Texture({ context: this.viewer.scene.context, source: { width: 1, height: 1, arrayBufferView: new Uint8Array([255, 255, 255, 255]) }, flipY: !1 }), this.defaultShow = Cesium.defaultValue(option.show, !0), !this.cameraPosition || !this.position) return void console.log('初始化失败:请确认相机位置与视点位置正确!') this.activeVideo() this._createShadowMap() this._getOrientation() this._addCameraFrustum() this._addPostProcess() this.viewer.scene.primitives.add(this) } get alpha () { return this._alpha } set alpha (e) { this._alpha = e this._changeViewPos() } get aspectRatio () { return this._aspectRatio } set aspectRatio (e) { this._aspectRatio = e this._changeVideoWidHei() } get debugFrustum () { return this._debugFrustum } set debugFrustum (e) { this._debugFrustum = e this.cameraFrustum.show = e this.cameraFrustumOutline.show = e } get fov () { return this._camerafov } set fov (e) { this._camerafov = e this._changeCameraFov() } get cameraPosition () { return this._cameraPosition } set cameraPosition (e) { e && (this._cameraPosition = e, this._changeCameraPos()) } get isShadowMap () { return this._isShadowMap } set isShadowMap (e) { this._isShadowMap = e this._changeCameraIsShadowMap() } get position () { return this._position } set position (e) { e && (this._position = e, this._changeViewPos()) } get params () { var t = {} return t.element = this.element, t.position = this.position, t.cameraPosition = this.cameraPosition, t.fov = this.fov, t.isShadowMap = this.isShadowMap, t.aspectRatio = this.aspectRatio, t.alpha = this.alpha, t.debugFrustum = this.debugFrustum, t.show = this.show } get show () { return this.defaultShow } set show (e) { this.defaultShow = Boolean(e), this._switchShow() } isDestroyed () { return false } _initCameraParam () { var cameraPosition = Cesium.Cartesian3.fromDegrees(this.param.position.x * 1, this.param.position.y * 1, this.param.position.z * 1) const hpr = Cesium.HeadingPitchRoll.fromDegrees(this.param.rotation.y * 1, this.param.rotation.x * 1, 0) const orientation = Cesium.Transforms.headingPitchRollQuaternion(cameraPosition, hpr) let matrix = Cesium.Matrix3.fromQuaternion( orientation, new Cesium.Matrix3() ) let direction = Cesium.Matrix3.getColumn(matrix, 2, new Cesium.Cartesian3()) let frontDirect = Cesium.Cartesian3.multiplyByScalar( direction, 250, new Cesium.Cartesian3() ) let position = Cesium.Cartesian3.add(cameraPosition, frontDirect, new Cesium.Cartesian3()) return { cameraPosition: cameraPosition, position: position, alpha: this.param.alpha, near: this.param.near, fov: this.param.fov, isShadowMap: this.param.isShadowMap, debugFrustum: this.param.debugFrustum, aspectRatio: this.param.aspectRatio, element: this.param.element, } } _changeAlpha (e) { this.alpha = e } _changeAspectRatio (e) { this.aspectRatio = e } _changeRotation (e) { if (e) { this.param.rotation = e var option = this._initCameraParam() this.position = option.position } } _changeCameraPosition (e) { if (e) { this.param.position = e var option = this._initCameraParam() this.cameraPosition = option.cameraPosition } } _changeFov (e) { if (e) { this.param.fov = e var option = this._initCameraParam() this.fov = option.fov } } _changeIsShadowMap (e) { this.param.isShadowMap = e var option = this._initCameraParam() this.isShadowMap = option.isShadowMap } _changeFar (e) { if (e) { this.param.far = e var option = this._initCameraParam() this.position = option.position } } _changeNear (e) { if (e) { this.param.near = e this.near = this.param.near this._changeCameraPos() } } _getWinWidHei () { var viewer = this.viewer.scene return viewer.canvas.clientWidth / viewer.canvas.clientHeight } _changeCameraFov () { this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess) this.viewer.scene.primitives.remove(this.cameraFrustum), this.viewer.scene.primitives.remove(this.cameraFrustumOutline), this._createShadowMap(this.cameraPosition, this.position), this._getOrientation(), this._addCameraFrustum(), this._addPostProcess() } _changeCameraIsShadowMap () { this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess) this.viewShadowMap && this.viewShadowMap.destroy() this.viewShadowMap = null this.viewer.scene.primitives.remove(this.cameraFrustum), this.viewer.scene.primitives.remove(this.cameraFrustumOutline), this._createShadowMap(this.cameraPosition, this.position), this._getOrientation(), this._addCameraFrustum(), this._addPostProcess() } _changeVideoWidHei () { this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess) this.viewer.scene.primitives.remove(this.cameraFrustum), this.viewer.scene.primitives.remove(this.cameraFrustumOutline), this._createShadowMap(this.cameraPosition, this.position), this._getOrientation(), this._addCameraFrustum(), this._addPostProcess() } _changeCameraPos () { this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess) this.viewShadowMap && this.viewShadowMap.destroy() this.viewShadowMap = null this.viewer.scene.primitives.remove(this.cameraFrustum), this.viewer.scene.primitives.remove(this.cameraFrustumOutline), // this.cameraFrustum.destroy(), this._createShadowMap(this.cameraPosition, this.position), this._getOrientation(), this._addCameraFrustum(), this._addPostProcess() } _changeViewPos () { this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess) this.viewShadowMap && this.viewShadowMap.destroy() this.viewShadowMap = null this.viewer.scene.primitives.remove(this.cameraFrustum), this.viewer.scene.primitives.remove(this.cameraFrustumOutline), // this.cameraFrustum.destroy(), this._createShadowMap(this.cameraPosition, this.position), this._getOrientation(), this._addCameraFrustum(), this._addPostProcess() } _switchShow () { this.show ? !this.postProcess && this._addPostProcess() : (this.postProcess && this.viewer.scene.postProcessStages.remove(this.postProcess), delete this.postProcess, this.postProcess = null), this.cameraFrustum.show = this.show, this.cameraFrustumOutline.show = this.show } activeVideo () { var video = this.element, that = this if (video) { var viewer = this.viewer this.activeVideoListener || (this.activeVideoListener = function () { that.videoTexture && that.videoTexture.destroy(), that.videoTexture = new Cesium.Texture({ context: viewer.scene.context, source: video, width: 1, height: 1, pixelFormat: Cesium.PixelFormat.RGBA, pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE }) }), viewer.clock.onTick.addEventListener(this.activeVideoListener) } } locate () { var cameraPosition = Cesium.clone(this.cameraPosition), position = Cesium.clone(this.position) this.viewer.Camera.position = cameraPosition, this.viewer.camera.direction = Cesium.Cartesian3.subtract(position, cameraPosition, new Cesium.Cartesian3(0, 0, 0)), this.viewer.camera.up = Cesium.Cartesian3.normalize(cameraPosition, new Cesium.Cartesian3(0, 0, 0)) } update (e) { this.viewShadowMap && this.viewer.scene.frameState.shadowMaps.push(this.viewShadowMap) // *重点* 多投影 } _createShadowMap () { var e = Cesium.Cartesian3.fromDegrees(this.param.position.x * 1, this.param.position.y * 1, this.param.position.z * 1) var t = this.position var i = this.viewer.scene var a = new Cesium.Camera(i) a.position = e this.customFrustum = new Cesium.PerspectiveFrustum({ fov: Cesium.Math.toRadians(this.fov), aspectRatio: this.aspectRatio, near: this.near, far: 250 }) a.frustum = new Cesium.PerspectiveFrustum({ fov: Cesium.Math.toRadians(this.fov), aspectRatio: this.aspectRatio, near: this.near, far: 250 }) let curOrientation = {} const headingPitchRoll = new Cesium.HeadingPitchRoll( Cesium.Math.toRadians(this.param.rotation.y, 0), Cesium.Math.toRadians(this.param.rotation.x, 0), Cesium.Math.toRadians(0), ) curOrientation['heading'] = headingPitchRoll['heading'] curOrientation['pitch'] = headingPitchRoll['pitch'] curOrientation['roll'] = headingPitchRoll['roll'] let curDestination = {} curDestination.destination = e curDestination.orientation = curOrientation a.setView(curDestination) var n = Cesium.Cartesian3.distance(t, e) this.viewDis = n if (!this.isShadowMap) return this.viewShadowMap = new Cesium.ShadowMap({ lightCamera: a, enable: !1, isPointLight: !1, isSpotLight: !0, cascadesEnabled: !1, context: i.context, pointLightRadius: n, }) } _getOrientation () { const hpr = Cesium.HeadingPitchRoll.fromDegrees(180 + this.param.rotation.y * 1, 0, 90 - this.param.rotation.x * 1 || 0) const orientation = Cesium.Transforms.headingPitchRollQuaternion(this.cameraPosition, hpr) return this.orientation = orientation, orientation } _addCameraFrustum () { var e = this this.cameraFrustum = new Cesium.Primitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: new Cesium.FrustumGeometry({ origin: e.cameraPosition, orientation: e.orientation, frustum: e.customFrustum, _drawNearPlane: !0 }), attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.fromBytes(0, 213, 144, 20) ), } }), releaseGeometryInstances: false, appearance: new Cesium.PerInstanceColorAppearance({ translucent: true, flat: !0, closed: true, }), asynchronous: false, show: this.debugFrustum && this.show }) this.cameraFrustumOutline = new Cesium.Primitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: new Cesium.FrustumOutlineGeometry({ origin: e.cameraPosition, orientation: e.orientation, frustum: e.customFrustum, _drawNearPlane: !0 }), attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.fromBytes(0, 213, 144, 255) ), } }), appearance: new Cesium.PerInstanceColorAppearance({ closed: true, flat: true, translucent: true }), asynchronous: false, show: this.debugFrustum && this.show }) this.viewer.scene.primitives.add(this.cameraFrustum) this.viewer.scene.primitives.add(this.cameraFrustumOutline) } _addPostProcess () { if (!this.isShadowMap) return var e = this, t = videoShed3dShader const i = e.viewShadowMap['_primitiveBias'] this.postProcess = new Cesium.PostProcessStage({ fragmentShader: t, uniforms: { ztzf_frustum_videoTexture: function () { return e.videoTexture }, ztzf_frustum_maskTexture: function () { return new Cesium['Texture']({ 'context': e.viewer.scene.context, 'source': { 'width': 0x1, 'height': 0x1, 'arrayBufferView': new Uint8Array([255, 255, 255, 255]) }, 'flipY': ![] }) }, ztzf_frustum_opacity: function () { return e.alpha }, ztzf_frustum_hiddenAreaColor: () => { return new Cesium.Color(0, 0, 0, 0.5) }, shadowMap_texture: function () { if (!e.viewShadowMap || !e.viewShadowMap['_shadowMapTexture'] || e.viewShadowMap['_shadowMapTexture']['isDestroyed']()) { return new Cesium['Texture']({ 'context': e.viewer.scene.context, 'source': { 'width': 0x1, 'height': 0x1, 'arrayBufferView': new Uint8Array([0x0, 0x0, 0x0, 0x0]) }, 'flipY': ![] }) } return e.viewShadowMap._shadowMapTexture }, shadowMap_matrix: function () { return e.viewShadowMap._shadowMapMatrix }, shadowMap_lightPositionEC: function () { return e.viewShadowMap._lightPositionEC }, shadowMap_texelSizeDepthBias: function () { var t = new Cesium.Cartesian2 return t.x = 1 / e.viewShadowMap._textureSize.x, t.y = 1 / e.viewShadowMap._textureSize.y, Cesium.Cartesian4.fromElements(t.x, t.y, i.depthBias, i.normalShadingSmooth, this.combinedUniforms1) }, shadowMap_normalOffsetScale: function () { return Cesium.Cartesian4.fromElements(i.normalOffsetScale, e.viewShadowMap._distance, e.viewShadowMap.maximumDistance, e.viewShadowMap._darkness, this.combinedUniforms2) }, ztzf_frustum_flipx: () => { return false }, ztzf_frustum_flipy: () => { return false }, ztzf_frustum_borderColor: () => { return new Cesium.Color(0 / 255, 213 / 255, 144 / 255, 255 / 255) }, ztzf_frustum_borderWidth: () => { return 0.01 }, } }), this.viewer.scene.postProcessStages.add(this.postProcess) } destroy () { this.viewer.scene.postProcessStages.remove(this.postProcess), this.viewer.scene.primitives.remove(this.cameraFrustum), this.viewer.scene.primitives.remove(this.cameraFrustumOutline), //this._videoEle && this._videoEle.parentNode.removeChild(this._videoEle), this.activeVideoListener && this.viewer.clock.onTick.removeEventListener(this.activeVideoListener), this.activeVideoListener && delete this.activeVideoListener, delete this.postProcess, delete this.viewShadowMap, delete this.color, delete this.viewDis, delete this.cameraPosition, delete this.position, delete this.alpha, delete this._camerafov, delete this._isShadowMap, delete this._cameraPosition, delete this.videoTexture, delete this.cameraFrustum, delete this.cameraFrustumOutline, delete this._videoEle, delete this._debugFrustum, delete this._position, delete this._aspectRatio, delete this.element, delete this.orientation, delete this.texture, delete this.videoId, this.viewer.scene.primitives.remove(this), delete this.viewer } } export default CesiumVideoFrustum