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>;
}
  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值