Three.js學(xué)習(xí)項目--3D抗美援朝數(shù)據(jù)可視化

部分場景
image.png

image.png
體驗地址

https://kmyc.hongbin.xyz/

操作說明 視頻

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

我做了哪些(功能)
  • draco解析glb模型 同時處理部分紋理請求 減輕一次加載紋理壓力
  • 手動控制軌道控制器鏡頭動畫
  • 多音頻拼接 控制
  • 封裝動畫播放器 控制進度切換
  • 動畫進度控制器 同步音頻 模擬視頻體驗
  • useContext狀態(tài)共享
  • 自定義多級右鍵菜單 模擬原生菜單體驗
  • 空閑時間加載后續(xù)用到的模型
  • 模型紋理&位置動態(tài)切換
  • echart圖表使用
  • 瀏覽器自適應(yīng)單位vw vmax使用(大面積使用)
  • 兼容移動端手機瀏覽
  • 模型的銷毀和動畫加載
  • useRef暴露方法多方調(diào)用(大量使用)
  • css-in-js 方案實踐 css引擎styled-component
  • 未完成請求避免產(chǎn)生影響
  • 未執(zhí)行計數(shù)器清理 字幕播放中 鏡頭切換動畫執(zhí)行中切換 后續(xù)的功能等
  • 點擊不同模型產(chǎn)生不同效果 點擊的事件監(jiān)聽 鼠標(biāo)hover的樣式
  • ...
局限
  • 性能拉垮 考慮到諸多原因 未采用按需渲染在低配機上幀率大概只有30幀左右
  • 模型做的不精細-第一次建模
  • 在手機或者高刷設(shè)備上動畫模型播放渲染速度與60幀設(shè)備不一致
源代碼地址

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: 加載相冊模型 返回相關(guān)操作回調(diào)
 * @param {number} animationIndex 戰(zhàn)役索引
 * @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);
  /**
   * 設(shè)置不同的紋理 -- 切換圖片
   */
  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`
      );
      //旋轉(zhuǎn)角度
      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));
    };
    //設(shè)置錯誤信息
    manager.onError = setIsLoadFail;
    //創(chuàng)建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會觸發(fā)事件改變progress狀態(tài)造成內(nèi)存泄漏
    const normalGltfLoader = new GLTFLoader();
    normalGltfLoader.setDRACOLoader(dracoLoader);
    window.gltfLoader = normalGltfLoader;
  };
  
/**
 * 進度增長檢測
  * @param {number} increase 增長的數(shù)值
  * @param {number} prev state原本數(shù)值
  * @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();
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末六剥,一起剝皮案震驚了整個濱河市刘陶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌益愈,老刑警劉巖嫁赏,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磨隘,死亡現(xiàn)場離奇詭異,居然都是意外死亡嗅定,警方通過查閱死者的電腦和手機自娩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渠退,“玉大人忙迁,你說我怎么就攤上這事≈鞘玻” “怎么了动漾?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荠锭。 經(jīng)常有香客問我旱眯,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任删豺,我火速辦了婚禮共虑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘呀页。我一直安慰自己妈拌,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布蓬蝶。 她就那樣靜靜地躺著尘分,像睡著了一般丸氛。 火紅的嫁衣襯著肌膚如雪培愁。 梳的紋絲不亂的頭發(fā)上缓窜,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音禾锤,去河邊找鬼私股。 笑死,一個胖子當(dāng)著我的面吹牛恩掷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播螃成,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寸宏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起偿曙,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤氮凝,失蹤者是張志新(化名)和其女友劉穎望忆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體启摄,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年傅是,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喧笔。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尼变,靈堂內(nèi)的尸體忽然破棺而出浆劲,到底是詐尸還是另有隱情,我是刑警寧澤牌借,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蚯嫌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏择示。R本人自食惡果不足惜晒旅,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谈秫。 院中可真熱鬧,春花似錦鱼鼓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拇囊,卻和暖如春靶橱,著一層夾襖步出監(jiān)牢的瞬間寥袭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工鬓长, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尝江,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓啤覆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親窗声。 傳聞我的和親對象是個殘疾皇子辜纲,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容