ArcGIS JSAPI 高级教程 - 通过RenderNode实现视频融合效果(不借助三方工具)

ArcGIS JSAPI 高级教程 - 通过RenderNode实现视频融合效果(不借助三方工具)

    • 核心代码
    • 完整代码
    • 在线示例

在这里插入图片描述

地球中展示视频可以通过替换纹理的方式实现,但是随着摄像头和无人机的流行,需要视频和场景深度融合,简单的实现方式则不能满足需求。

三维视频融合技术将视频资源与三维模型场景相结合,将视频内容精准地映射在地图上,以提供更真实、全面的视觉体验。

对于 WebGL 引擎来说,实现视频融合的方式有几种,而最常用的则是通过阴影的方式实现。

本文主要介绍一下 WebGL 视频融合原理以及如何实现,当然包括最重要的在线示例。

本文包括视频融合核心代码、完整代码以及在线示例


核心代码

视频融合原理:

首先通过官方类构建视频对象(VideoElement),

然后通过视点、倾斜角度等构建 相机对象(Camera,再通过观察点、远近距离、垂直视角等构建 可视域对象(Viewshed

可视域构建成功之后,会自动构建 阴影纹理、阴影矩阵 等 ,

构建 GLSL 统一变量以及 GLSL 着色器代码,最后通过后处理(RenderNode

在着色器代码中,判断片元是否可见,可见片元修改为视频纹理,不可见片元修改为暗色,从而实现视频融合。

详细介绍见代码注释。


// 通过官方方法加载视频对象
async function loadVideoOfficial(url = "./WeChat_20240708093501.mp4") {
    const videoParamTemp = new VideoElement({
        video: url,
    });
    // 视频载入之后才能使用
    await videoParamTemp.load();
    return videoParamTemp.content;
}

// 创建视频对象
let video = await loadVideoOfficial();

const width = video instanceof HTMLImageElement ? video.naturalWidth : video.width,
    height = video instanceof HTMLImageElement ? video.naturalHeight : video.height;

// 创建视频纹理
let texture = new Texture.Texture(video, {
    width, height,
    mipmap: !0,
    reloadable: !0
});

// 载入纹理
texture.load(view._stage.renderView.renderingContext);

// ===================================================================

// 着色器代码,主函数
void main() {

    // 从纹理中获取颜色
    vec4 color = texture(colorTex, uv);
    // 从纹理中获取深度
    float depth = depthFromTexture(depthTex, uv);

    // 在相机平面之外
    if (depth >= 1.0 || depth <= 0.0) {
        return;
    }

    // 将深度线性化
    float linearDepth = linearizeDepth(depth);

    // 重建相对于视图位置的局部位置
    vec4 localPosition = reconstructLocalPosition(gl_FragCoord.xy, linearDepth);

    ViewshedPoint point;

    // 获取视图点
    bool foundFace = getViewshedPointVideo(localPosition, point);

    // 记录原纹理
    fragColor = color;

    // 在每个视图之外
    if (!foundFace || !point.isWithin) {
        return;
    }

    // 从阴影图中获取视图深度
    float viewshedDepth = getDepthFromShadowMap(point.uv, point.face);
    // 计算距离
    float distance = point.orthographicDepth;

    // 判断是否可见
    bool visible = distance < viewshedDepth;

    // 调整点的 UV
    // 经验值
    point.uv.x -= 0.0825;
    point.uv.x *= 1.2;

    // 混合视频颜色和原始颜色
    vec4 videoColor = mix(texture(videoTex, point.uv), color, 0.1);

    // 根据可见性使用视频纹理
    fragColor = visible? videoColor : color;

    // 计算线性深度和局部位置的法线余弦角
    float cosAngle = normalCosAngle(linearDepth, localPosition.xyz);
    // 所有背离的以及接近平行的都被认为是被遮挡的。
    // 阈值对应大约 0.6 度,根据经验调整。
    if (cosAngle > -0.01) {
        fragColor = color;
    }
}

在这里插入图片描述


完整代码


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
    <title>Custom RenderNode - 视频融合 | Sample | ArcGIS Maps SDK for JavaScript 4.29</title>

    <link rel="stylesheet" href="https://js.arcgis.com/4.30/esri/themes/light/main.css"/>

    <script src="https://js.arcgis.com/4.30/"></script>
    <script src="./renderCommon.js"></script>

    <script type="module" src="https://js.arcgis.com/calcite-components/2.5.1/calcite.esm.js"></script>
    <link rel="stylesheet" type="text/css" href="https://js.arcgis.com/calcite-components/2.5.1/calcite.css"/>

    <style>
        html,
        body,
        #viewDiv {
            padding: 0;
            margin: 0;
            height: 100%;
            width: 100%;
        }
    </style>
</head>
<body>
<calcite-block open heading="Toggle Render Node" id="renderNodeUI">
    <calcite-label layout="inline">
        Color
        <calcite-switch id="renderNodeToggle" checked></calcite-switch>
        Grayscale
    </calcite-label>
    <calcite-label layout="inline">
        播放
        <calcite-switch id="renderNodeToggleViewshed" checked></calcite-switch>
        暂停
    </calcite-label>
</calcite-block>
<script>
    require(["esri/Map", "esri/views/SceneView", "esri/views/3d/webgl/RenderNode",
        "esri/Graphic", "esri/views/3d/webgl",
        "esri/geometry/SpatialReference",
        "esri/widgets/Home",
        "esri/core/libs/gl-matrix-2/math/mat4",
        "esri/chunks/vec42",
        "esri/core/libs/gl-matrix-2/math/vec2",
        "esri/core/libs/gl-matrix-2/factories/vec2f64",
        "esri/core/libs/gl-matrix-2/factories/vec4f64",
        "esri/core/libs/gl-matrix-2/factories/mat4f64",

        "esri/layers/IntegratedMeshLayer",
        "esri/analysis/Viewshed",
        "esri/views/3d/webgl-engine/lib/Viewshed",
        "esri/views/3d/analysis/Viewshed/ViewshedComputedData",
        "esri/analysis/ViewshedAnalysis",
        "esri/Camera",
        "esri/geometry/Point",
        "esri/layers/WebTileLayer",
        'esri/layers/support/TileInfo',

        "esri/views/3d/webgl-engine/core/shaderModules/ShaderBuilder.js",
        "esri/views/3d/webgl-engine/core/shaderLibrary/NormalFromDepth.glsl.js",
        "esri/views/3d/webgl-engine/core/shaderLibrary/util/LocalFromScreenSpace.glsl.js",
        "esri/views/3d/webgl-engine/core/shaderLibrary/util/RgbaFloat16Encoding.glsl.js",
        "esri/views/3d/webgl-engine/core/shaderLibrary/util/TextureAtlasLookup.glsl.js",
        "esri/views/3d/webgl-engine/shaders/ViewshedTechnique.js",
        "esri/core/reactiveUtils.js",
        "esri/core/Collection",

        "esri/layers/support/SceneModifications",
        "esri/layers/support/SceneModification",
        "esri/geometry/Polygon",

        "esri/views/3d/webgl-engine/lib/Texture",
        "esri/views/webgl/enums",
        "esri/layers/support/VideoElement",


    ], function (
        Map,
        SceneView,
        RenderNode,
        Graphic,
        webgl,
        SpatialReference,
        Home,
        mat4,
        vec42,
        vec2,
        vec2f64,
        vec4f64,
        mat4f64,

        IntegratedMeshLayer,
        Viewshed,
        InnerViewshed,
        ViewshedComputedData,
        ViewshedAnalysis,
        Camera,
        Point,
        WebTileLayer,
        TileInfo,
        ShaderBuilder,
        NormalFromDepth,
        LocalFromScreenSpace,
        RgbaFloat16Encoding,
        TextureAtlasLookup,
        ViewshedTechnique,
        reactiveUtils,
        Collection,
        SceneModifications,
        SceneModification,
        Polygon,
        Texture,
        enums,
        VideoElement,
    ) {

        const view = new SceneView({
            container: "viewDiv",
            camera: {
                position: {
                    spatialReference: SpatialReference.WebMercator,
                    x: -9753837.742627423,
                    y: 5140806.202422867,
                    z: 995.4546383377165
                },
                heading: 1.2311944909542853,
                tilt: 70.07900968078631
            },
            map: new Map({
                // basemap: "hybrid",
                // ground: "world-elevation"
            }),
            environment: {}
        });

        const layer = new IntegratedMeshLayer({
            url: "https://gs3d.geosceneonline.cn/server/rest/services/Hosted/%E9%AB%98%E6%96%B0%E4%B9%9D%E5%8F%B7/SceneServer",
            // Frankfurt integrated mesh data provided by Aerowest GmbH
            copyright: "Aerowest GmbH",
            title: "Integrated Mesh Frankfurt"
        });

        view.map.add(layer);


        let imLayer = layer;

        updateIntegratedMesh()

        // 更新倾斜摄影显示范围
        function updateIntegratedMesh() {

            const geom = {
                "hasZ": true,
                "spatialReference": {"latestWkid": 3857, "wkid": 102100},
                "rings": [[[12121950.326925978, 4061211.071907152, 466.23319461848587], [12122119.455563923, 4061527.424461947, 466.23319461848587], [12122324.574477604, 4061381.2400526297, 466.23319461848587], [12122159.396676224, 4061092.9049046407, 466.23319461848587], [12121950.326925978, 4061211.071907152, 466.23319461848587]]]
            }

            // create the modification collection with the geometry and attribute from the graphicsLayer
            let modifications = new SceneModifications(
                [
                    new SceneModification({
                        geometry: Polygon.fromJSON(geom),
                        type: 'replace'
                    })
                ]
            );

            // add the modifications to the IntegratedMesh
            imLayer.modifications = modifications;
        }

        // 通过官方方法加载视频对象
        async function loadVideoOfficial(url = "./WeChat_20240708093501.mp4") {
            const videoParamTemp = new VideoElement({
                video: url,
            });
            // 视频载入之后才能使用
            await videoParamTemp.load();
            return videoParamTemp.content;
        }

        view.when(async () => {

            // 相机信息
            const viewParams = {
                // 远距离
                "farDistance": 329.18556793671647,
                // 水平方向
                "heading": 211.78560080071696,
                // 水平视角
                "horizontalFieldOfView": 62.96905525251295,
                // 观察点,相机位置
                "observer": {
                    "spatialReference": {"latestWkid": 3857, "wkid": 102100},
                    "x": 12121997.388159065,
                    "y": 4061165.7843687492,
                    "z": 538.6676084687933
                },
                // 倾斜角度
                "tilt": 20.67387007921511,
                // 垂直视角
                "verticalFieldOfView": 20.072508240756747
            }

            // 定义相机,用于定位视角
            let cam = new Camera({
                position: new Point({
                    ...viewParams.observer,
                }),
                heading: viewParams.heading, // facing due south
                tilt: viewParams.tilt     // bird's eye view
            });

            // 定位
            view.goTo(cam);

            // 创建可视域
            const viewshed = new Viewshed({
                ...viewParams
            });

            // 可视域工具,用于渲染可视域
            const viewshedAnalysis = new ViewshedAnalysis({
                viewsheds: [viewshed]
            });
            view.analyses.add(viewshedAnalysis);

            const analysisView = await view.whenAnalysisView(viewshedAnalysis);

            // 获取可视域视椎边框,关闭
            const visualization =
                analysisView._analysisVisualization._viewshedVisualizations.items[0].visualization;
            visualization.visible = false;

            // 获取地球渲染器
            const renderer = view._stage.renderer;

            // 创建 glsl 生成类,获取官方 glsl 代码
            const shaderBuilder = new ShaderBuilder.ShaderBuilder,
                fragment = shaderBuilder.fragment;

            shaderBuilder.include(LocalFromScreenSpace.LocalFromScreenSpace);
            shaderBuilder.include(TextureAtlasLookup.TextureAtlasLookup);
            fragment.include(RgbaFloat16Encoding.Rgba4FloatEncoding);
            shaderBuilder.include(NormalFromDepth.NormalFromDepth);

            // 创建视频对象
            let video = await loadVideoOfficial();

            const width = video instanceof HTMLImageElement ? video.naturalWidth : video.width,
                height = video instanceof HTMLImageElement ? video.naturalHeight : video.height;

            // 创建视频纹理
            let texture = new Texture.Texture(video, {
                width, height,
                mipmap: !0,
                reloadable: !0
            });

            // 载入纹理
            texture.load(view._stage.renderView.renderingContext);
            view._stage.add(texture);

            // 创建视频融合后处理类
            const LuminanceRenderNode = RenderNode.createSubclass({
                constructor: function (option) {

                    option = {
                        ...option
                    }

                    // consumes and produces define the location of the the render node in the render pipeline
                    this.consumes = {required: ["composite-color"]};
                    this.produces = "composite-color";

                    // 获取可视域对象
                    this.renderer_viewshed = option.renderer_viewshed;

                    if (this.renderer_viewshed) {

                        // 生成 glsl 代码
                        this.commonGLSL = this.commonGLSL || shaderBuilder.generate();

                        const renderer_viewshed = this.renderer_viewshed;

                        // 重写默认渲染方法
                        // 主要是取消直接渲染可视域
                        this.renderer_viewshed.renderNode = function (a, b, c) {

                            const {bindParameters: d} = a;

                            if (renderer_viewshed.enabled && d.depth && null != c) {
                                b = renderer_viewshed._setupNormals(c);

                                if (null == renderer_viewshed._technique
                                    || renderer_viewshed._configuration.useNormalMap !== b) {

                                    renderer_viewshed._configuration.useNormalMap = b;
                                    renderer_viewshed._technique =
                                        renderer_viewshed._pluginContext?.techniques.acquire(
                                            ViewshedTechnique.ViewshedTechnique,
                                            renderer_viewshed._configuration
                                        );
                                }

                                if (renderer_viewshed._technique?.compiled) {
                                    for (const n of renderer_viewshed._viewsheds) {
                                        b = a.rctx.getBoundFramebufferObject();
                                        c = renderer_viewshed._renderViewshedShadowCubeMap(d, n);
                                        const p = renderer_viewshed._viewshedShadowMap;

                                        // 注意,这里修改了渲染过程,不会渲染到屏幕上
                                        c && null != p.depthTexture && !p.isTextureZero && (
                                            renderer_viewshed._setPassParameters(n)
                                        );
                                    }
                                } else {
                                    renderer_viewshed._pluginContext?.requestRender();
                                }
                            }
                        };
                    }
                },
                // Ensure resources are cleaned up when render node is removed
                destroy() {
                    this.shaderProgram && this.gl?.deleteProgram(this.shaderProgram);
                    this.positionBuffer && this.gl?.deleteBuffer(this.positionBuffer);
                    this.vao && this.gl?.deleteVertexArray(this.vao);
                },
                properties: {
                    // Define getter and setter for class member enabled
                    enabled: {
                        get: function () {
                            return this.produces != null;
                        },
                        set: function (value) {
                            // Setting produces to null disables the render node
                            this.produces = value ? "composite-color" : null;
                            this.requestRender();
                        }
                    }
                },

                render(inputs) {

                    // The field input contains all available framebuffer objects
                    // We need color texture from the composite render target
                    const input = inputs.find(({name}) => name === "composite-color");

                    const color = input.getTexture();

                    const output = this.acquireOutputFramebuffer();

                    const gl = this.gl;

                    // Clear newly acquired framebuffer
                    gl.clearColor(0, 0, 0, 0);
                    gl.colorMask(true, true, true, true);
                    gl.clear(gl.COLOR_BUFFER_BIT);

                    // Prepare custom shaders and geometry for screenspace rendering
                    this.ensureShader(gl);
                    this.ensureScreenSpacePass(gl);

                    // Bind custom program
                    gl.useProgram(this.shaderProgram);

                    // Use composite-color render target to be modified in the shader
                    gl.activeTexture(gl.TEXTURE0);
                    gl.bindTexture(gl.TEXTURE_2D, color.glName);
                    gl.uniform1i(this.textureUniformLocation, 0);

                    // ======================================================

                    // 绑定视频融合 uniform 变量
                    if (this.renderer_viewshed) {

                        const _viewshedParam = this.renderer_viewshed._parameters;
                        const viewshed2ShadowMap = this.renderer_viewshed._viewshedShadowMap;

                        // 可视域参数
                        gl.uniform3fv(this.viewshedTargetVectorLocation, _viewshedParam.targetVector);
                        gl.uniform3fv(this.viewshedUpVectorLocation, _viewshedParam.upVector);
                        gl.uniform2fv(this.viewshedFOVsLocation, _viewshedParam.fovs);
                        gl.uniform2fv(this.viewshedHeadingAndTiltLocation, _viewshedParam.headingAndTilt);

                        // 可视域矩阵
                        _viewshedParam.projectionMatrices && _viewshedParam.projectionMatrices.length > 0
                        && gl.uniformMatrix4fv(this.projectionMatricesLocation, false,
                            _viewshedParam.projectionMatrices.flat());
                        _viewshedParam.viewMatrices && _viewshedParam.viewMatrices.length > 0 &&
                        gl.uniformMatrix4fv(this.viewMatricesLocation, false,
                            _viewshedParam.viewMatrices.flat());


                        // 可视域阴影对象
                        if (viewshed2ShadowMap) {

                            // 阴影对象参数
                            viewshed2ShadowMap.nearFar && gl.uniform2fv(this.viewshedNearFarLocation, viewshed2ShadowMap.nearFar);

                            // 阴影矩阵
                            viewshed2ShadowMap.viewshedProjectionMatrices && viewshed2ShadowMap.viewshedProjectionMatrices.length > 0
                            && gl.uniformMatrix4fv(this.viewshedProjectionMatricesLocation, false,
                                viewshed2ShadowMap.viewshedProjectionMatrices.flat());
                            viewshed2ShadowMap.viewshedViewMatrices && viewshed2ShadowMap.viewshedViewMatrices.length > 0 &&
                            gl.uniformMatrix4fv(this.viewshedViewMatricesLocation, false,
                                viewshed2ShadowMap.viewshedViewMatrices.flat());
                            viewshed2ShadowMap.numActiveFaces &&
                            gl.uniform1i(this.viewshedNumFacesLocation, viewshed2ShadowMap.numActiveFaces);
                            viewshed2ShadowMap.atlasRegions && viewshed2ShadowMap.atlasRegions.length > 0 && gl.uniform1fv(this.viewshedAtlasRegionsLocation,
                                viewshed2ShadowMap.atlasRegions.flat());
                        }

                        // 阴影纹理使用 1 号纹理对象
                        gl.activeTexture(gl.TEXTURE1)
                        gl.bindTexture(gl.TEXTURE_2D, viewshed2ShadowMap.depthTexture?.glName);
                        gl.uniform1i(this.textureSamplerColor, 1);



                        // 计算视图逆矩阵
                        let programUniformInverseViewMatrix = gl.getUniformLocation(
                            this.shaderProgram,
                            'inverseViewMatrix'
                        );

                        // 计算矩阵的逆矩阵
                        let inverseMatrix = mat4f64.create();
                        mat4.translate(inverseMatrix, this.camera.viewMatrix, _viewshedParam.localOrigin);
                        mat4.invert(inverseMatrix, inverseMatrix);

                        gl.uniformMatrix4fv(
                            programUniformInverseViewMatrix,
                            false,
                            // this.camera.projectionMatrix
                            inverseMatrix
                        );
                    }

                    // ======================================================

                    // 使用三号纹理,这里使用视频渲染的深度纹理
                    gl.activeTexture(gl.TEXTURE2);
                    gl.bindTexture(gl.TEXTURE_2D, renderer._bindParameters.depth?.attachment?.glName);
                    gl.uniform1i(this.depthTexUniformLocation, 2);

                    // 视频纹理,帧循环更新纹理
                    texture?.frameUpdate();
                    // 只用四号号纹理单元并绑定到纹理对象
                    gl.activeTexture(gl.TEXTURE3);
                    //将帧缓冲区的颜色关联对象关联的纹理对象绑定到纹理单元
                    gl.bindTexture(gl.TEXTURE_2D, texture.glTexture?.glName)
                    // 视频纹理使用 1 号纹理对象
                    gl.uniform1i(this.textureSamplerVideo, 3);


                    // 激活视图逆转置法向量矩阵
                    let programUniformMatrix = gl.getUniformLocation(
                        this.shaderProgram,
                        'u_inverseViewNormalMatrix'
                    );

                    const temp = this.camera.viewInverseTransposeMatrix;
                    gl.uniformMatrix4fv(
                        programUniformMatrix,
                        false,
                        mat4.invertOrIdentity(mat4f64.create(), temp)
                    );

                    // 生成还原世界坐标参数
                    const l = vec4f64.create(),
                        f = vec2f64.create();

                    function projInfo(r) {
                        const x = r.projectionMatrix;
                        return 0 === x[11] ?
                            vec42.set(l, 2 / (r.fullWidth * x[0]), 2 / (r.fullHeight * x[5]), (1 + x[12]) / x[0], (1 + x[13]) / x[5])
                            : vec42.set(l, -2 / (r.fullWidth * x[0]), -2 / (r.fullHeight * x[5]), (1 - x[8]) / x[0], (1 - x[9]) / x[5])
                    }

                    function zScale(r) {
                        return 0 === r.projectionMatrix[11] ? vec2.set(f, 0, 1) : vec2.set(f, 1, 0)
                    }

                    // 投影矩阵
                    let programUniformCameraProjection = gl.getUniformLocation(
                        this.shaderProgram,
                        'projInfo'
                    );

                    gl.uniform4fv(
                        programUniformCameraProjection,
                        projInfo(this.camera)
                    );

                    // z 比例
                    let programUniformCameraZScale = gl.getUniformLocation(
                        this.shaderProgram,
                        'zScale'
                    );

                    gl.uniform2fv(
                        programUniformCameraZScale,
                        zScale(this.camera)
                    );

                    // 投影 z
                    let programUniformCameraZProjectionMap = gl.getUniformLocation(
                        this.shaderProgram,
                        'zProjectionMap'
                    );

                    const projectionMatrix = this.camera.projectionMatrix;

                    gl.uniform2fv(
                        programUniformCameraZProjectionMap,
                        [projectionMatrix[14], projectionMatrix[10]]
                    );


                    // Issue the render call for a screen space render pass
                    gl.bindVertexArray(this.vao);

                    gl.drawArrays(gl.TRIANGLES, 0, 3);

                    // use depth from input on output framebuffer
                    output.attachDepth(input.getAttachment(gl.DEPTH_STENCIL_ATTACHMENT));

                    // 开启帧循环
                    this.requestRender();

                    return output;
                },

                shaderProgram: null,
                textureUniformLocation: null,

                positionLocation: null,
                vao: null,
                positionBuffer: null,
                // used to avoid allocating objects in each frame.

                // Setup screen space filling triangle
                ensureScreenSpacePass(gl) {
                    if (this.vao) {
                        return;
                    }

                    this.vao = gl.createVertexArray();
                    gl.bindVertexArray(this.vao);
                    this.positionBuffer = gl.createBuffer();
                    gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
                    const vertices = new Float32Array([-1.0, -1.0, 3.0, -1.0, -1.0, 3.0]);
                    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

                    gl.vertexAttribPointer(this.positionLocation, 2, gl.FLOAT, false, 0, 0);
                    gl.enableVertexAttribArray(this.positionLocation);


                    gl.bindVertexArray(null);
                },

                // Setup custom shader programs
                ensureShader(gl) {
                    if (this.shaderProgram != null) {
                        return;
                    }
                    // The vertex shader program
                    // Sets position from 0..1 for fragment shader
                    // Forwards texture coordinates to fragment shader
                    const vshader = `#version 300 es

                    in vec2 position;

                    out vec2 uv;
                    void main() {
                       uv = position * 0.5 + vec2(0.5);
                       gl_Position = vec4(position, 0.0, 1.0);
                    }`;

                    // The fragment shader program applying a greyscsale conversion
                    const fshader = this.commonGLSL + `

                    // 场景颜色
                    uniform sampler2D colorTex;
                    // 视频纹理
                    uniform sampler2D videoTex;

                    in vec2 uv;

                    // 可视域阴影纹理
                    uniform sampler2D viewshedShadowMap;

                    // 渲染深度纹理
                    uniform sampler2D depthTex;

                    // 逆转置纹理矩阵
                    uniform mat4 u_inverseViewNormalMatrix;

                    // 可视域参数
                    uniform vec3 viewshedTargetVector;
                    uniform vec3 viewshedUpVector;
                    uniform vec2 viewshedFOVs;
                    uniform vec2 viewshedHeadingAndTilt;
                    uniform vec2 viewshedNearFar;

                    // 可视域矩阵
                    uniform mat4[6] viewshedProjectionMatrices;
                    uniform mat4[6] viewshedViewMatrices;
                    uniform mat4[6] projectionMatrices;
                    uniform mat4[6] viewMatrices;

                    uniform int viewshedNumFaces;

                    uniform float[24] viewshedAtlasRegions;

                    // 获取可视域 uv
                    vec2 getViewshedUv(vec4 worldPosition, int face) {
                        mat4 viewshedMatrix = viewshedProjectionMatrices[face];
                        vec4 viewshedUv4 = viewshedMatrix * worldPosition;
                        vec3 viewshedUv = viewshedUv4.xyz / viewshedUv4.w;
                        return viewshedUv.xy;
                    }

                    // 可视域三维坐标
                    vec3 getViewshedXyz(vec4 worldPosition, int face) {
                        mat4 viewshedMatrix = viewshedProjectionMatrices[face];
                        vec4 viewshedUv4 = viewshedMatrix * worldPosition;
                        vec3 viewshedUv = viewshedUv4.xyz / viewshedUv4.w;
                        return viewshedUv.xyz;
                    }

                    float viewshedDepthToFloat(float depth) {
                        return (depth - viewshedNearFar[0]) / (viewshedNearFar[1] - viewshedNearFar[0]);
                    }

                    // Orthographic depth to viewshed of given point and given cube map face in range [0, 1].
                    float getOrthographicDepthToViewshed(vec4 worldPosition, int face) {
                        mat4 viewshedViewMatrix = viewshedViewMatrices[face];
                        vec4 viewshedUv4 = viewshedViewMatrix * worldPosition;
                        vec3 viewshedUv = viewshedUv4.xyz / viewshedUv4.w;
                        float depth = -viewshedUv.z;
                        return viewshedDepthToFloat(depth);
                    }

                    // Read depth from shadow map given uv and cube map face
                    float getDepthFromShadowMap(vec2 uv, int face) {
                        int index = 4 * face;

                        float umin = viewshedAtlasRegions[index];
                        float umax = viewshedAtlasRegions[index + 1];
                        float vmin = viewshedAtlasRegions[index + 2];
                        float vmax = viewshedAtlasRegions[index + 3];

                        vec4 atlasRegion = vec4(umin, vmin, umax, vmax);
                        return rgba4ToFloat(textureAtlasLookup(viewshedShadowMap, uv, atlasRegion));
                    }

                    struct ViewshedPoint {
                        int face;
                        vec2 uv;
                        bool isWithin;
                        float orthographicDepth;
                    };

                   // 查找给定位置所在的立方体阴影并返回其相关信息
                    bool getViewshedPointVideo(vec4 worldPosition, out ViewshedPoint point) {

                        // 获取视线方向上的单位向量
                        vec3 nUp = normalize(viewshedUpVector);

                        int i = 0; // 初始化索引

                        // 检查投影后的点是否在阴影贴图纹理内
                        vec2 viewshedUv = getViewshedUv(worldPosition, i);
                        vec3 viewshedXyz = getViewshedXyz(worldPosition, i);

                        // 判断可视域矩阵范围
                        if (!(any(lessThan(viewshedXyz.xyz, vec3(0.0)))
                        || any(greaterThan(viewshedXyz.xyz, vec3(1.0))))) {

                            float orthoDepth = getOrthographicDepthToViewshed(worldPosition, i);
                            if (orthoDepth >= 0.0) {

                                // 找到了一个立方体贴图面
                                // 检查点是否确实在视线范围内,不仅仅是在摄像机视锥内
                                // 不在远距离外
                                vec3 position = worldPosition.xyz;

                                // 检查是否在视线范围内
                                bool isWithin = true; // 假设在范围内

                                // 检查是否在视野的下半部分
                                float t = dot(nUp, position);
                                bool isBottomHalf = t > 0.0;
                                vec3 nProjVector = normalize(position - t * nUp);
                                if (isWithin) {

                                    // 计算角度
                                    float angle = acos(dot(normalize(viewshedTargetVector), nProjVector));
                                    // 检查是否在视野的纵向范围内
                                    if (angle > viewshedFOVs[0] / 2.0) {
                                        isWithin = false;
                                    }
                                }

                                point = ViewshedPoint(i, viewshedUv, isWithin, orthoDepth);

                                return true;
                            }
                        }

                        // 没有匹配的立方体面
                        return false;
                    }

                    // 计算法线和视线的点积,用于获取法线的余弦角度
                    float normalCosAngle(float linearDepth, vec3 localPosition) {

                        // 使用深度纹理和线性深度重建世界空间中的点
                        vec3 cameraSpacePosition = reconstructPosition(gl_FragCoord.xy, linearDepth);

                        // 从深度纹理中获取法线
                        vec3 normal = normalFromDepth(depthTex, cameraSpacePosition, gl_FragCoord.xy, uv);

                        // 将法线转换到视图空间
                        normal = (u_inverseViewNormalMatrix * vec4(normal, 1.0)).xyz;

                        // 获取视线的方向向量
                        vec3 viewingDir = normalize(localPosition);

                        // 计算法线和视线的点积,即法线的余弦角度
                        return dot(normal, viewingDir);
                    }

                    // 主函数
                    void main() {
                        // 从纹理中获取颜色
                        vec4 color = texture(colorTex, uv);
                        // 从纹理中获取深度
                        float depth = depthFromTexture(depthTex, uv);

                        // 在相机平面之外
                        if (depth >= 1.0 || depth <= 0.0) {
                            return;
                        }

                        // 将深度线性化
                        float linearDepth = linearizeDepth(depth);

                        // 重建相对于视图位置的局部位置
                        vec4 localPosition = reconstructLocalPosition(gl_FragCoord.xy, linearDepth);

                        ViewshedPoint point;

                        // 获取视图点
                        bool foundFace = getViewshedPointVideo(localPosition, point);

                        fragColor = color;

                        // 在每个视图之外
                        if (!foundFace || !point.isWithin) {
                            return;
                        }

                        // 从阴影图中获取视图深度
                        float viewshedDepth = getDepthFromShadowMap(point.uv, point.face);
                        // 计算距离
                        float distance = point.orthographicDepth;

                        // 判断是否可见
                        bool visible = distance < viewshedDepth;

                        // 调整点的 UV
                        point.uv.x -= 0.0825;
                        point.uv.x *= 1.2;

                        // 混合视频颜色和原始颜色
                        vec4 videoColor = mix(texture(videoTex, point.uv), color, 0.1);

                        // vec4 occludedColor = mix(vec4(0.0, 0.0, 0.0, 1.0), color, 0.7);

                        fragColor = visible? videoColor : color;

                        // 计算线性深度和局部位置的法线余弦角
                        float cosAngle = normalCosAngle(linearDepth, localPosition.xyz);
                        // // 所有背离的以及接近平行的都被认为是被遮挡的。
                        // // 阈值对应大约 0.6 度,根据经验调整。
                        if (cosAngle > -0.01) {
                            // fragColor = videoColor;
                            // fragColor = occludedColor;
                            fragColor = color;
                        }
                    }

                   `;

                    this.shaderProgram = initWebgl2Shaders(gl, vshader, fshader);
                    this.positionLocation = gl.getAttribLocation(this.shaderProgram, "position");
                    this.textureUniformLocation = gl.getUniformLocation(this.shaderProgram, "colorTex");
                    this.depthTexUniformLocation = gl.getUniformLocation(this.shaderProgram, "depthTex");
                    this.textureSamplerVideo = gl.getUniformLocation(this.shaderProgram, "videoTex");

                    this.textureSamplerColor = gl.getUniformLocation(this.shaderProgram, 'viewshedShadowMap');

                    this.viewshedTargetVectorLocation = gl.getUniformLocation(this.shaderProgram, "viewshedTargetVector");
                    this.viewshedUpVectorLocation = gl.getUniformLocation(this.shaderProgram, "viewshedUpVector");
                    this.viewshedFOVsLocation = gl.getUniformLocation(this.shaderProgram, "viewshedFOVs");
                    this.viewshedHeadingAndTiltLocation = gl.getUniformLocation(this.shaderProgram, "viewshedHeadingAndTilt");
                    this.viewshedNearFarLocation = gl.getUniformLocation(this.shaderProgram, "viewshedNearFar");

                    this.projectionMatricesLocation = gl.getUniformLocation(this.shaderProgram, "viewshedProjectionMatrices");
                    this.viewMatricesLocation = gl.getUniformLocation(this.shaderProgram, "viewshedViewMatrices");

                    this.viewshedProjectionMatricesLocation = gl.getUniformLocation(this.shaderProgram, "projectionMatrices");
                    this.viewshedViewMatricesLocation = gl.getUniformLocation(this.shaderProgram, "viewMatrices");


                    this.viewshedNumFacesLocation = gl.getUniformLocation(this.shaderProgram, "viewshedNumFaces");
                    this.viewshedAtlasRegionsLocation = gl.getUniformLocation(this.shaderProgram, "viewshedAtlasRegions");
                }
            });

            const renderer_viewshed = renderer._viewshed;
            const luminanceRenderNode = new LuminanceRenderNode({view, renderer_viewshed});

            // Toggle button to enable/disable the custom render node
            const renderNodeToggle = document.getElementById("renderNodeToggle");
            renderNodeToggle.addEventListener("calciteSwitchChange", () => {
                luminanceRenderNode.enabled = !luminanceRenderNode.enabled;
            });

            const renderNodeToggleViewshed = document.getElementById("renderNodeToggleViewshed");
            renderNodeToggleViewshed.addEventListener("calciteSwitchChange", () => {
                if (video) {
                    if (video.paused) {
                        video.play();
                    } else {
                        video.pause();
                    }
                }
            });
        });
    });
</script>
<div id="viewDiv"></div>
</body>
</html>




在线示例

ArcGIS JSAPI 在线示例:视频融合效果(视频投影)


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/946325.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【大模型实战篇】LLaMA Factory微调ChatGLM-4-9B模型

1. 背景介绍 虽然现在大模型微调的文章很多&#xff0c;但纸上得来终觉浅&#xff0c;大模型微调的体感还是需要自己亲自上手实操过&#xff0c;才能有一些自己的感悟和直觉。这次我们选择使用llama_factory来微调chatglm-4-9B大模型。 之前微调我们是用两块3090GPU显卡&…

微信流量主挑战:三天25用户!功能未完善?(新纪元4)

&#x1f389;【小程序上线第三天&#xff01;突破25用户大关&#xff01;】&#x1f389; 嘿&#xff0c;大家好&#xff01;今天是我们小程序上线的第三天&#xff0c;我们的用户量已经突破了25个&#xff01;昨天还是16个&#xff0c;今天一觉醒来竟然有25个&#xff01;这涨…

【工具变量】国际消费中心城市DID数据(2007年-2023年)

数据简介 国际消费中心城市的定位是一个国家乃至全球消费市场消费资源的集中地和关键枢纽&#xff0c;该城市特质不单顺应我国对外交流与开放的不断扩大的趋势&#xff0c;其培育和建设国际消费中心城市的一大意义在于&#xff0c;以地区地域资源中心定位&#xff0c;来推动周围…

如何修复 WordPress 中的“Error establishing a database connection”问题

如何修复 WordPress 中的“Error establishing a database connection”问题 在使用 WordPress 建站时&#xff0c;如果你看到“Error establishing a database connection”的提示&#xff0c;不要慌张。这通常意味着网站无法连接到数据库&#xff0c;因此无法显示内容。下面…

streamlit、shiny、gradio、fastapi四个web APP平台体验

streamlit、shiny、gradio、fastapi四个web APP平台体验 经常被问的问题就是&#xff1a;web APP平台哪个好&#xff1f;该用哪个&#xff1f;刚开始只有用streamlit和shiny&#xff0c;最近体验了一下gradio和fastapi&#xff0c;今天根据自己的体会尝试着回答一下。 使用R语…

http报头解析

http报文 http报文主要有两类是常见的&#xff0c;第一类是请求报文&#xff0c;第二类是响应报文&#xff0c;每个报头除了第一行&#xff0c;都是采用键值对进行传输数据&#xff0c;请求报文的第一行主要包括http方法&#xff08;GET&#xff0c;PUT&#xff0c; POST&#…

Qwen-Agent

文章目录 一、关于 Qwen-Agent更新准备&#xff1a;模型服务免责声明 二、安装三、快速开发步骤 1&#xff1a;添加自定义工具步骤 2&#xff1a;配置 LLM步骤 3&#xff1a;创建智能体步骤 4&#xff1a;运行智能体 四、FAQ1、支持函数调用&#xff08;也称为工具调用&#xf…

flux文生图模型实践

flux文生图模型实践 flyfish https://github.com/black-forest-labs/flux Black Forest Labs发布FLUX.1 Tools&#xff0c;这是一套模型全家桶&#xff0c;旨在为FLUX.1基础文本转图像模型添加控制和可操纵性&#xff0c;从而实现对真实图像和生成图像的修改和重新创建。FLU…

【ETCD】【实操篇(十九)】ETCD基准测试实战

目录 1. 设定性能基准要求2. 使用基准测试工具基准测试命令 3. 测试不同的负载和场景4. 监控集群性能5. 评估硬件和网络的影响6. 对比性能基准7. 负载均衡和容错能力测试8. 优化与调优9. 测试在高负载下的表现总结 1. 设定性能基准要求 首先&#xff0c;明确集群性能的目标&am…

Docker Compose 构建 EMQX 集群 实现mqqt 和websocket

EMQX 集群化管理mqqt真香 目录 #目录 /usr/emqx 容器构建 vim docker-compose.yml version: 3services:emqx1:image: emqx:5.8.3container_name: emqx1environment:- "EMQX_NODE_NAMEemqxnode1.emqx.io"- "EMQX_CLUSTER__DISCOVERY_STRATEGYstatic"- …

【Cesium】三、实现开场动画效果

文章目录 实现效果实现方法实现代码组件化 实现效果 实现方法 Cesium官方提供了Camera的flyTo方法实现了飞向目的地的动画效果。 官方API&#xff1a;传送门 这里只需要用到目的地&#xff08;destination&#xff09;和持续时间&#xff08;duration&#xff09;这两个参数…

Qt从入门到入土(七)-实现炫酷的登录注册界面(下)

前言 Qt从入门到入土&#xff08;六&#xff09;-实现炫酷的登录注册界面&#xff08;上&#xff09;主要讲了如何使用QSS样式表进行登录注册的界面设计&#xff0c;本篇文章将介绍如何对登录注册界面进行整体控件的布局&#xff0c;界面的切换以及实现登录、记住密码等功能。…

智能化人才招聘系统是怎样的?

随着企业规模的扩大和业务范围的拓展&#xff0c;人才招聘成为了企业发展的关键环节。然而&#xff0c;市面上的人才招聘系统琳琅满目&#xff0c;质量参差不齐&#xff0c;许多企业发现&#xff0c;并非所有系统都能满足他们的需求&#xff0c;特别是智能化的需求。今天&#…

论文分享 | PromptFuzz:用于模糊测试驱动程序生成的提示模糊测试

大语言模型拥有的强大能力可以用来辅助多种工作&#xff0c;但如何有效的辅助仍然需要人的精巧设计。分享一篇发表于2024年CCS会议的论文PromptFuzz&#xff0c;它利用模型提示生成模糊测试驱动代码&#xff0c;并将代码片段嵌入到LLVM框架中执行模糊测试。 论文摘要 制作高质…

[最佳方法] 如何将视频从 Android 发送到 iPhone

概括 将大视频从 Android 发送到 iPhone 或将批量视频从 iPhone 传输到 Android 并不是一件容易的事情。也许您已经尝试了很多关于如何将视频从 Android 发送到 iPhone 15/14 的方法&#xff0c;但都没有效果。但现在&#xff0c;通过本文中的这 6 种强大方法&#xff0c;您可…

cesium小知识: 处理动画的5种方式

在 Cesium 中处理动画可以通过多种方式实现,具体取决于你想要创建的动画类型。Cesium 提供了丰富的API来支持不同种类的动画,包括但不限于物体的移动、旋转、缩放、属性变化等。以下是几种常见的动画处理方法: 1. 使用 Entity 和 SampledProperty 对于动态数据或随时间变化…

003:如何理解 CNN 中的 RGB 图像和通道?

本文为合集收录&#xff0c;欢迎查看合集/专栏链接进行全部合集的系统学习。 合集完整版请参考这里。 在灰度图一节的最后&#xff0c;给出了一个由彩色图片转成灰度图的示例&#xff0c;并且通过 color_image.mode获取了图片的格式&#xff1a;彩色图片获取到的格式为 RGBA&a…

小程序基础 —— 07 创建小程序项目

创建小程序项目 打开微信开发者工具&#xff0c;左侧选择小程序&#xff0c;点击 号即可新建项目&#xff1a; 在弹出的新页面&#xff0c;填写项目信息&#xff08;后端服务选择不使用云服务&#xff0c;开发模式为小程序&#xff0c;模板选择为不使用模板&#xff09;&…

TP 钱包插件版本的使用

目前 TokenPocket 的几个平台中&#xff0c;以 ios 和 安卓版本最为常见&#xff0c;其实很少有人知道&#xff0c;浏览器上有一个插件版本的 Tp, 用电脑多的话&#xff0c;这也是一个挺好的选择。 最新版本现在支持Chrome、Brave 浏览器、Edge&#xff08;Firefox及Opera正在…

【AIGC】使用Java实现Azure语音服务批量转录功能:完整指南

文章目录 引言技术背景环境准备详细实现1. 基础架构设计2. 实现文件上传功能3. 提交转录任务crul4. 获取转录结果 使用示例结果示例最佳实践与注意事项总结 引言 在当今数字化时代&#xff0c;将音频内容转换为文本的需求越来越普遍。无论是会议记录、视频字幕生成&#xff0c…