美文网首页
Three.js学习项目--3D抗美援朝数据可视化

Three.js学习项目--3D抗美援朝数据可视化

作者: 薛定谔的程序 | 来源:发表于2022-04-20 18:49 被阅读0次
    部分场景
    image.png
    image.png
    体验地址

    https://kmyc.hongbin.xyz/

    操作说明 视频

    https://www.bilibili.com/video/BV1kY4y1v78z/

    我做了哪些(功能)
    • draco解析glb模型 同时处理部分纹理请求 减轻一次加载纹理压力
    • 手动控制轨道控制器镜头动画
    • 多音频拼接 控制
    • 封装动画播放器 控制进度切换
    • 动画进度控制器 同步音频 模拟视频体验
    • useContext状态共享
    • 自定义多级右键菜单 模拟原生菜单体验
    • 空闲时间加载后续用到的模型
    • 模型纹理&位置动态切换
    • echart图表使用
    • 浏览器自适应单位vw vmax使用(大面积使用)
    • 兼容移动端手机浏览
    • 模型的销毁和动画加载
    • useRef暴露方法多方调用(大量使用)
    • css-in-js 方案实践 css引擎styled-component
    • 未完成请求避免产生影响
    • 未执行计数器清理 字幕播放中 镜头切换动画执行中切换 后续的功能等
    • 点击不同模型产生不同效果 点击的事件监听 鼠标hover的样式
    • ...
    局限
    • 性能拉垮 考虑到诸多原因 未采用按需渲染在低配机上帧率大概只有30帧左右
    • 模型做的不精细-第一次建模
    • 在手机或者高刷设备上动画模型播放渲染速度与60帧设备不一致
    源代码地址

    https://gitee.com/honbingitee/kmyc

    部分逻辑
    按需渲染
    controls.addEventListener('change', () => {
        renderer.render(scene, camera);
    });
    
    模型加载

    https://hongbin.blog.csdn.net/article/details/122594047

    动画控制器

    https://hongbin.blog.csdn.net/article/details/123662686

    模型纹理条件切换
    
    /**
     * @description: 加载相册模型 返回相关操作回调
     * @param {number} animationIndex 战役索引
     * @param {THREE.TextureLoader} textureLoader 纹理加载器
     * @return {XCBack} XCBack
     */
    export async function loadXCModel(
      animationIndex: number,
      textureLoader: THREE.TextureLoader
    ): Promise<XCBack> {
      const gltf = await window.gltfLoader.loadAsync(xcModel);
      const [model] = gltf.scene.children;
      const multiple = 5;
      model.position.y = 1.8 * multiple;
    
      if (animationIndex === 1) {
        model.rotateY(-Math.PI / 2);
        model.rotateX(-Math.PI / 10);
        model.rotateZ(Math.PI / 10);
      }
    
      const setZero = (mash: typeof model) => {
        mash.scale.x = 0;
        mash.scale.y = 0;
        mash.scale.z = 0;
      };
      setZero(model);
      /**
       * 设置不同的纹理 -- 切换图片
       */
      const setMaterial = (mash: Object3D, url: string) => {
        const texture = textureLoader.load(url);
        texture.flipY = false;
        texture.encoding = 3001;
        //@ts-ignore
        const mater = mash.material.clone(); //不能共用一个material 以为 instance.material 指向的都是同一个对象
    
        mater.map = texture;
        //@ts-ignore
        mash.material = mater;
      };
    
      const pictures: XCBack["models"] = [];
    
      for (let i = 0; i < 4; i++) {
        const instance = model.clone();
        //hover tip
        instance.userData.type = ModelType["Picture"];
        instance.userData.desc = XCDesc[animationIndex]
          ? XCDesc[animationIndex][i]
          : "";
    
        setMaterial(
          instance,
          `${process.env.REACT_APP_URL}xc/${animationIndex}-${i}-y.jpg`
        );
        if (animationIndex === 1) {
          instance.position.x = (i - 1) * -5 * multiple;
          instance.position.z = -14 * multiple;
        } else if (animationIndex === 2) {
          instance.position.x = -10 * multiple;
          instance.position.z = (i - 2) * 5 * multiple;
        } else {
          instance.position.x = -10 * multiple;
          instance.position.z = (i - 1) * 5 * multiple;
        }
        pictures.push(instance);
      }
    
      let timer1: number;
      let count = 0;
      const range = 30;
      const show = () => {
        if (count < range) {
          pictures.forEach(item => {
            item.scale.y += multiple / range;
            item.scale.z += (multiple / range) * 1.8;
            item.scale.x += multiple / range / 10;
          });
          count++;
          timer1 = requestAnimationFrame(show);
        }
      };
    
      const hide = () => {
        pictures.forEach(setZero);
        count = 0;
        cancelAnimationFrame(timer1);
        requestAnimationFrame(() => {
          cancelAnimationFrame(timer1);
        });
      };
    
      let prevIndex = animationIndex;
    
      const toggle: XCBack["toggle"] = nextIndex => {
        pictures.forEach((item, index) => {
          /**
           * hover 显示图片介绍
           */
          item.userData.type = ModelType["Picture"];
          item.userData.desc = XCDesc[nextIndex][index];
    
          setMaterial(
            item,
            `${process.env.REACT_APP_URL}xc/${nextIndex}-${index}-y.jpg`
          );
          //旋转角度
          if (nextIndex === 1) {
            if (prevIndex !== 1) {
              item.rotateY(-Math.PI / 2);
              item.rotateX(-Math.PI / 10);
              item.rotateZ(Math.PI / 10);
            }
          } else {
            if (prevIndex === 1) {
              item.rotateY(Math.PI / 2);
              item.rotateX(Math.PI / 10);
              item.rotateZ(Math.PI / 10);
            }
          }
          //位置
          if (nextIndex === 1) {
            item.position.x = (index - 1) * -5 * multiple;
            item.position.z = -14 * multiple;
          } else if (nextIndex === 2) {
            item.position.x = -10 * multiple;
            item.position.z = (index - 2) * 5 * multiple;
          } else {
            item.position.x = -10 * multiple;
            item.position.z = (index - 1) * 5 * multiple;
          }
        });
        prevIndex = nextIndex;
      };
    
      return {
        show,
        hide,
        models: pictures,
        toggle,
      };
    }
    
    模型加载同时请求部分纹理 生成进度条
    //加载10个纹理
    const loadTexture = () => {
        const textureLoader = new TextureLoader();
    
        for (let i = 0; i < 10; i++) {
          const index = i.toString().padStart(2, "0");
          const url = `${process.env.REACT_APP_URL}q/${i}.jpg`;
          const texture = textureLoader.load(
            url,
            _ => {
              setProgress(timeCheck(5));
            },
            undefined,
            err => {
              console.error("load texture fail:", err);
              setProgress(timeCheck(5));
            }
          );
          texture.flipY = false;
          texture.encoding = sRGBEncoding;
          addTexture(index, texture);
        }
      };
      
    //draco解析模型
    const dracoLoader = () => {
        let prevModel = 0;
        const manager = new LoadingManager();
        manager.onProgress = (_, loaded, total) => {
          const progress = Math.floor((loaded / total) * 100);
          if (progress === 100) return setProgress(timeCheck(50 - prevModel));
          prevModel += progress / 4;
          setProgress(timeCheck(progress / 4));
        };
        //设置错误信息
        manager.onError = setIsLoadFail;
        //创建draco解析器
        const dracoLoader = new DRACOLoader(manager);
        dracoLoader.setDecoderConfig({ type: "js" });
        dracoLoader.setDecoderPath(process.env.REACT_APP_URL as string);
        // gltf 加载器
        const gltfLoader = new GLTFLoader(manager);
        gltfLoader.setDRACOLoader(dracoLoader);
        gltfLoader.load(mapModel, setMap);
        //不带LoadingManager的加载器 如果使用gltfLoader会触发事件改变progress状态造成内存泄漏
        const normalGltfLoader = new GLTFLoader();
        normalGltfLoader.setDRACOLoader(dracoLoader);
        window.gltfLoader = normalGltfLoader;
      };
      
    /**
     * 进度增长检测
      * @param {number} increase 增长的数值
      * @param {number} prev state原本数值
      * @return {number} newValue
      */
     const timeCheck = (increase: number) => (prev: number) => {
       if (increase + prev < 100) return prev + increase;
       // >= 100 检测时间
       if (Date.now() - enterTime > maxLoadTime) return 100;
       //time < maxLoadTime
       timer = setTimeout(() => {
         setProgress(100);
       }, maxLoadTime - (Date.now() - enterTime));
       //显示跳过按钮
       setIsCanJump(true);
       return prev;
     };
    
    模型缩放小动画
    let timer: number;
    let i = 0;
    const r = () => {
     if (i < 15) {
       timer = requestAnimationFrame(r);
     }
     for (const item of iconScene.children) {
       item.scale.x += 0.03;
       item.scale.y += 0.03;
       item.scale.z += 0.03;
     }
     i++;
    };
    r();
    

    相关文章

      网友评论

          本文标题:Three.js学习项目--3D抗美援朝数据可视化

          本文链接:https://www.haomeiwen.com/subject/omavertx.html