介绍

Three.js 提供的 Loader 系列模块主要有:
Loaders:

LoaderingManager:

const DefaultLoadingManager = new LoadingManager();

export { DefaultLoadingManager, LoadingManager };

  1. <a name="MnZrg"></a>
  2. # Loader
  3. 在 Three.js 中,Loader 主要负责对远程静态资源的加载。并且,根据应用场景分为以下两种:
  4. 1. 加载远程数据。比如 ImageLoader、FileLoader;
  5. 1. 加载远程数据,并将其转换成 Three.js 的概念对象。比如 TextureLoader、MaterialLoader、BufferGeometryLoader、ObjectLoader、GLTFLoader。
  6. <a name="glgCW"></a>
  7. ## load 方法
  8. 在 Loader 基类中,只定义 load 方法的抽象接口,其具体的实现由具类负责。
  9. ```typescript
  10. // 以下为截选代码
  11. // 完整代码见:https://github.com/mrdoob/three.js/blob/2a2a8a55a91778f4ae76f2d8ffa5f7f9f2f0a694/src/loaders/Loader.js
  12. class Loader {
  13. load( url, onLoad, onProgress, onError ) {}
  14. }

其中,onProgress 参数早已废弃了,但是为了保障接口的向后兼容,一直未从参数列表中移除。

关于 load 方法的具体实现,主要是负责向 url 请求数据,然后唤起相应的生命周期钩子。比如,FileLoader 的load 方法 :

// 以下为截选代码
// 完整代码见:https://github.com/mrdoob/three.js/blob/483cf30801069d24122a130dfcb9d85fd55474c2/src/loaders/FileLoader.js

class FileLoader extends Loader {

    load( url, onLoad, onProgress, onError ) {
        // create request
        const req = new Request( url, {
            headers: new Headers( this.requestHeader ),
            credentials: this.withCredentials ? 'include' : 'same-origin',
            // An abort controller could be added within a future PR
        } );

        // start the fetch
        fetch( req )
            .then( response => {

                if ( response.status === 200 || response.status === 0 ) {

                    if ( typeof ReadableStream === 'undefined' || response.body.getReader === undefined ) {

                        return response;

                    }

                    // periodically read data into the new stream tracking while download progress
                    const stream = new ReadableStream();

                    return new Response( stream );

                }

            } )
            .then( data => {

                onLoad( data )

            } )
            .catch( err => {

                onError( err )

            } )
    }
}

loadAsync 方法

是对 load 方法的 Promise 版。返回 Promise,其加载到的数据将被 resolve 返回。

// 以下为截选代码
// 完整代码见:https://github.com/mrdoob/three.js/blob/2a2a8a55a91778f4ae76f2d8ffa5f7f9f2f0a694/src/loaders/Loader.js

class Loader {

    loadAsync( url, onProgress ) {

        const scope = this;

        return new Promise( function ( resolve, reject ) {

            scope.load( url, resolve, onProgress, reject );

        } );

    }
}

HTTP 相关参数

对于 HTTP 请求,Loader 从接口上,目前只支持了 URL、Request Headers、Credentials 三个参数的设置,缺乏对其它参数的支持。(关于原生 fetch 方法的完整参数,可参见:MDN: fetch parameters。)

这样会面临一些明显的限制。比如,难以使用 Post 方法进行传参(因为其依托于 HTTP.body 字段)。而且即使是 Get 方法也需要在 Loader 类的外部自行将查询参数构造进 URL,使用起来不太方便。

所以从这一点看,Loader 只适合用于 GET 请求的应用场景。而且最好是「查询参数」相对固定的,比如静态资源。

CrossOrigin

目前只对 ImageLoader 有效。其中的原理是:

  1. 创建 Loader - 图1 HTMLElement image;
  2. 设置 image.crossOrigin 为 ImageLoader 的 crossOrigin 属性;
  3. 浏览器会根据 image.crossOrigin 的值,在发起对图片的 HTTP 请求时带上相应的 request headers;
  4. 从而,实现 CORS。 ```typescript // 以下为截选代码 // 完整代码见:https://github.com/mrdoob/three.js/blob/63632f2ae94a4b7b196bc6c7796e01085230ed1f/src/loaders/ImageLoader.js

class ImageLoader extends Loader {

load( url, onLoad, onProgress, onError ) {
    const image = createElementNS( 'img' );

    image.crossOrigin = this.crossOrigin;
}

}


<a name="UsbH1"></a>
# LoadingManager
<a name="NVFyt"></a>
## 主要功能
从目前看,LoadingManager 主要负责对其管理的多个 loaders 实例,进行一些统一处理:

- 提供了一些生命周期钩子 ,包括 onStart、onProgress、onLoad、onError。可用于实现在某个生命周期进行的统一处理逻辑。
- 提供了一个前置的 URL 拦截器。可用于添加对 URL 的统一处理逻辑。
```typescript
// 以下为截选代码
// 完整代码见:https://github.com/mrdoob/three.js/blob/2a2a8a55a91778f4ae76f2d8ffa5f7f9f2f0a694/src/loaders/LoadingManager.js

class LoadingManager {

    constructor( onLoad, onProgress, onError ) {
    const scope = this;
        let urlModifier = undefined;

        this.itemStart = function ( url ) {
            scope.onStart( url, itemsLoaded, itemsTotal );
        };

        this.itemEnd = function ( url ) {
            scope.onProgress( url, itemsLoaded, itemsTotal );
        };

        this.itemError = function ( url ) {
                scope.onError( url );
        };

        this.resolveURL = function ( url ) {
            return urlModifier( url );
        };

        this.setURLModifier = function ( transform ) {
            urlModifier = transform;
        };
    }
}

Loader 对其的操作

Loader 对 LoadingManager 的操作主要分布在两处:

  • 一处是 Loader 基类中;
  • 一处是 Loader 具类的 load 方法中;

其中,Loader 基类对 LoadingManager 的操作,只有保存了 LoadingManager 的 ref 。

// 以下为截选代码
// 完整代码见:https://github.com/mrdoob/three.js/blob/380f0f63e27753f7a34ef2be836db09f1f9963f4/src/loaders/Loader.js#L3-L15

class Loader {
    constructor( manager ) {
        this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
    }
}

而 Loader 具类的 load 方法,为主要地调用 LoadingManager 的地方。以 FileLoader 为例:

// 以下为截选代码
// 完整代码见:https://github.com/mrdoob/three.js/blob/380f0f63e27753f7a34ef2be836db09f1f9963f4/src/loaders/FileLoader.js#L6-L232

class FileLoader extends Loader {

    load( url, onLoad, onProgress, onError ) {

        url = this.manager.resolveURL( url );

        if ( cached !== undefined ) {

            this.manager.itemStart( url );

            setTimeout( () => {

                if ( onLoad ) onLoad( cached );

                this.manager.itemEnd( url );

            }, 0 );

            return cached;

        }

        // start the fetch
        fetch( req )
            .catch( err => {

                this.manager.itemError( url );

            } )
            .finally( () => {

                this.manager.itemEnd( url );

            } );

        this.manager.itemStart( url );

    }
}

适用场景

适合以下应用场景:

  • 获取(GET)并加载远程数据资源。
  • 远程数据的格式和 HTTP 请求配置基本固定,仅 URL 可变。这种情况下,远程数据服务,往往满足于某种标准协议,比如 Image、glTF、 3D Tiles、BufferGeoemtryJSON 等,只是不同的数据实体,被存放在在不同的 URL 上。

不适合于以下应用场景:

  • 不适合对内存中的已存在数据进行二次处理和转换,因为其入参只是一个 string 类型的 URL。

具体案例分析:

  • 用 Loader 加载后端后端数据,和我们自行定义服务数据请求类(比如,在前端项目中,毕竟常见的 DataSource / Service 类)加载,有什么区别?考虑的因素:
    • 是否存在绝对性的技术限制。
      • 用我们自定义的数据服务请求类,在 Three.js 中,有没有绝对性的技术限制?
        • Three.js 内有没有哪些些接口,被限制为了 Loader 类?
          • 如果直接看源码没有发现,没有。
          • 可以看看 example,example也没有发现
          • 可以想想 texture 有一个延迟加载显示的功能是怎么实现的,是否依赖了 Loader 类的接口定义?
            • 是由 Texture 类 联合 needsUpdate 机制实现的,与 Loader 类 无关。
          • 所以,结论没有
        • 所以,使用我们自定义的数据服务请求类,应该没有绝对性的技术限制。
      • 用 Loader 类加载后端服务数据,有没有绝对性的技术限制?
        • 关于 HTTP 请求的接口,Loader 类接口只支持了 URL、Request Headers、Credentials 三个参数的设置,缺乏对其它参数的支持。关于原生 fetch 方法的完整参数,可参见:MDN: fetch parameters
          • 这会面临一些明显的限制。比如,无法实现 Post 方法的传参(因为其依托于 HTTP.body 字段)。而且即使是 Get 方法也需要在 Loader 类的外部自行将查询参数构造进 URL,使用起来不太方便。
        • 所以目前看,从技术上只适合用于 GET 请求。而且最好是查询参数固定的,也就是静态资源。
    • 是否符合社区使用习惯。

参考资料