您的位置:首页 > 游戏 > 游戏 > three.js GLTFLoader加载外部三维模型

three.js GLTFLoader加载外部三维模型

2024/10/6 10:39:30 来源:https://blog.csdn.net/weixin_44242600/article/details/141225031  浏览:    关键词:three.js GLTFLoader加载外部三维模型

在场景、光源、相机、渲染器这些基础都奠定好了之后,也了解了通过three.js的几何体相关API创建简单的立方体、球体等模型,不过复杂的模型,比如一辆轿车、一栋房子、一个仓库,一般需要通过3D建模软件来实现。现在我们来了解用GLTFLoader来加载外部的模型。
在这里插入图片描述

GLTF格式简介

GLTF格式是新2015发布的三维模型格式,随着物联网、WebGL、5G的进一步发展,会有越来越多的互联网项目Web端引入3D元素,你可以把GLTF格式的三维模型理解为.jpg、.png格式的图片一样,现在的网站,图片基本是标配,对于以后的网站来说如果需要展示一个场景,使用3D来替换图片表达也是很正常的事情。图片有很多格式,对于三维模型自然也是如此,Web开发的时候图片会有常用格式,对于Web3D开发也一样,肯定会根据需要选择一个常见的大家都熟悉的格式,随时时间的发展,GLTF必然称为一个极为重要的标准格式。

gltf包含内容

glTF(gl传输格式)是一种开放格式的规范(open format specification),用于更高效地传输、加载3D内容。该类文件以JSON(.gltf)格式或二进制(.glb)格式提供,外部文件存储贴图(.jpg、.png)和额外的二进制数据(.bin)。一个glTF组件几乎包含所有的三维模型相关信息的数据,可传输一个或多个场景, 包括网格、材质、贴图、蒙皮、骨架、变形目标、动画、灯光以及摄像机。

GLTFLoader

three.js GLTFLoader是一个用于加载和解析GLTF格式3D模型文件的工具,‌它是three.js库的一部分,‌基于WebGL技术。‌
通过使用GLTFLoader,‌开发人员可以快速构建出复杂的3D场景,‌并实现交互和动画效果,‌进一步增强场景的真实感和交互性。

引入GLTFLoader.js

在three.js官方文件的 examples/jsm/子文件 loaders/目录下,可以找到一个文件GLTFLoader.js,这个文件就是three.js的一个扩展库,专门用来加载gltf格式模型加载器。

// 引入gltf模型加载库GLTFLoader.js
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

gltf加载器new GLTFLoader()

执行new GLTFLoader()就可以实例化一个gltf的加载器对象。

// 创建GLTF加载器对象
const loader = new GLTFLoader();

gltf加载器方法.load()

通过gltf加载器方法.load()就可以加载外部的gltf模型。

.load( url, onLoad, onProgress, onError)

  • url:包含有.gltf .glb文件路径URL的字符串。
  • onLoad:加载成功完成后将会被调用的函数。返回一个gltf对象,该gltf对象包含.scene.cameras.animations.asset
  • onProgress:加载正在进行过程中会被调用的函数。其参数将会是XMLHttpRequest实例,包含有总字节数.total与已加载的字节数.loaded
  • onError:若在加载过程发生错误,将被调用的函数。该函数接收error来作为参数。

gltf参数内容:
在这里插入图片描述

loader.load('gltf模型.gltf', (gltf)=> {// 加载成功的回调函数console.log('gltf对象场景属性',gltf.scene);// 返回的场景对象gltf.scene插入到threejs场景中scene.add( gltf.scene );},(xhr) => {// 加载过程中的回调函数},(error) => {// 加载错误回调函数}
);

onload函数返回参数gltf.scene

gltf的场景属性gltf.scene,该属性包含的是模型信息,比如几何体BufferGometry、材质Material、网格模型Mesh

通过浏览器控制打印gltf.scene可以看出。

  • 模型父对象节点可以用Object3D对象表示,也可以用组对象Group表示。
  • 通过.children属性可以查看一个父对象模型的的所有子对象。
  • 通过.name属性可以查看模型节点的名称。
    在这里插入图片描述
.getObjectByName()根据.name获取模型节点

three.js加载外部模型,外部模型的名称体现为three.js对象的.name属性,three.js可以通过.getObjectByName()方法,把模型节点的名字.name作为函数参数,快速查找某个模型对象。

// 返回名.name为"HandL"对应的对象
const obj = gltf.scene.getObjectByName("HandL");
console.log('obj', obj); // 控制台查看返回结果
console.log('obj.children', obj.children); 
// obj.children的所有子对象都是Mesh,改变Mesh对应颜色
obj.children.forEach(function (mesh) {mesh.material.color.set(0xffff00);
})

注意:.getObjectByName("HandL")获取对象返回结果,包含了对象本身以及对象的所有子对象。
在这里插入图片描述

// 返回名.name为"HandL"对应的对象
const obj = gltf.scene.getObjectByName("HandL");
console.log('obj.children', obj.children); 
// obj.children的所有子对象都是Mesh,改变Mesh对应颜色
obj.children.forEach(function (mesh) {mesh.material.color.set(0xffff00);
})
递归遍历方法.traverse()

加载一个外部模型,如果你想批量修改每个Mesh的材质,一个一个设置比较麻烦,可以通过递归遍历方法.traverse()批量操作更加方便。

// 递归遍历所有模型节点批量修改材质
gltf.scene.traverse(function(obj) {if (obj.isMesh) { // 判断是否是网格模型console.log('模型节点', obj);console.log('模型节点名字', obj.name);console.log('模型默认材质', obj.material);}
});

threejs解析gltf模型默认材质一般是MeshStandardMaterialMeshPhysicalMaterial,相比较其它网格材质,这两个材质属于PBR物理材质,可以提供更加真实的材质效果。

gltf.scene.traverse(function(obj) {if (obj.isMesh) {// 重新设置材质obj.material = new THREE.MeshLambertMaterial({color:0xffffff,});}
});

onProgress函数

一个可选的进度回调函数,通过此函数计算或者操作显示加载进度。

loader.load('gltf模型.gltf', (gltf)=> {// 加载成功的回调函数scene.add( gltf.scene );},(xhr) => {// 加载过程中的回调函数,计算模型加载进度const num = (xhr.loaded / xhr.total) * 100;console.log(`已加载:${num}%`)},(error) => {// 加载错误回调函数}
);

gltf加载不同文件形式

Blender三维建模软件,可以根据设置,以不同形式导出gltf模型,比如单独导出一个.gltf文件,比如单独导出一个.glb文件,比如导出形式为.gltf + .bin + 贴图多个文件。这些不同形式的gltf模型,加载代码其实没啥区别。

// 单独.gltf文件
loader.load("../../工厂.gltf", function (gltf) { scene.add(gltf.scene);
})
// 单独.glb文件
loader.load("../../工厂.glb", function (gltf) { scene.add(gltf.scene);
})
// .gltf + .bin + 贴图文件
loader.load("../../工厂/工厂.gltf", function (gltf) { scene.add(gltf.scene);
})

纹理贴图颜色偏差解决

纹理对象Texture颜色空间编码属性.encoding有多个属性值,默认值是线性颜色空间THREE.LinearEncoding

  • THREE.LinearEncoding:线性颜色空间,在threejs内部表示数字3000
  • THREE.sRGBEncoding:sRGB颜色空间,在threejs内部表示数字3001
const texture = new THREE.TextureLoader().load('./earth.jpg');
texture.encoding = THREE.LinearEncoding;//默认值
// THREE.LinearEncoding变量在threejs内部表示数字3000
console.log('texture.encoding',texture.encoding);
// 修改为THREE.sRGBEncoding,
texture.encoding = THREE.sRGBEncoding;
// THREE.sRGBEncoding变量在threejs内部表示数字3001
console.log('texture.encoding',texture.encoding);

threejs加载gltf模型,颜色贴图map属性.encoding的默认值是sRGB颜色空间THREE.sRGBEncoding

// 查看gltf所有颜色贴图的.encoding值
gltf.scene.traverse(function(obj) {if (obj.isMesh) {if(obj.material.map){//判断是否存在贴图console.log('.encoding',obj.material.map.encoding);}}
});
// .encoding显示3001,说明是THREE.sRGBEncoding
console.log('.encoding',mesh.material.map.encoding);

webGL渲染器.outputEncoding的默认值是线性空间THREE.LinearEncoding,和纹理对象.encoding默认值一样,如果颜色贴图.encoding的值是THREE.sRGBEncoding,为了避免颜色偏差,.outputEncoding的值也需要设置为THREE.sRGBEncoding

综上所述,three.js加载gltf模型的时候,可能会遇到three.js渲染结果颜色偏差,对于这种情况,你只需要修改WebGL渲染器默认的编码方式.outputEncoding即可。

//解决加载gltf格式模型纹理贴图和原图不一样问题
renderer.outputEncoding = THREE.sRGBEncoding;

注意:最新版本属性名字有改变。渲染器属性名.outputEncoding已经变更为.outputColorSpace
查WebGL渲染器文档,你可以看到.outputColorSpace的默认值就是SRGB颜色空间THREE.SRGBColorSpace,意味着新版本代码中,加载gltf,没有特殊需要,不设置.outputColorSpace也不会引起色差。

//新版本,加载gltf,不需要执行下面代码解决颜色偏差
renderer.outputColorSpace = THREE.SRGBColorSpace;//设置为SRGB颜色空间

完整实例代码

结合React,GLTFLoader示例代码。

import React, { useRef, useEffect } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
const modelUrl = require('@/static/files/RobotExpressive.glb');let scene, camera, renderer, controls;// three.js加载3D场景 模型glb
export default function InitModelPage() {const box = useRef(); // canvas盒子// 加载模型function setGltfModel() {// 导入GlTF模型let gltfLoader = new GLTFLoader();gltfLoader.load(modelUrl, (gltf) => {gltf.scene.traverse(obj => {// 可操作模型if (obj.isMesh) {if(obj.material.map){// 判断是否存在贴图}}});scene.add(gltf.scene);});}// 渲染动画function renderFn() {requestAnimationFrame(renderFn);// 用相机渲染一个场景renderer && renderer.render(scene, camera);}// 监听窗体变化、自适应窗体事件function onWindowResize() {let width = box.current.offsetWidth;let height = box.current.offsetHeight;camera.aspect = width / height;// 更新相机投影矩阵,在相机任何参数被改变以后必须被调用camera.updateProjectionMatrix();renderer.setSize(width, height); // 设置渲染区域尺寸}// 初始化环境、灯光、相机、渲染器useEffect(() => {scene = new THREE.Scene();// 添加光源const ambitlight = new THREE.AmbientLight(0x404040);scene.add(ambitlight)const sunlight = new THREE.DirectionalLight(0xffffff);sunlight.position.set(-20, 1, 1);scene.add(sunlight);// 加载模型setGltfModel();// 获取宽高设置相机和渲染区域大小let width = box.current.offsetWidth;let height = box.current.offsetHeight;let k = width / height;// 投影相机camera = new THREE.PerspectiveCamera(45, k, 0.1, 3000);camera.position.set(5, 10, 25);camera.lookAt(scene.position);// 创建一个webGL对象renderer = new THREE.WebGLRenderer();renderer.setSize(width, height); // 设置渲染区域尺寸renderer.setClearColor(0x333333, 1); // 设置颜色透明度renderer.outputEncoding = THREE.sRGBEncoding; // 解决纹理贴图颜色偏差box.current.appendChild(renderer.domElement);// 监听鼠标事件controls = new OrbitControls(camera, renderer.domElement);// 渲染renderFn();// 监听窗体变化window.addEventListener('resize', onWindowResize, false);}, []);useEffect(() => {return () => {// 清除数据scene = null;camera = null;renderer = null;controls = null;}}, []);return <div style={{ width: '100%', height: '100%' }} ref={box}></div>;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com