调研探索
加载 Three.js 的 GLTF、纹理、HDR…
在 Web Three.js 项目中加载 3D 模型、纹理、HDR 环境贴图,并管理加载进度、缓存、错误和异步流程时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
加载 Three.js 的 GLTF、纹通用
解决思路
- Quick Start:从 `three` 导入 `THREE`,从 `three/addons/loaders/GLTFLoader.js` 导入 `GLTFLoader`;用 `new GLTFLoader()` 调用 `loader.load(model.glb, (gltf) => scene.add(gltf.scene))` 加载 GLTF 模型。
- Quick Start:用 `new THREE.TextureLoader()` 调用 `textureLoader.load(texture.jpg)` 加载纹理。
- LoadingManager:用 `new THREE.LoadingManager()` 创建管理器,设置 `manager.onStart = (url, loaded, total) => ...` 记录开始加载。
- LoadingManager:设置 `manager.onLoad = () => ...`,在所有资源加载完成后执行 `startGame()` 等逻辑。
- LoadingManager:设置 `manager.onProgress = (url, loaded, total) => ...`,计算 `progress = (loaded / total) * 100` 并调用 `updateProgressBar(progress)`。
- LoadingManager:设置 `manager.onError = (url) => ...`,记录加载失败 URL。
- LoadingManager:把同一个 manager 传给多个 loader,例如 `new THREE.TextureLoader(manager)` 和 `new GLTFLoader(manager)`。
- LoadingManager:通过该 manager 加载 `texture1.jpg`、`texture2.jpg`、`model.glb`,`onLoad` 会在全部完成后触发。
- TextureLoader:使用回调式 `loader.load(url, onLoad, undefined, onError)`;在 `onLoad` 中设置 `material.map = texture` 和 `material.needsUpdate = true`。
- TextureLoader:也可同步返回 texture、异步加载,例如 `const texture = loader.load(texture.jpg); material.map = texture;`。
- 纹理配置:颜色/反照率贴图设置 `tex.colorSpace = THREE.SRGBColorSpace`;数据贴图(法线、粗糙度)设置 `THREE.LinearSRGBColorSpace`。
- 纹理配置:按需设置 `tex.wrapS` 和 `tex.wrapT` 为 `THREE.RepeatWrapping`、`ClampToEdgeWrapping` 或 `MirroredRepeatWrapping`。
- 纹理配置:设置重复、偏移、旋转和中心点,例如 `tex.repeat.set(2, 2)`、`tex.offset.set(0.5, 0.5)`、`tex.rotation = Math.PI / 4`、`tex.center.set(0.5, 0.5)`。
- 纹理配置:设置过滤方式,`tex.minFilter = THREE.LinearMipmapLinearFilter`、`tex.magFilter = THREE.LinearFilter`;可选 `NearestFilter` 像素化、`LinearFilter` 平滑、`LinearMipmapLinearFilter` 带 mipmap 平滑。
- 纹理配置:设置 `tex.anisotropy = renderer.capabilities.getMaxAnisotropy()` 以改善斜角清晰度。
- 纹理配置:标准纹理通常设置 `tex.flipY = true`。
- 纹理配置:配置完成后设置 `tex.needsUpdate = true`。
- CubeTextureLoader:用 `new THREE.CubeTextureLoader()` 按 `px.jpg, nx.jpg, py.jpg, ny.jpg, pz.jpg, nz.jpg` 顺序加载 6 个面。
- CubeTextureLoader:把加载结果用于 `scene.background`、`scene.environment` 或 `material.envMap`。
- HDR/EXR:用 `RGBELoader` 加载 `environment.hdr`,设置 `texture.mapping = THREE.EquirectangularReflectionMapping`,再赋给 `scene.environment` 和/或 `scene.background`。
- HDR/EXR:用 `EXRLoader` 加载 `environment.exr`,设置 `texture.mapping = THREE.EquirectangularReflectionMapping`,再赋给 `scene.environment`。
- PMREMGenerator:创建 `new THREE.PMREMGenerator(renderer)` 并调用 `pmremGenerator.compileEquirectangularShader()`。
- PMREMGenerator:加载 HDR 后用 `pmremGenerator.fromEquirectangular(texture).texture` 生成预过滤环境贴图,赋给 `scene.environment` 和 `scene.background`,然后调用 `texture.dispose()` 和 `pmremGenerator.dispose()`。
- GLTF/GLB:从 `three/addons/loaders/GLTFLoader.js` 导入 `GLTFLoader`,用 `loader.load(model.glb, (gltf) => ...)` 加载;取 `gltf.scene` 并加入场景。
- GLTF/GLB:若 `gltf.animations` 非空,创建 `new THREE.AnimationMixer(model)`,遍历 `animations` 并执行 `mixer.clipAction(clip).play()`。
- GLTF/GLB:从 `gltf.cameras` 读取模型内相机。
- GLTF/GLB:读取 `gltf.asset` 获取版本、生成器等信息。
- GLTF/GLB:读取 `gltf.userData` 获取来自 Blender 等的自定义数据。
- GLTF Draco:导入 `DRACOLoader`,创建 `new DRACOLoader()`,调用 `dracoLoader.setDecoderPath(https://www.gstatic.com/draco/versioned/decoders/1.5.6/)` 并调用 `dracoLoader.preload()`。
- GLTF Draco:调用 `gltfLoader.setDRACOLoader(dracoLoader)`,再加载 `compressed-model.glb` 并加入场景。
- GLTF KTX2:导入 `KTX2Loader`,创建 `new KTX2Loader()`,调用 `ktx2Loader.setTranscoderPath(https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/basis/)` 并调用 `ktx2Loader.detectSupport(renderer)`。
- GLTF KTX2:调用 `gltfLoader.setKTX2Loader(ktx2Loader)`,再加载 `model-with-ktx2.glb` 并加入场景。
- 处理 GLTF 内容:用 `model.traverse((child) => ...)`,对 `child.isMesh` 设置 `child.castShadow = true` 和 `child.receiveShadow = true`。
- 处理 GLTF 内容:用 `model.getObjectByName(Head)` 查找指定 mesh。
- 处理 GLTF 内容:遍历模型并调整材质,例如对 `child.material.envMapIntensity = 0.5`。
- 处理 GLTF 内容:用 `new THREE.Box3().setFromObject(model)` 后调用 `box.getCenter(new THREE.Vector3())` 和 `box.getSize(new THREE.Vector3())` 获取中心与尺寸。
- 处理 GLTF 内容:用 `model.position.sub(center)` 居中,计算 `maxDim = Math.max(size.x, size.y, size.z)`,执行 `model.scale.setScalar(1 / maxDim)` 归一化尺寸,再 `scene.add(model)`。
- OBJ + MTL:用 `MTLLoader` 加载 `model.mtl`,在回调中调用 `materials.preload()`;用 `OBJLoader.setMaterials(materials)`,再加载 `model.obj` 并加入场景。
- FBX:用 `FBXLoader` 加载 `model.fbx`;常设置 `object.scale.setScalar(0.01)`;创建 `new THREE.AnimationMixer(object)`,遍历 `object.animations` 并执行 `mixer.clipAction(clip).play()`,再加入场景。
- STL:用 `STLLoader` 加载 `model.stl` 得到 geometry,创建 `new THREE.MeshStandardMaterial({ color: 0x888888 })`,用 `new THREE.Mesh(geometry, material)` 加入场景。
- PLY:用 `PLYLoader` 加载 `model.ply` 得到 geometry,调用 `geometry.computeVertexNormals()`,创建 `new THREE.MeshStandardMaterial({ vertexColors: true })`,用 `new THREE.Mesh(geometry, material)` 加入场景。
- 异步/Promise:把 loader 包装为 Promise,`function loadModel(url) { return new Promise((resolve, reject) => { loader.load(url, resolve, undefined, reject); }); }`。
- 异步/Promise:在 `async function init()` 中 `try { const gltf = await loadModel(model.glb); scene.add(gltf.scene); } catch (error) { console.error(Failed to load model:, error); }`。
- 异步/Promise:用 `Promise.all` 并行加载多个资源,例如 `[loadGLTF(model.glb), loadRGBE(environment.hdr), loadTexture(color.jpg)]`,随后赋给 `scene.add(modelGltf.scene)`、`scene.environment = envTexture`、`material.map = colorTexture`。
- 异步/Promise:实现 `loadGLTF(url)`,内部用 `new GLTFLoader().load(url, resolve, undefined, reject)`。
- 异步/Promise:实现 `loadRGBE(url)`,在 RGBELoader 回调中设置 `texture.mapping = THREE.EquirectangularReflectionMapping` 后 resolve,并传入 reject。
- 异步/Promise:实现 `loadTexture(url)`,内部用 `new THREE.TextureLoader().load(url, resolve, undefined, reject)`。
- 缓存:开启内建缓存 `THREE.Cache.enabled = true`。
- 缓存:用 `THREE.Cache.clear()` 清空缓存。
- 缓存:手动管理缓存,使用 `THREE.Cache.add(key, data)`、`THREE.Cache.get(key)`、`THREE.Cache.remove(key)`。
- 自定义资源管理器:实现 `AssetManager`,在 constructor 中创建 `this.textures = new Map()`、`this.models = new Map()`、`this.gltfLoader = new GLTFLoader()`、`this.textureLoader = new THREE.TextureLoader()`。
- 自定义资源管理器:实现 `loadTexture(key, url)`,若 `this.textures.has(key)` 则返回缓存;否则用 Promise 加载,存入 `this.textures` 后返回。
- 自定义资源管理器:实现 `loadModel(key, url)`,若 `this.models.has(key)` 则返回 `this.models.get(key).clone()`;否则用 Promise 加载 gltf,存入 `gltf.scene` 到 `this.models`,返回 clone。
- 自定义资源管理器:实现 `dispose()`,遍历 `this.textures` 调用 `t.dispose()`,执行 `this.textures.clear()` 和 `this.models.clear()`。
- 自定义资源管理器:使用 `const assets = new AssetManager()`,`await assets.loadTexture(brick, brick.jpg)`,`await assets.loadModel(tree, tree.glb)`。
- 不同来源:Data URL/Base64 可直接传给 `THREE.TextureLoader`,例如 `loader.load(data:image/png;base64,iVBORw0KGgo...)`。
- 不同来源:Blob URL 先用 `URL.createObjectURL(blob)`,再用 `loadTexture(url)` 加载,完成后调用 `URL.revokeObjectURL(url)`。
- 不同来源:ArrayBuffer 用 `fetch(model.glb)` 后 `await response.arrayBuffer()`,再用 `GLTFLoader.parse(buffer, , (gltf) => scene.add(gltf.scene))` 解析。
- 不同来源:用 `loader.setPath(assets/models/)` 设置基础路径后 `loader.load(model.glb)`;用 `loader.setResourcePath(assets/textures/)` 设置模型内引用的纹理路径。
- 不同来源:用 `manager.setURLModifier((url) => https://cdn.example.com/${url})` 自定义 URL 修饰器。
- 错误处理:实现 `loadWithFallback(primaryUrl, fallbackUrl)`,先 `try` 加载主资源,失败后 `console.warn` 并 `return await loadModel(fallbackUrl)`。
- 错误处理:实现 `loadWithRetry(url, maxRetries = 3)`,循环尝试 `return await loadModel(url)`;捕获错误后若不是最后一次则 `await new Promise((r) => setTimeout(r, 1000 * (i + 1)))`。
- 错误处理:实现 `loadWithTimeout(url, timeout = 30000)`,创建 `AbortController`,用 `setTimeout(() => controller.abort(), timeout)`,在 `fetch(url, { signal: controller.signal })` 后 `clearTimeout(timeoutId)`;捕获 `AbortError` 时抛出 `Loading timed out`。
- 性能:使用压缩格式,几何用 DRACO,纹理用 KTX2/Basis。
- 性能:渐进加载,加载期间显示 placeholder。
- 性能:懒加载,只加载当前需要的资源。
- 性能:使用 CDN 加速资源交付。
- 性能:开启缓存,设置 `THREE.Cache.enabled = true`。
- 性能:渐进加载示例:先创建 wireframe placeholder,`new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ wireframe: true }))` 并 `scene.add(placeholder)`;`loadModel(model.glb).then((gltf) => { scene.remove(placeholder); scene.add(gltf.scene); })`。
适用边界 · 注意事项
- 不适用于不使用 Three.js、或非浏览器/非 WebGL 3D 渲染的项目。
- 不适用于只是加载普通 2D 图片且不需要 Three.js 纹理、材质或场景能力的场景。
- 需要 DRACO/KTX2 压缩资源时,如果无法提供对应 decoder/transcoder 文件或无法访问/本地托管其路径,不应直接使用对应加载流程。