【问题标题】:Is there a way to wait for THREE.TextureLoader.load() to finish?有没有办法等待 THREE.TextureLoader.load() 完成?
【发布时间】:2018-02-24 00:14:36
【问题描述】:

我正在使用版本 R73。 我目前的任务是用材料填充数组。该数组的内容应该稍后使用。这种用法取决于我完全加载的所有材料。

现在我遍历一个 JSON 信息数组并为每个元素调用此代码:

TLoader.load(
        BASE_ODB_URL + jsonMat.pic,
        function (texture) {
            texture.wrapS = THREE.RepeatWrapping;
            texture.wrapT = THREE.RepeatWrapping;
            texture.repeat.set(jsonMat.scaleu, jsonMat.scalev);
            Mat = new THREE.MeshLambertMaterial({
                    map : texture,
                    side : THREE.DoubleSide,
                    name : jsonMat.mname
                });
            THREEMatList.push(Mat);
        },
        function (xhr) {
        }, //onProgress
            function (xhr) {
            Mat = new THREE.MeshLambertMaterial({
                    color : 0xff0000,
                    side : THREE.DoubleSide,
                    name : jsonMat.mname
                });
            THREEMatList.push(Mat);
        } 
    )

TLoader 之前初始化过:var TLoader = new THREE.TextureLoader();

如果材料不存在,当需要时,我会获得备用材料。这只是作为一个错误选项。 有没有办法等到 .load() 完成?

【问题讨论】:

  • 在您的材质列表完全填充之前是否跳过实际渲染(而不是使用备用材质)?
  • function (texture) { 仅在纹理加载后调用。
  • 为什么不先填写一组“备用”素材,以在成功下载的情况下替换它们?

标签: javascript three.js


【解决方案1】:

Threejs 已经为所有加载的元素提供了回调 - 通过使用 LoadingManager。默认情况下,TextureLoader 使用 DefaultLoadingManager:

import {TextureLoader, DefaultLoadingManager} from './three.module.js';

const getTextures = ()=> new Promise((resolve, reject)=>{
  const loader = new TextureLoader();
  DefaultLoadingManager.onLoad = ()=>resolve(textures);
  const textures = [
    "image1.jpg",
    "image2.jpg",
    "image3.jpg"
  ].map(filename=>loader.load(filename));
});

getTextures().then(result=>console.log("We received,", result,"!"));

不过,这将等待 所有 资产加载。如果您想监听加载的特定资产子集,您可以通过构建自定义 LoadingManager 并将其传递给您的 TextureLoader 来分别管理不同的资产包:

import {TextureLoader, LoadingManager} from './three.module.js';

const getTextures = ()=> new Promise((resolve, reject)=>{
  const manager = new LoadingManager(()=>resolve(textures));
  const loader = new TextureLoader(manager);
  const textures = [
    "image1.jpg",
    "image2.jpg",
    "image3.jpg"
  ].map(filename=>loader.load(filename));
});

getTextures().then(result=>console.log("We received,", result,"!"));

【讨论】:

    【解决方案2】:

    解决这个问题的方法是通过Promise,因为它们确实是前进的方向,我个人认为很遗憾TextureLoader 没有返回Promise 开始(这会使这有点容易一点)。但是,请记住,IE11 需要一个 polyfill,因为它缺乏对 Promise 的原生支持。

    应该这样做,其中textureArray 在可迭代的Array 中表示您的 JSON 数据:

    var allPromises = [];
    
    textureArray.forEach( function( jsonMat ) {
    
        allPromises.push( new Promise( function( resolve, reject ) {
    
            TLoader.load(
               BASE_ODB_URL + jsonMat.pic,
    
               function( texture ) {
                   // Success callback of TextureLoader
                   texture.wrapS = THREE.RepeatWrapping;
                   texture.wrapT = THREE.RepeatWrapping;
                   texture.repeat.set( jsonMat.scaleu, jsonMat.scalev );
                   var material = new THREE.MeshLambertMaterial({
                       map: texture,
                       side: THREE.DoubleSide,
                       name: jsonMat.mname
                   });
                   THREEMatList.push( material );
    
                   // We're done, so tell the promise it is complete
                   resolve( material );
               },
    
               function( xhr ) {
                   // Progress callback of TextureLoader
                   // ...
               },
    
               function( xhr ) {
                   // Failure callback of TextureLoader
                   // Reject the promise with the failure
                   reject( new Error( 'Could not load ' + jsonMat.pic ) );
               }
            );
    
        }));
    
    });
    
    Promise.all( allPromises )
        .then( function( arrayOfMaterials ) {
            // All textures are now loaded, and this array
            // contains all the materials that you created
        }, function( error ) {
            console.error( "Could not load all textures:", error );
        });
    

    所以这里发生的事情是一个总体Promise 用于跟踪另一个Promises 的状态。正在加载的每个纹理都包含在 Promise 中,并添加到 allPromises 中。最后,对整个 Promise 集进行成功或失败测试,​​此时您就知道整体的成功或失败。

    这里要记住的重要一点是,Promise.all 只有在 所有 纹理都已加载时才会成功,并且一旦任何一个失败,它就会失败。如果您需要比这更精细的控制,您将需要一个具有比 Promise/A+ 规范提供的更多功能的 Promise polyfill,例如 RSVP.js。或者,您可以 resolve() 它并优雅地处理结果,而不是 reject()ing 承诺。

    【讨论】:

      【解决方案3】:

      如果您需要在渲染场景之前加载多个纹理,您也可以使用这个简单的助手:

          /**
           *
           * @param {Array} texturesSources - List of Strings that represent texture sources
           * @returns {Array} Array containing a Promise for each source 
           */
          function getTextures (texturesSources) {
              const loader = new THREE.TextureLoader()
              return texturesSources.map(textureSource => {
                  return new Promise((resolve, reject) => {
                      loader.load(
                          textureSource,
                          texture => resolve(texture),
                          undefined, // onProgress callback not supported from r84
                          err => reject(err)
                      )
                  })
              })
          }
      

      然后使用Promise.all 包装您的代码,允许并行获取源代码,并通过捕获错误正确失败。

      例子:

      const texturesBasePath = '../assets/textures/'
      const texturesSRC = [
          'image1.jpg',
          'image2.jpg',
          'image3.jpg',
      ].map(texture => texturesBasePath + texture)
      
      Promise.all(getTextures(texturesSRC))
          .then(textures => {
              // create your materials, meshs...
              // render the scene
          })
          .catch(err => console.error(err))
      

      【讨论】:

      • 嘿,这是一个很好的答案 - 但我有一个问题:有没有办法枚举从 Promise 返回的纹理?以便它们与您调用.maptexturesSRC 数组中的URL 的顺序相匹配?我之所以问是因为我认为某些纹理/图像可能比其他纹理/图像小,因此它们的加载和返回速度可能比在它们之前出现的更快 - 你知道我的意思吗?或者这不是 Promises 的工作方式?他们总是按照发送的顺序返回吗?
      • @Sirab33 你仍然可以循环 textures 参数,该参数作为回调的参数传递,一旦所有的承诺都被解决(当 then 被调用时)。因此,通过使用Promise.all()then() 函数将在所有纹理承诺都已解决时被调用。回答你最后一个问题,是的,传递给Promise.all(array) 的数组中包含的元素的顺序被保留。
      猜你喜欢
      • 2019-08-19
      • 2013-10-24
      • 1970-01-01
      • 2010-11-04
      • 2011-10-21
      • 2020-09-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多