Skip to content

122. 实战:酷家乐装修编辑器(二十六)

Published:

上节实现了拖拽家具到 3D 场景:

2025-07-25 21.42.13.gif

这节我们压缩一下模型。

因为我在 push 代码的时候,提示我模型文件超过 100M 了:

image.png

我们用 draco 来压缩下。

前面讲过,可以用 gltf-pipeline 来做这个。

我们模型是从这里下载的:

https://sketchfab.com/3d-models/bed-52da52f882e242aba646980e9757ef9f#download

image.png

是 120M 左右:

image.png

然后我们用 gltf-pipeline 压缩下:

npx gltf-pipeline -i ./public/bed.glb -o ./public/bedDraco.glb -d

image.png

压缩后只有 50M 左右了:

image.png

小了一半多。

但这样运行时就需要用 DracoLoader 来解压缩了,相当于用时间换空间。

加载快了,但是需要一段解压的时间。

把之前的 bed.glb 删掉,把压缩后的 bedDraco.glb 改名为 bed.glb

image.png

然后跑一下:

image.png

可以看到,报错说是没提供 DracoLoader

我们加一下:

我们代码里一共 new 过 6 个 GLTFLoader,现在统一提取一下:

image.png

let loaderCache: GLTFLoader;
function getGLTFLoader() {
    if(!loaderCache) {
        const gltfLoader = new GLTFLoader();
        const dracoLoader = new DRACOLoader();
        dracoLoader.setDecoderPath( 'https://www.gstatic.com/draco/versioned/decoders/1.5.6/' );
        gltfLoader.setDRACOLoader(dracoLoader);
        loaderCache = gltfLoader;
    }
    return loaderCache;
}

加一个变量来缓存,如果创建过就直接返回,这样全局就一个 GLTFLoader。

指定 DracoLoader,并且设置一个 draco 包的下载地址。

然后替换下代码里的 new GLTFLoader

image.png

除了第一个,其余的全部替换。

再跑下:

image.png

现在床的模型就加载出来了。

但你可以明显感觉到模型加载比之前慢了。

这是之前的加载速度:

image.png

这是现在的:

image.png

加载速度是快了一倍,但渲染出来等的更久了。

这就是因为用 draco 压缩之后,运行时解压需要时间。

我们之前的写法每次用到模型都会单独加载,这样每次都要单独下载这个模型文件,然后解压缩。

这样显然是性能很差的。

所以我们要在页面加载的时候统一加载模型,模型加载完之后,就直接用,渲染的时候不再加载模型。

其实酷家乐的也是这样:

2025-07-27 18.06.41.gif

模型加载完之后再进入。

把这个方法导出下:

image.png

然后在 App.tsx 加一下 loading:

image.png

就是用一个 map 来保存,key 是路径,value 是加载模型的 promise

然后 App 组件里加一个 useState 记录加载 modelLoaded 状态。

用 Promise.all 在所有模型加载完之后设置 modelLoaded 状态。

根据 modelLoaded 状态来切换界面和 loading

import './App.scss'
import Header from './components/Header';
import Menu from './components/Menu';
import Main, { getGLTFLoader } from './components/Main';
import Properties from './components/Properties';
import { useEffect, useState } from 'react';

const gltfLoader = getGLTFLoader();

export const modelMap = {
  './bed.glb': gltfLoader.loadAsync('./bed.glb'),
  './dining-table.glb': gltfLoader.loadAsync('./dining-table.glb'),
  './door.glb': gltfLoader.loadAsync('./door.glb'),
  './window.glb': gltfLoader.loadAsync('./window.glb')
}

function App() {
  const [modelLoaded, setModelLoaded] = useState(false);

  useEffect(() => {
    Promise.all(Object.values(modelMap)).then(() => {
      setModelLoaded(true);
    })
  }, []);

  return <div>
      {
        modelLoaded ? 
          <div className='wrap'>
            <Header />
            <div className='editor'>
              <Menu/>
              <Main/>
              <Properties/>
            </div>
          </div>
          : <div>
            <p>loading...</p>
          </div>
      }
    </div>
}

export default App

试一下:

2025-07-27 18.25.18.gif

确实是在 loading 之后进入的界面。

我们打开 devtools 搜一下 glb 文件:

2025-07-27 18.30.24.gif

可以看到是下载完 glb 模型之后再渲染的界面。

先给 modelMap 加一下类型:

image.png

Record<string, Promise<GLTF>>

整体是一个 Record 键值对类型,key 是 string,value 是 Promise<GLTF> 类型。

然后我们把后面用的模型改成这里的。

先改一下家具的:

就是把之前 gltfLoader.load 换成直接从 modelMap 里取。

modelMap[furniture.modelUrl].then(gltf => {  

搜一下,其余几处也是这样改:

image.png

image.png

image.png

看下效果:

2025-07-27 18.41.53.gif

可以看到,家具模型全程只渲染了一次。

这里我们要 clone 一下,不然都是同一个。

image.png

image.png

image.png

image.png

4 处都改一下。

再看下:

2025-07-27 18.48.17.gif

这样,模型就都出来了。

然后再改下门窗的:

image.png

const gltf = await modelMap['./window.glb'];
const gltf = await modelMap['./door.glb'];

同样是只渲染了一个:

image.png

image.png

gltf.scene = gltf.scene.clone();

2025-07-27 18.52.10.gif

现在就好了。

打开 devtools 看下:

2025-07-27 18.53.06.gif

可以看到,现在全部加载完 4个模型才渲染界面,并且之后再也没加载过模型。

案例代码上传了小册仓库

总结

这节我们优化了下性能。

首先用 gltf-pipeline 对一些模型进行了压缩,运行时用 DracoLoader 解压

然后加了 loading,最开始加载全部模型,等模型都加载完之后再渲染,并且之后不再加载模型。

这样改造下来,加载模型的速度,以及后续的操作体验都会好很多。

评论