使用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 开发的实用技巧和最佳实践。

相关推荐
百万蹄蹄向前冲5 分钟前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳58143 分钟前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路1 小时前
GeoTools 读取影像元数据
前端
ssshooter1 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友1 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry2 小时前
Jetpack Compose 中的状态
前端
dae bal3 小时前
关于RSA和AES加密
前端·vue.js
lynn8570_blog3 小时前
低端设备加载webp ANR
前端·算法
LKAI.3 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi