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

这节我们压缩一下模型。
因为我在 push 代码的时候,提示我模型文件超过 100M 了:

我们用 draco 来压缩下。
前面讲过,可以用 gltf-pipeline 来做这个。
我们模型是从这里下载的:
https://sketchfab.com/3d-models/bed-52da52f882e242aba646980e9757ef9f#download

是 120M 左右:

然后我们用 gltf-pipeline 压缩下:
npx gltf-pipeline -i ./public/bed.glb -o ./public/bedDraco.glb -d

压缩后只有 50M 左右了:

小了一半多。
但这样运行时就需要用 DracoLoader 来解压缩了,相当于用时间换空间。
加载快了,但是需要一段解压的时间。
把之前的 bed.glb 删掉,把压缩后的 bedDraco.glb 改名为 bed.glb

然后跑一下:

可以看到,报错说是没提供 DracoLoader
我们加一下:
我们代码里一共 new 过 6 个 GLTFLoader,现在统一提取一下:

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

除了第一个,其余的全部替换。
再跑下:

现在床的模型就加载出来了。
但你可以明显感觉到模型加载比之前慢了。
这是之前的加载速度:

这是现在的:

加载速度是快了一倍,但渲染出来等的更久了。
这就是因为用 draco 压缩之后,运行时解压需要时间。
我们之前的写法每次用到模型都会单独加载,这样每次都要单独下载这个模型文件,然后解压缩。
这样显然是性能很差的。
所以我们要在页面加载的时候统一加载模型,模型加载完之后,就直接用,渲染的时候不再加载模型。
其实酷家乐的也是这样:

模型加载完之后再进入。
把这个方法导出下:

然后在 App.tsx 加一下 loading:

就是用一个 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
试一下:

确实是在 loading 之后进入的界面。
我们打开 devtools 搜一下 glb 文件:

可以看到是下载完 glb 模型之后再渲染的界面。
先给 modelMap 加一下类型:

Record<string, Promise<GLTF>>
整体是一个 Record 键值对类型,key 是 string,value 是 Promise<GLTF> 类型。
然后我们把后面用的模型改成这里的。
先改一下家具的:

就是把之前 gltfLoader.load 换成直接从 modelMap 里取。
modelMap[furniture.modelUrl].then(gltf => {
搜一下,其余几处也是这样改:



看下效果:

可以看到,家具模型全程只渲染了一次。
这里我们要 clone 一下,不然都是同一个。




4 处都改一下。
再看下:

这样,模型就都出来了。
然后再改下门窗的:

const gltf = await modelMap['./window.glb'];
const gltf = await modelMap['./door.glb'];
同样是只渲染了一个:


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

现在就好了。
打开 devtools 看下:

可以看到,现在全部加载完 4个模型才渲染界面,并且之后再也没加载过模型。
案例代码上传了小册仓库
总结
这节我们优化了下性能。
首先用 gltf-pipeline 对一些模型进行了压缩,运行时用 DracoLoader 解压
然后加了 loading,最开始加载全部模型,等模型都加载完之后再渲染,并且之后不再加载模型。
这样改造下来,加载模型的速度,以及后续的操作体验都会好很多。