使用three.js搭建3d隧道监测-2

使用three.js搭建3d隧道监测-1

加载基础线条与地面效果

在我们的隧道监控系统中,地面网格和方向指示器是重要的视觉元素,它们帮助用户理解空间关系和导航方向。

1. 网格地面的创建与优化

ini 复制代码
javascript
// 初始化场景中的地面
const addGround = () => {
    const size = 40000; // 网格大小
    const divisions = 100; // 分割数(越高越密集)

    // 主网格线颜色(亮蓝色)
    const color1 = 0x6E7DB9; // 蓝色

    // 次网格线颜色(深蓝色)
    const color2 = 0x282C3C; // 深蓝色

    const gridHelper = new THREE.GridHelper(size, divisions, color1, color2);

    // 调整网格线的透明度和材质
    gridHelper.material.opacity = 1;
    gridHelper.material.transparent = true;
    gridHelper.material.depthWrite = false; // 防止网格阻挡其他物体的渲染

    // 设置材质的混合模式以实现发光效果
    gridHelper.material.blending = THREE.AdditiveBlending;
    gridHelper.material.vertexColors = false;

    // 增强线条对比度
    gridHelper.material.color.setHex(color1);
    gridHelper.material.linewidth = 100;

    // 旋转网格,使其位于水平面
    gridHelper.rotation.x = Math.PI;

    sceneRef.current.add(gridHelper);
};

知识点: Three.js 中的网格地面实现技术

  • GridHelper:Three.js 提供的辅助对象,用于创建二维网格,常用于表示地面或参考平面
  • 材质优化 :通过设置 depthWrite = false 避免渲染排序问题,防止网格阻挡其他物体
  • 混合模式AdditiveBlending 混合模式使重叠线条颜色叠加,产生发光效果
  • 性能考量:网格分割数(divisions)会影响性能,需要在视觉效果和性能间平衡
  • 旋转技巧 :通过 rotation.x = Math.PI 将默认垂直的网格旋转到水平面

这种科幻风格的网格地面在虚拟现实、数据可视化和游戏中非常常见,能够提供空间参考而不显得过于突兀。

2. 动态方向指示器的实现

ini 复制代码
javascript
const createPolygonRoadIndicators = (dis) => {
  const routeIndicationGeometry = new THREE.PlaneGeometry(3024, 4000); // 创建平面几何体

  // 创建文本纹理的辅助函数
  const getTextCanvas = (text) => {
    const width = 200;
    const height = 300;
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext('2d');
    ctx.font = "bold 40px Arial"; // 设置字体大小和样式
    ctx.fillStyle = '#949292'; // 设置字体颜色
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(text, width / 2, height / 2);
    return canvas;
  };

  // 创建方向1文本平面
  const textMap = new THREE.CanvasTexture(getTextCanvas('方向1'));
  const textMaterial = new THREE.MeshBasicMaterial({ 
    map: textMap, 
    transparent: true, 
    depthTest: false 
  }); 
  const plane = new THREE.Mesh(routeIndicationGeometry, textMaterial);
  plane.castShadow = false; 
  plane.position.set(1024, 0, 1400);
  plane.rotateX(-Math.PI / 2);

  // 创建方向2文本平面
  const textMap1 = new THREE.CanvasTexture(getTextCanvas('方向2'));
  const textMaterial1 = new THREE.MeshBasicMaterial({ 
    map: textMap1, 
    transparent: true, 
    depthTest: false 
  }); 
  const plane1 = new THREE.Mesh(routeIndicationGeometry, textMaterial1);
  plane1.castShadow = false;
  plane1.position.set(1024, 0, -1400);
  plane1.rotateX(-Math.PI / 2);

  // 创建箭头指示器
  const loader = new THREE.TextureLoader();
  const texture = loader.load('/image/arrow1.png', (t) => {
    t.wrapS = t.wrapT = THREE.RepeatWrapping;
    t.repeat.set(1, 1);
  });
  const geometryRoute = new THREE.PlaneGeometry(1024, 1200);
  const materialRoute = new THREE.MeshStandardMaterial({
    map: texture,
    transparent: true,
    side: THREE.DoubleSide, // 确保可以从两个面看见
  });
  const plane2 = new THREE.Mesh(geometryRoute, materialRoute);
  plane2.receiveShadow = false;
  plane2.position.set(1000, 0, 0);
  plane2.rotateX(dis==="left"?-Math.PI / 2:Math.PI / 2);
  
  // 将所有元素组合成一个组
  const group = new THREE.Group();
  group.add(plane2, plane, plane1);
  group.scale.set(0.4, 0.4, 0.4);
  group.position.set(dis==="left"?500:500-4000, 0, 0);

  return group;
};

知识点: Three.js 中的动态文本与指示器实现技术

  • Canvas 纹理:使用 HTML Canvas 动态生成文本,然后转换为 Three.js 纹理,这是在 3D 场景中显示文本的高效方法
  • CanvasTexture:Three.js 提供的特殊纹理类型,可以直接从 Canvas 元素创建纹理,支持动态更新
  • 透明度处理 :通过设置 transparent: true 和适当的 depthTest 设置解决透明纹理的渲染问题
  • 几何体组织 :使用 THREE.Group 将多个相关的 3D 对象组织在一起,便于统一变换和管理
  • 条件旋转 :根据参数 dis 动态决定箭头的朝向,实现可配置的方向指示
  • 纹理重复 :通过 RepeatWrappingrepeat 设置可以控制纹理的重复方式,适用于创建连续的纹理效果

这种动态方向指示器在导航系统、虚拟导览和交互式地图中非常有用,可以为用户提供直观的方向引导。

3.地面方向指示器实现

在隧道监控系统中,方向指示是帮助用户理解空间方向和导航的关键元素。我们实现了一套包含文本标签和箭头的地面方向指示系统。

ini 复制代码
javascript
import * as THREE from "three";

const createPolygonRoadIndicators = (dis) => {
  const routeIndicationGeometry = new THREE.PlaneGeometry(3024, 4000); // 创建平面几何体

  const getTextCanvas = (text) => {
    const width = 200;
    const height = 300;
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext('2d');
    ctx.font = "bold 40px Arial"; // 设置字体大小和样式
    ctx.fillStyle = '#949292'; // 设置字体颜色
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(text, width / 2, height / 2);
    return canvas;
  };

  const textMap = new THREE.CanvasTexture(getTextCanvas('方向1'));
  const textMaterial = new THREE.MeshBasicMaterial({ map: textMap, transparent: true, depthTest: false }); // 创建材质,depthTest解决黑色块问题
  const plane = new THREE.Mesh(routeIndicationGeometry, textMaterial);
  plane.castShadow = false; // 不投影阴影"
  plane.position.set(1024, 0, 1400);
  plane.rotateX(-Math.PI / 2);

  const textMap1 = new THREE.CanvasTexture(getTextCanvas('方向2'));
  const textMaterial1 = new THREE.MeshBasicMaterial({ map: textMap1, transparent: true, depthTest: false }); // 创建材质,depthTest解决黑色块问题
  const plane1 = new THREE.Mesh(routeIndicationGeometry, textMaterial1);
  plane1.castShadow = false; // 不投影阴影
  plane1.position.set(1024, 0, -1400);
  plane1.rotateX(-Math.PI / 2);


  const loader = new THREE.TextureLoader();
  const texture = loader.load('/image/arrow1.png', (t) => {
    t.wrapS = t.wrapT = THREE.RepeatWrapping;
    t.repeat.set(1, 1);
  });
  const geometryRoute = new THREE.PlaneGeometry(1024, 1200);
  const materialRoute = new THREE.MeshStandardMaterial({
    map: texture,
    transparent: true,
    side: THREE.DoubleSide, // 确保可以从两个面看见
  });
  const plane2 = new THREE.Mesh(geometryRoute, materialRoute);
  plane2.receiveShadow = false; // 不接收阴影
  plane2.position.set(1000, 0, 0);
  plane2.rotateX(dis==="left"?-Math.PI / 2:Math.PI / 2);
  const group = new THREE.Group();
  group.add(plane2, plane, plane1);
  group.scale.set(0.4, 0.4, 0.4);
  group.position.set(dis==="left"?500:500-4000, 0, 0);

  return group;
};

export default createPolygonRoadIndicators;

知识点: Three.js 中的地面方向指示器实现技术

  • 平面投影标记 :使用 PlaneGeometry 创建平面,通过旋转使其平行于地面,形成"地面投影"效果

    • 使用 rotateX(-Math.PI / 2) 将平面从垂直旋转到水平位置
  • 动态文本生成:使用 Canvas API 动态生成文本纹理

    • getTextCanvas 函数创建一个临时 Canvas 并在其上绘制文本
    • 使用 CanvasTexture 将 Canvas 转换为 Three.js 可用的纹理
    • 这种方法比使用 3D 文本几何体更高效,特别是对于频繁变化的文本
  • 纹理渲染优化

    • transparent: true 启用透明度处理,使背景透明
    • depthTest: false 禁用深度测试,解决半透明纹理的渲染问题,防止出现"黑色块"
    • castShadow: falsereceiveShadow: false 避免不必要的阴影计算
  • 方向性指示:使用箭头纹理创建明确的方向指示

    • 通过 TextureLoader 加载外部箭头图像
    • 根据 dis 参数动态调整箭头方向(rotateX(dis==="left"?-Math.PI / 2:Math.PI / 2)
    • side: THREE.DoubleSide 确保从任何角度都能看到箭头
  • 组织与缩放

    • 使用 THREE.Group 将相关元素(文本标签和箭头)组织在一起
    • 通过 group.scale.set(0.4, 0.4, 0.4) 统一调整组内所有元素的大小
    • 根据方向参数设置整个组的位置,实现左右两侧不同的指示效果
  • 纹理重复设置

    • RepeatWrappingrepeat.set(1, 1) 控制纹理的重复方式
    • 这为创建连续的纹理效果提供了基础,虽然本例中设为1(不重复)

这种地面方向指示系统在大型空间(如隧道、机场、展馆)的导航中特别有用,为用户提供直观的方向感,不会干扰主要视觉元素。

隧道指示牌制作

在隧道监控系统中,指示牌是引导用户和提供空间信息的重要元素。我们实现了一种复合结构的隧道指示牌,包含支柱、横梁和信息板。

ini 复制代码
javascript
import * as THREE from 'three';
import {TextGeometry} from "three/examples/jsm/geometries/TextGeometry";

/**
 * 创建石头柱子(竖直 + 横向)
 * @returns {THREE.Group} - 返回包含柱子和横梁的组
 */
const createStonePillar = () => {
    const pillarGroup = new THREE.Group();

    // 创建六边形的竖直柱子
    const pillarGeometry = new THREE.CylinderGeometry(6, 6, 340, 6); // 直径12, 高度340, 六边形柱体
    const pillarMaterial = new THREE.MeshStandardMaterial({color: 0x808080}); // 石头颜色
    const pillar = new THREE.Mesh(pillarGeometry, pillarMaterial);
    pillar.position.set(0, 0, 0); 

    // 创建第一根横向长方体
    const beam1Geometry = new THREE.BoxGeometry(100, 10, 0.1); 
    const beam1Material = new THREE.MeshStandardMaterial({color: 0x808080}); 
    const beam1 = new THREE.Mesh(beam1Geometry, beam1Material);
    beam1.position.set(-50, 150, 0); 

    // 创建第二根横向长方体
    const beam2Geometry = new THREE.BoxGeometry(100, 10, 0.1); 
    const beam2Material = new THREE.MeshStandardMaterial({color: 0x808080}); 
    const beam2 = new THREE.Mesh(beam2Geometry, beam2Material);
    beam2.position.set(-50, 130, 0); 

    // 将柱子和横梁添加到组
    pillarGroup.add(pillar);
    pillarGroup.add(beam1);
    pillarGroup.add(beam2);
    return pillarGroup;
};

/**
 * 创建一个用于绘制文本的 Canvas
 * @param {string} text - 要绘制的文本
 * @returns {HTMLCanvasElement} - 返回 Canvas 元素
 */
const getTextCanvas = (text) => {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');

    // 设置 Canvas 尺寸
    const fontSize = 32;
    canvas.width = 512;
    canvas.height = 128;

    // 设置背景色
    context.fillStyle = '#1E3E9A'; // 蓝底
    context.fillRect(0, 0, canvas.width, canvas.height);

    // 设置文本样式
    context.font = `${fontSize}px Arial`;
    context.fillStyle = '#ffffff'; // 白色文本
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.fillText(text, canvas.width / 2, canvas.height / 2);

    return canvas;
};

/**
 * 创建交通指示牌并添加到场景中
 * @param {Object} sceneRef - React ref 对象,指向 Three.js 的场景
 * @returns {Promise<THREE.Group>} - 返回创建的指示牌组
 */
export default (sceneRef, png, dis) => {
    const createSignBoard = async () => {
        const signGroup = new THREE.Group();

        const loader = new THREE.TextureLoader();
        loader.load(png, texture => {
            // 创建一个平面作为标志背景
            const signGeometry = new THREE.PlaneGeometry(100, 50); // 宽100,高50
            texture.encoding = THREE.sRGBEncoding // 设置纹理的颜色空间
            texture.colorSpace = THREE.SRGBColorSpace;
            const signMaterial = new THREE.MeshStandardMaterial({
                map: texture,
                transparent: true,
                side: THREE.DoubleSide,
            })

            const sign = new THREE.Mesh(signGeometry, signMaterial);
            sign.position.set(-60, 140, 0.3)
            signGroup.add(sign);
        })

        // 创建并添加石头柱子
        const pillar = createStonePillar();
        signGroup.add(pillar);

        if (dis == "left") {
            signGroup.position.set(370, 180, 3750); // 左侧位置
        } else {
            signGroup.rotateY(Math.PI); // 旋转180度
            signGroup.position.set(-370 - 2000, 180, 3450 - 7200); // 右侧位置
        }

        signGroup.add(pillar);
        sceneRef.current.add(signGroup);

        return signGroup; // 返回整个组
    };

    // 调用创建指示牌函数
    return createSignBoard().then((signGroup) => {
        console.log('交通指示牌创建完成:', signGroup);
        return signGroup;
    });
};

知识点: Three.js 中的复合结构与指示牌实现技术

  • 模块化设计:将指示牌分解为柱子、横梁和信息板三个主要组件,便于维护和复用

  • 几何体组合:使用简单几何体(圆柱体、长方体、平面)组合构建复杂结构

    • CylinderGeometry 创建六边形柱体作为支撑
    • BoxGeometry 创建横向支撑梁
    • PlaneGeometry 创建平面显示信息
  • 空间层次 :使用 THREE.Group 将相关元素组织在一起,便于整体变换和管理

  • 纹理映射 :使用 TextureLoader 加载外部图像作为指示牌内容

    • 设置 colorSpace = THREE.SRGBColorSpace 确保颜色正确显示
    • 使用 side: THREE.DoubleSide 使平面从两面都可见
  • 条件定位 :根据 dis 参数动态决定指示牌的位置和朝向

    • 使用 rotateY(Math.PI) 旋转180度实现方向反转
  • Canvas 动态文本 :使用 getTextCanvas 函数创建动态文本纹理

    • 可以方便地生成不同内容和样式的文本标识
  • 异步处理:使用 Promise 处理纹理加载的异步过程,确保资源正确加载

    • 返回 Promise 使调用者可以在指示牌创建完成后执行后续操作

这种组合式设计方法允许我们创建高度可定制的指示牌,适用于隧道、道路、建筑内部等多种场景,同时保持代码的可维护性和可扩展性。

多渲染器协同工作机制

在我们的项目中,实现了 WebGL 渲染器、CSS2D 渲染器和 CSS3D 渲染器的协同工作:

javascript 复制代码
const initRenderer = () => {
    // WebGL 渲染器
    rendererRef.current = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
        logarithmicDepthBuffer: true
    });
    rendererRef.current.setSize(window.innerWidth, window.innerHeight);
    rendererRef.current.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    rendererRef.current.shadowMap.enabled = true;
    rendererRef.current.shadowMap.type = THREE.PCFSoftShadowMap;
    rendererRef.current.outputEncoding = THREE.sRGBEncoding;
    rendererRef.current.toneMapping = THREE.ACESFilmicToneMapping;
    containerRef.current.appendChild(rendererRef.current.domElement);
};

const initCSS2DScene = () => {
    // CSS2D 渲染器
    css2DRendererRef.current = new CSS2DRenderer();
    css2DRendererRef.current.setSize(window.innerWidth, window.innerHeight);
    css2DRendererRef.current.domElement.style.position = 'absolute';
    css2DRendererRef.current.domElement.style.top = '0';
    css2DRendererRef.current.domElement.style.pointerEvents = 'none';
    containerRef.current.appendChild(css2DRendererRef.current.domElement);
};

const initCSS3DScene = () => {
    // 初始化 CSS3DRenderer
    css3DRendererRef.current = new CSS3DRenderer();
    css3DRendererRef.current.setSize(sizes.width, sizes.height);
    css3DRendererRef.current.domElement.style.position = 'absolute';
    css3DRendererRef.current.domElement.style.top = '0px';
    css3DRendererRef.current.domElement.style.pointerEvents = 'none'; // 确保CSS3D元素不阻碍鼠标事件
    containerRef.current.appendChild(css3DRendererRef.current.domElement);
};

知识点: Three.js 支持多种渲染器同时工作,每种渲染器有不同的优势:

  • WebGLRenderer:利用 GPU 加速渲染 3D 内容,性能最佳
  • CSS2DRenderer:将 HTML 元素作为 2D 标签渲染在 3D 空间中,适合信息标签
  • CSS3DRenderer:将 HTML 元素转换为 3D 对象,支持 3D 变换,适合复杂 UI

多渲染器协同可以充分发挥各自优势,实现复杂的混合现实效果。

后期处理管线设计

项目中实现了基于 EffectComposer 的后期处理管线:

javascript 复制代码
const initPostProcessing = () => {
    composerRef.current = new EffectComposer(rendererRef.current);
    
    // 基础渲染通道
    const renderPass = new RenderPass(sceneRef.current, cameraRef.current);
    composerRef.current.addPass(renderPass);
    
    // 环境光遮蔽通道
    const ssaoPass = new SSAOPass(
        sceneRef.current,
        cameraRef.current,
        window.innerWidth,
        window.innerHeight
    );
    ssaoPass.kernelRadius = 16;
    ssaoPass.minDistance = 0.005;
    ssaoPass.maxDistance = 0.1;
    composerRef.current.addPass(ssaoPass);
    
    // 抗锯齿通道
    const fxaaPass = new ShaderPass(FXAAShader);
    const pixelRatio = rendererRef.current.getPixelRatio();
    fxaaPass.material.uniforms['resolution'].value.x = 1 / (window.innerWidth * pixelRatio);
    fxaaPass.material.uniforms['resolution'].value.y = 1 / (window.innerHeight * pixelRatio);
    composerRef.current.addPass(fxaaPass);
};

知识点: 后期处理(Post-processing)是一种在 3D 场景渲染完成后对图像进行额外处理的技术:

  • EffectComposer:Three.js 中的后期处理管理器,可以将多个处理效果组合在一起
  • RenderPass:基础渲染通道,将场景渲染到目标缓冲区
  • SSAOPass:屏幕空间环境光遮蔽,增强场景深度感和真实感
  • FXAAShader:快速近似抗锯齿,提高图像质量

后期处理可以大幅提升画面质量,添加如景深、发光、色彩校正等专业效果。

多层次动画系统

项目实现了一个多层次的动画系统:

javascript 复制代码
// 骨骼动画控制
const getActions = (animations, model) => {
    const mixer = new THREE.AnimationMixer(model);
    const mixerArray = [];
    mixerArray.push(mixer);
    
    const actions = {};
    animations.forEach((clip) => {
        const action = mixer.clipAction(clip);
        actions[clip.name] = action;
    });
    
    return {actions, mixerArray};
};

// 动画播放控制
const playActiveAction = (actions, name, startTime = true, loopType = THREE.LoopOnce, clampWhenFinished = true) => {
    const action = actions[name];
    if (!action) return;
    
    action.reset();
    action.clampWhenFinished = clampWhenFinished;
    action.setLoop(loopType);
    if (startTime) {
        action.play();
    }
};

知识点: Three.js 提供了多种动画技术:

  • AnimationMixer:用于播放和控制模型骨骼动画的核心类,相当于动画播放器
  • AnimationClip:包含一组关键帧轨道的动画数据,如"走路"、"跑步"等动作
  • AnimationAction:控制单个动画的播放状态,包括播放、暂停、循环设置等
  • 动画混合:可以实现多个动画之间的平滑过渡,如从走路切换到跑步

合理使用这些技术可以创建流畅、自然的角色动画和场景变换。

第一人称视角控制算法

项目实现了一种先进的第一人称视角控制算法:

javascript 复制代码
const animate1 = () => {
    requestRef1.current = requestAnimationFrame(animate1);
    if (isFirstPerson && robotRef.current) {
        // 获取机器人的世界坐标
        const robotWorldPosition = new THREE.Vector3();
        robotRef.current.getWorldPosition(robotWorldPosition);
        
        // 计算摄像机位置偏移
        const offset = new THREE.Vector3(0, 140, 20);
        
        // 获取机器人的前方方向向量
        const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(robotRef.current.quaternion);
        const lookAheadDistance = 150;
        
        // 计算摄像头位置和视线目标
        const targetCameraPosition = robotWorldPosition.clone().add(offset);
        const lookAtPosition = robotWorldPosition.clone().add(forward.multiplyScalar(lookAheadDistance));
        
        // 使用 TWEEN 实现平滑过渡
        cameraTweenRef.current = new TWEEN.Tween(cameraRef.current.position)
            .to({
                x: targetCameraPosition.x,
                y: targetCameraPosition.y,
                z: targetCameraPosition.z,
            }, 1000)
            .easing(TWEEN.Easing.Quadratic.Out)
            .onUpdate(() => {
                cameraRef.current.lookAt(lookAtPosition);
                controlsRef.current.target.set(lookAtPosition.x, lookAtPosition.y, lookAtPosition.z);
            })
            .start();
    }
};

知识点: 第一人称相机控制涉及多个关键技术:

  • 世界坐标计算 :通过 getWorldPosition() 获取对象在世界坐标系中的位置
  • 四元数旋转 :使用 applyQuaternion() 将向量按对象的旋转方向进行变换
  • 向量运算:通过向量加法和标量乘法计算相机位置和视线方向
  • 平滑过渡:使用 TWEEN.js 实现相机位置的平滑变化,避免生硬的跳变
  • lookAt:让相机始终"看着"目标点,实现跟随效果

这种技术常用于第一人称游戏、虚拟导览等应用。

递归资源释放算法

项目实现了一种递归资源释放算法,用于彻底清理 Three.js 资源:

javascript 复制代码
const disposeSceneObjects = (object) => {
    if (!object) return;

    // 递归清理子对象
    while (object.children.length > 0) {
        const child = object.children[0];
        disposeSceneObjects(child);
        object.remove(child);
    }

    // 清理几何体
    if (object.geometry) {
        object.geometry.dispose();
    }

    // 清理材质
    if (object.material) {
        if (Array.isArray(object.material)) {
            object.material.forEach(material => disposeMaterial(material));
        } else {
            disposeMaterial(object.material);
        }
    }

    // 清理纹理
    if (object.texture) {
        object.texture.dispose();
    }
};

// 清理材质的辅助函数
const disposeMaterial = (material) => {
    if (!material) return;

    // 清理所有纹理属性
    const textureProperties = [
        'map', 'normalMap', 'roughnessMap', 'metalnessMap',
        'emissiveMap', 'bumpMap', 'displacementMap',
        'alphaMap', 'lightMap', 'aoMap', 'envMap'
    ];

    textureProperties.forEach(prop => {
        if (material[prop] && material[prop].dispose) {
            material[prop].dispose();
        }
    });

    material.dispose();
};

知识点: WebGL 资源管理是 3D 应用开发中的关键挑战:

  • JavaScript 垃圾回收的局限性:虽然 JS 有自动垃圾回收,但 WebGL 资源(如纹理、缓冲区)需要手动释放
  • 深度优先遍历:通过递归算法遍历整个场景图,确保所有对象都被正确处理
  • 资源类型处理:不同类型的资源(几何体、材质、纹理)需要不同的释放方法
  • 内存泄漏防护:不正确的资源管理是 WebGL 应用中最常见的内存泄漏原因

合理的资源释放策略对长时间运行的 3D 应用至关重要,可以避免性能下降和浏览器崩溃。

资源预加载与缓存策略

项目实现了资源预加载与缓存策略:

javascript 复制代码
// 资源管理器
const ResourceManager = {
    // 资源缓存
    cache: new Map(),
    
    // 预加载资源
    preload: async (resources) => {
        const loader = new GLTFLoader();
        
        // 并行加载所有资源
        const loadPromises = resources.map(resource => {
            return new Promise((resolve, reject) => {
                loader.load(
                    resource.url,
                    (gltf) => {
                        ResourceManager.cache.set(resource.id, {
                            data: gltf,
                            lastUsed: Date.now(),
                            refCount: 0
                        });
                        resolve(gltf);
                    },
                    undefined,
                    reject
                );
            });
        });
        
        return Promise.all(loadPromises);
    },
    
    // 获取资源
    get: (id) => {
        const resource = ResourceManager.cache.get(id);
        if (resource) {
            resource.lastUsed = Date.now();
            resource.refCount++;
            return resource.data;
        }
        return null;
    },
    
    // 释放资源
    release: (id) => {
        const resource = ResourceManager.cache.get(id);
        if (resource) {
            resource.refCount--;
            if (resource.refCount <= 0) {
                // 可以选择立即释放或稍后由缓存清理机制释放
            }
        }
    }
};

知识点: 3D 应用中的资源管理策略:

  • 预加载:提前加载关键资源,减少用户等待时间
  • 并行加载:使用 Promise.all 并行加载多个资源,提高加载效率
  • 资源缓存:使用 Map 数据结构存储已加载资源,避免重复加载
  • 引用计数:跟踪资源的使用情况,只有当引用计数为零时才考虑释放
  • 最近使用时间:记录资源最后使用时间,可用于实现 LRU (最近最少使用) 缓存策略

这种资源管理策略可以平衡内存使用和加载性能,适用于资源密集型的 3D 应用。

总结

通过这个隧道监控可视化系统的开发,我们深入实践了 Three.js 的多项高级技术,包括多渲染器协同、后期处理、动画系统、相机控制和资源管理等。这些技术不仅适用于隧道监控,还可以应用于数字孪生、产品可视化、教育培训等多个领域。

希望这次分享对大家了解 Web 3D 开发有所帮助!如有任何问题或改进建议,非常欢迎与我交流讨论。我将在后续分享中带来更多 Three.js 开发的实用技巧和最佳实践。

相关推荐
十一吖i1 小时前
vue3表格显示隐藏列全屏拖动功能
前端·javascript·vue.js
冰暮流星2 小时前
css之线性渐变
前端·css
徐同保3 小时前
tailwindcss暗色主题切换
开发语言·前端·javascript
mapbar_front3 小时前
大厂精英为何在中小公司水土不服?
前端
生莫甲鲁浪戴3 小时前
Android Studio新手开发第二十七天
前端·javascript·android studio
细节控菜鸡5 小时前
【2025最新】ArcGIS for JS 实现随着时间变化而变化的热力图
开发语言·javascript·arcgis
2501_916008895 小时前
Web 前端开发常用工具推荐与团队实践分享
android·前端·ios·小程序·uni-app·iphone·webview
SkylerHu6 小时前
前端代码规范:husky+ lint-staged+pre-commit
前端·代码规范
菜鸟una6 小时前
【微信小程序 + 消息订阅 + 授权】 微信小程序实现消息订阅流程介绍,代码示例(仅前端)
前端·vue.js·微信小程序·小程序·typescript·taro·1024程序员节
Yeats_Liao6 小时前
Go Web 编程快速入门 05 - 表单处理:urlencoded 与 multipart
前端·golang·iphone