如何用 three.js 的 GLTFLoader 加载 .glb/.gltf 模型并渲染?
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
任务是:在网页应用里把 glTF 2.0 格式的模型(.glb或.gltf)加载进 three.js 场景并渲染出来。three.js 官方手册明确推荐 glTF 作为运行时模型格式,.GLB和.GLTF两种版本都得到了良好的支持(见 Loading 3D Models)。本文按「搭好本地环境 → 导入加载器 → 加载并渲染 → 验证结果」的路径展开,代码来自官方 API 文档与 manual 教程。
准备条件:项目结构与本地服务器
three.js 项目至少需要一个 HTML 文件和一个 JavaScript 文件,纹理、音频和 3D 模型通常放在public/(或叫 static)目录下,这些文件会原样推送到网站(见 Installation 手册)。
官方手册给出两种搭建方式,任选其一即可:
方式一:npm + Vite(手册推荐的默认方式)
- 安装 Node.js,然后在项目目录执行:
npm install --save three npm install --save-dev vite- 运行本地开发服务器:
npx vite- 终端出现类似
http://localhost:5173的地址,在浏览器打开它。此时页面是空白的,说明环境就绪。
方式二:CDN + import map(可选分支)
不装构建工具时,需要在index.html的<head>中加入 import map(<version>需替换为真实版本号,手册示例用的是"v0.149.0",最新版本号以 npm 列表为准):
<script type="importmap"> { "imports": { "three": "https://cdn.jsdelivr.net/npm/three@<version>/build/three.module.js", "three/addons/": "https://cdn.jsdelivr.net/npm/three@<version>/examples/jsm/" } } </script>然后装 Node.js 并运行npx serve .启动本地服务器,打开终端给出的http://localhost:3000。
两种方式都有一个硬性前提:不能直接双击 HTML 文件打开。手册说明,出于安全原因,直接以本地文件方式打开页面时,后面要用到的功能无法正常工作;很多加载 3D 模型时的常见错误都是因为没有正确托管文件。另外,如果用 CDN,所有依赖必须来自同一版本的 three.js 和同一个 CDN,混用不同来源可能导致代码重复加载甚至应用损坏。
导入 GLTFLoader
three.js 默认只内置了少数几个加载器(如ObjectLoader),其余组件——包括GLTFLoader——属于 addons,不需要单独安装,但必须显式导入(见 GLTFLoader API 文档):
import * as THREE from 'three'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';在 Vite 项目中three/addons/由构建工具解析;在 CDN 方式中,它由上面的 import map 映射到examples/jsm/目录。
加载模型并加入场景
GLTFLoader的.load( url, onLoad, onProgress, onError )从给定 URL 开始加载,完成后把结果传给onLoad回调。官方 Loading 3D Models 给出的基本用法:
const loader = new GLTFLoader(); loader.load( 'path/to/model.glb', function ( gltf ) { scene.add( gltf.scene ); }, undefined, function ( error ) { console.error( error ); } );其中'path/to/model.glb'是文档中的占位路径,必须替换成你的模型的实际路径或 URL——url参数也支持 data URI。想拿现成模型做验证时,仓库里的 duck.glb 可以直接复制到你的public/目录作为测试模型。
onLoad收到的gltf对象(API 文档中的LoadObject)包含这些字段:
gltf.scene:Group类型,默认场景,加入场景的就是它;gltf.scenes:Group数组,glTF 资产可能定义多个场景;gltf.animations:AnimationClip数组,模型里的动画剪辑;gltf.asset:资产元数据;gltf.userData:附加数据。
加载完成后还需要渲染循环。下面是与手册示例 load-gltf 一致的最小可运行结构(main.js):
import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; const renderer = new THREE.WebGLRenderer( { antialias: true } ); document.body.appendChild( renderer.domElement ); const camera = new THREE.PerspectiveCamera( 45, 2, 0.1, 100 ); camera.position.set( 0, 10, 20 ); const controls = new OrbitControls( camera, renderer.domElement ); controls.target.set( 0, 5, 0 ); controls.update(); const scene = new THREE.Scene(); scene.background = new THREE.Color( 'black' ); // 场景需要光源,否则模型可能不可见 const hemiLight = new THREE.HemisphereLight( 0xB1E1FF, 0xB97A20, 2 ); scene.add( hemiLight ); const dirLight = new THREE.DirectionalLight( 0xFFFFFF, 2.5 ); dirLight.position.set( 5, 10, 2 ); scene.add( dirLight ); scene.add( dirLight.target ); // 模型 URL:替换成你的实际路径,例如放在 public/ 下的 duck.glb const MODEL_URL = 'path/to/model.glb'; { const gltfLoader = new GLTFLoader(); gltfLoader.load( MODEL_URL, ( gltf ) => { const root = gltf.scene; scene.add( root ); // 计算包围盒并自动把相机框住模型,见下文「验证结果」 const box = new THREE.Box3().setFromObject( root ); const boxSize = box.getSize( new THREE.Vector3() ).length(); const boxCenter = box.getCenter( new THREE.Vector3() ); frameArea( boxSize * 0.5, boxSize, boxCenter, camera ); controls.maxDistance = boxSize * 10; controls.target.copy( boxCenter ); controls.update(); }, undefined, ( error ) => { console.error( error ); } ); } function frameArea( sizeToFitOnScreen, boxSize, boxCenter, camera ) { const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5; const halfFovY = THREE.MathUtils.degToRad( camera.fov * .5 ); const distance = halfSizeToFitOnScreen / Math.tan( halfFovY ); const direction = ( new THREE.Vector3() ) .subVectors( camera.position, boxCenter ) .multiply( new THREE.Vector3( 1, 0, 1 ) ) .normalize(); camera.position.copy( direction.multiplyScalar( distance ).add( boxCenter ) ); camera.near = boxSize / 100; camera.far = boxSize * 100; camera.updateProjectionMatrix(); camera.lookAt( boxCenter.x, boxCenter.y, boxCenter.z ); } function resizeRendererToDisplaySize( renderer ) { const canvas = renderer.domElement; const width = canvas.clientWidth; const height = canvas.clientHeight; const needResize = canvas.width !== width || canvas.height !== height; if ( needResize ) { renderer.setSize( width, height, false ); } return needResize; } function render() { if ( resizeRendererToDisplaySize( renderer ) ) { camera.aspect = renderer.domElement.clientWidth / renderer.domElement.clientHeight; camera.updateProjectionMatrix(); } renderer.render( scene, camera ); requestAnimationFrame( render ); } requestAnimationFrame( render );这段代码里,相机自动取景(frameArea与包围盒计算)和渲染循环都取自 manual 的 GLTF 示例页面。它的作用是在模型加载后根据包围盒调整相机距离与 near/far,避免模型太小或相机位于模型内部而看不到内容——这正是官方排查清单里提到的一个常见现象。OrbitControls也是 addon,导入方式与GLTFLoader相同。
验证结果
- 浏览器里能看到模型:打开 Vite(或 serve)给出的本地地址,模型连同纹理一起渲染出来。manual 教程 Loading a .GLTF File 演示的就是这条流程——替换掉 OBJ 加载代码、导入
GLTFLoader、scene.add( gltf.scene )后,"textures and all" 直接出画面。 - 检查加载出的场景结构:manual 教程在
onLoad回调里调用打印函数输出场景图(scenegraph),确认节点是否按预期组织,例如定位某个命名节点:
gltfLoader.load( MODEL_URL, ( gltf ) => { const root = gltf.scene; scene.add( root ); console.log( dumpObject( root ).join( '\n' ) ); } );dumpObject是 manual 教程给出的完整工具函数(递归打印每个节点的名称、类型与层级,形如RootNode [Object3D]/SomeMesh [Mesh]),可原样取自 manual/pages/load-gltf.html。拿到场景图后,就可以用root.getObjectByName( '节点名' )找出后续要操作的子对象(教程用它找到了名为Cars的节点)。
模型不显示、变形或颜色不对时怎么排查
官方 Loading 3D Models 给出了五条排查步骤,按顺序执行:
- 查控制台报错:检查 JavaScript 控制台,并确保调用
.load()时传了onError回调来记录错误(上文代码已包含)。 - 在别的应用里打开模型:先用其他支持 glTF 的查看器打开模型。如果模型在别的软件里显示正常,向 three.js 提交 bug;如果在任何软件里都显示不出来,向制作模型的工具提交 bug。
- 把模型放大或缩小 1000 倍试试:很多模型的缩放各不相同,相机位于大模型内部时模型不会显示。
- 添加并摆放一个光源:模型可能只是"藏在黑暗里"。
- 在网络面板里找失败的纹理请求:比如出现
"C:\Path\To\Model\texture.jpg"这种绝对路径。应改用相对于模型的路径,如images/texture.jpg——这可能需要用文本编辑器修改模型文件。.gltf本质是 JSON 文件,可以直接打开查看内容;模型引用了绝对路径纹理时,纹理请求就会失败。
可选分支:加载 Draco 压缩的模型
如果模型用KHR_draco_mesh_compression扩展压缩过网格数据,必须给GLTFLoader设置DRACOLoader(setDRACOLoader方法即为此用途)。GLTFLoader API 文档 的示例:
const loader = new GLTFLoader(); // Optional: Provide a DRACOLoader instance to decode compressed mesh data const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath( '/examples/jsm/libs/draco/' ); loader.setDRACOLoader( dracoLoader ); const gltf = await loader.loadAsync( 'models/gltf/duck/duck.gltf' ); scene.add( gltf.scene );注意示例中的setDecoderPath路径与模型 URL 对应的是 three.js 仓库自身的目录结构:解码器文件(draco_decoder.js、draco_decoder.wasm等)位于仓库的examples/jsm/libs/draco/目录,模型 URL 需替换为你自己的资产。在自己的项目中,把解码器文件放到可访问的目录后,将setDecoderPath指向该目录即可。另外两点来自 DRACOLoader 文档:建议创建并复用同一个DRACOLoader实例,避免加载多个解码器;DRACOLoader会根据浏览器能力自动选择 JS 或 WASM 解码库。
同理,KTX2 压缩纹理需要setKTX2Loader,EXT_meshopt_compression压缩的资产需要setMeshoptDecoder,均见 GLTFLoader API 文档 的方法说明。
限制与注意事项
GLTFLoader支持一长串 glTF 2.0 扩展(KHR_draco_mesh_compression、KHR_materials_transmission、KHR_mesh_quantization、EXT_texture_webp等),完整列表以 API 文档为准;部分扩展(如KHR_gaussian_splatting、MSFT_texture_dds)需要另行注册插件才能支持。- glTF 中的位图纹理不会在失去引用后被浏览器自动垃圾回收,需要在销毁(disposal)流程中做特殊处理——模型会频繁切换的应用要留意这一点。
- 手动教程在实践里还提醒了两点边界:glTF 无法覆盖所有情况(例如
.blend里的灯光加载进来后可能不再是灯光);用于实时 3D 的资产,其节点的原点位置和缩放最好在导出前就整理好,运行时再修会很麻烦。这些经验来自 Loading a .GLTF File 的完整过程。
模型加载成功且gltf.animations非空时,数组里的AnimationClip可以直接接入 three.js 的动画系统(AnimationMixer/AnimationAction,见 docs/pages 对应文档),这就是加载完成后自然的下一步。
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考