如何让GLTFLoader的onProgress回调统计.gltf/.bin/.jpg所有文件的加载进度?
你遇到的问题非常典型——GLTFLoader默认的onProgress回调仅会追踪主.gltf文件的加载进度,而它内部自动加载的.bin(模型二进制数据)和.jpg(纹理资源)这类依赖文件的进度不会被这个回调捕获,这就导致主文件加载完成后进度直接跳至100%,但实际子资源还在后台加载。
另外,先帮你纠正代码里的一个语法错误:gltfLoader.load的参数顺序是load(url, onLoad, onProgress, onError),但你把onProgress函数嵌套在了onLoad函数内部,这会导致这个进度回调根本不会被执行,这也是你看不到正确进度的原因之一。
接下来,我们用three.js内置的**THREE.LoadingManager**来解决核心问题——它是全局资源加载管理器,能追踪所有通过three.js加载器发起的资源请求(包括GLTFLoader内部加载的子资源),让我们可以准确统计所有文件的总加载进度。
方案1:按资源个数统计进度(简单易实现)
这种方式基于待加载的资源总数计算进度,每个资源加载完成后更新进度,适合对精度要求不高的场景:
// 1. 创建自定义LoadingManager实例 const loadingManager = new THREE.LoadingManager(); let totalResources = 0; let loadedResources = 0; // 所有资源开始加载时触发:记录总资源数 loadingManager.onStart = (url, itemsLoaded, itemsTotal) => { totalResources = itemsTotal; console.log(`开始加载 ${totalResources} 个资源`); }; // 单个资源加载完成时触发:更新进度 loadingManager.onProgress = (url, itemsLoaded, itemsTotal) => { loadedResources = itemsLoaded; const progress = Math.round((loadedResources / totalResources) * 100); const loaderText = document.getElementById('loader-text'); loaderText.textContent = `${progress}%`; }; // 所有资源加载完成时触发:隐藏加载器 loadingManager.onLoad = () => { hideLoader(); console.log('所有资源加载完成!'); }; // 资源加载出错时触发 loadingManager.onError = (url) => { console.error(`加载资源 ${url} 时发生错误`); }; // 2. 将自定义LoadingManager传入GLTFLoader const gltfLoader = new GLTFLoader(loadingManager); // 显示加载器 document.getElementById('loader').style.display = 'block'; function hideLoader() { const loaderContainer = document.getElementById('loader'); loaderContainer.style.display = 'none'; } // 3. 加载GLTF模型 gltfLoader.load('./Model/Model.gltf', (gltf) => { scene.add(gltf.scene); // 无需在此调用hideLoader,LoadingManager的onLoad会统一处理 }, null, (error) => { console.error('加载模型时出错:', error); });
方案2:按总字节数统计进度(更精准)
如果需要更贴合实际加载量的进度(按文件大小比例计算),我们可以监听每个资源的字节加载进度并累加统计:
let totalBytes = 0; let loadedBytes = 0; const resourceByteMap = new Map(); // 记录每个资源的总大小和已加载大小 // 覆盖默认XHR创建逻辑,监听每个资源的字节加载进度 const originalCreateXHR = THREE.DefaultLoadingManager.createXHR; THREE.DefaultLoadingManager.createXHR = function() { const xhr = originalCreateXHR.apply(this, arguments); xhr.addEventListener('progress', (event) => { if (event.lengthComputable) { const resourceUrl = event.target.responseURL; // 记录当前资源的字节数据 resourceByteMap.set(resourceUrl, { total: event.total, loaded: event.loaded }); // 计算所有资源的总字节数和已加载字节数 totalBytes = Array.from(resourceByteMap.values()).reduce((sum, item) => sum + item.total, 0); loadedBytes = Array.from(resourceByteMap.values()).reduce((sum, item) => sum + item.loaded, 0); // 更新进度文本 if (totalBytes > 0) { const progress = Math.round((loadedBytes / totalBytes) * 100); const loaderText = document.getElementById('loader-text'); loaderText.textContent = `${progress}%`; } } }); return xhr; }; const loadingManager = new THREE.LoadingManager(); // 所有资源加载完成时隐藏加载器 loadingManager.onLoad = () => { hideLoader(); console.log('所有资源加载完成!'); }; // 资源加载错误处理 loadingManager.onError = (url) => { console.error(`加载资源 ${url} 时出错`); }; // 初始化GLTFLoader并传入自定义管理器 const gltfLoader = new GLTFLoader(loadingManager); // 显示加载器 document.getElementById('loader').style.display = 'block'; function hideLoader() { const loaderContainer = document.getElementById('loader'); loaderContainer.style.display = 'none'; } // 加载GLTF模型 gltfLoader.load('./Model/Model.gltf', (gltf) => { scene.add(gltf.scene); }, null, (error) => { console.error('加载模型时出错:', error); });
关键原理说明
LoadingManager是three.js的全局资源调度中心,GLTFLoader内部加载.bin和纹理资源时,会自动使用three.js的加载管理器发起请求。通过自定义LoadingManager,我们可以捕获到所有子资源的加载事件,从而实现全资源的进度统计。
备注:内容来源于stack exchange,提问作者Fedja Hadzibegovic

