Three.js 单模型,组合模型加载

three.js 单模型,组合模型加载

前言

three.js 基础模型加载可查看 threejs.org/ 上面涵盖各种模型的加载器及加载方式,在此不再赘述。

本文主要介绍如何使用vue配合three.js 库实现各种格式3d 模型文件的加载,组合式模型文件的加载,以及如何去更新相机的设置,调整模型的大小缩放去达到最适合观察的角度与大小。

一,模型加载器引入

官网之中提供了各种模型文件所对应的加载器,在此处只是简单列举常用模型加载器,其余加载器的使用方式于此大致无二。

js 复制代码
    import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
    import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
    import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
    import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
    import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
    import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader.js';
    import { ThreeMFLoader } from 'three/examples/jsm/loaders/3MFLoader.js';
    import { AMFLoader } from 'three/examples/jsm/loaders/AMFLoader.js';
    import { GCodeLoader } from 'three/examples/jsm/loaders/GCodeLoader.js';
    import { KMZLoader } from 'three/examples/jsm/loaders/KMZLoader.js';
    import { PCDLoader } from 'three/examples/jsm/loaders/PCDLoader.js';
    import { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader.js';
    import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
    import { ColladaLoader } from 'three/examples/jsm/loaders/ColladaLoader.js';
    import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js';
    
    this.modeLoader = {
        'gltf': () => { return new GLTFLoader() },
        'gcode': () => { return new GCodeLoader() },
        'glb': () => { return new GLTFLoader() },
        'fbx': () => { return new FBXLoader() },
        '3ds': () => { return new TDSLoader() },
        '3mf': () => { return new ThreeMFLoader() },
        'amf': () => { return new AMFLoader() },
        'kmz': () => { return new KMZLoader() },
        'obj': () => { return new OBJLoader() },
        'pcd': () => { return new PCDLoader() },
        'ply': () => { return new PLYLoader() },
        'stl': () => { return new STLLoader() },
        'dae': () => { return new ColladaLoader() },
        'mtl': () => { return new MTLLoader() },
    } 

以上便是常用加载器的列举及其初始化,在此处将各种模型放在一个对象中为了便于后面可以更加方便去通过对象属性的方式拿到此加载器的实例。

二,场景初始化

在此,准备工作便已经全部完成,接下来便开始场景,相机,渲染器,控制器等必不可少的元素的初始化及其属性配置。

js 复制代码
init() {
    this.camera = new Three.PerspectiveCamera(45, this.container.clientWidth / this.container.clientHeight, 0.1, 1000)
    this.camera.position.set(0, 0, 100)
    this.camera.updateProjectionMatrix();
    this.scene = new Three.Scene()
    const color = new Three.Color(0x800080);
    color.convertSRGBToLinear();
    this.renderer = new Three.WebGLRenderer({
        antialias: true,
    })
    this.renderer.setSize(this.container.clientWidth, this.container.clientHeight)
    this.renderer.gammaFactor = 2.2;
    this.renderer.outputEncoding = Three.sRGBEncoding
    this.renderer.setPixelRatio(window.devicePixelRatio);  // 设置设备像素比
    this.container.appendChild(this.renderer.domElement)
    this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    this.controls.enableDamping = true
    this.animate()
}

舞台已经打好了,接下来该有我们的主人公出场了,接下来便是在场景中加载模型文件。

三,单个模型加载

js 复制代码
loadModel() {
    const { type, url } = this.config
    const loader = this.modeLoader[type]()
    if(['gltf','glb'].includes(type)){
        const dracoloader = this.getDracoLoader()
        loader.setDRACOLoader(dracoloader)
    }
    loader.load(url, (object) => {
        let obj = object
        if (['stl', 'ply'].includes(type)) {
            let material = new Three.MeshPhongMaterial({
                color: 0x4eacc2,
                specular: 0x111111,
                shininess: 200
            })
            obj = new Three.Mesh(object, material);
        } else if (['glb', 'gltf', 'kmz', 'dae'].includes(type)) {
            obj = object.scene
        }
        this.scene.add(obj)
        this.initSize(obj)
    })
}

其中根据模型的类型做了处理,对与压缩模型进行了单独处理

const dracoloader = this.getDracoLoader()

loader.setDRACOLoader(dracoloader)

js 复制代码
// 解决压缩模型
const getDracoLoader = () => {
    const dracoloader = new DRACOLoader()
    dracoloader.setDecoderPath("/draco/")
    dracoloader.setDecoderConfig({ type: "js" })
    dracoloader.preload()
    return dracoloader
}

到此,模型就已经出现在场景中了,但是在此还是有很多问题,比如说,模型的大小,有些很大,有些很小甚至在场景中只能看见一个小点只能通过鼠标滚轮去放大,才能让他展示在我们面前,那有没有好的办法让他直接已最好的姿态展示在我们面前呢,当然可以,接下来我们要请出灯光师去给我们的舞台加上灯光,让摄影师去调整相机的角度去让模型更好展示。

灯光加载

  • 环境光(AmbientLight):光没有特定方向,只是整体改变场景的光照明暗。

  • 平行光(DirectionalLight):光就是沿着特定方向发射。这种光的表现像是无限远,从它发出的光线都是平行的。常常用平行光来模拟太阳光的效果。

js 复制代码
setLight() {
    const light1 = new Three.DirectionalLight(0xffffff, 1)
    light1.position.set(0, 0, 1)
    this.scene.add(light1)
    const light2 = new Three.DirectionalLight(0xffffff, 1)
    light2.position.set(0, 2, 100)
    this.scene.add(light2)
    const light3 = new Three.DirectionalLight(0xffffff, 1)
    light3.position.set(0, 0, -10)
    this.scene.add(light3)
    const ambientLight = new Three.AmbientLight(0xffffff, 1)
    this.scene.add(ambientLight)
}

模型大小设置:

js 复制代码
 initSize(obj) {
    let group = obj;
    group.updateMatrixWorld()
    const box = new Three.Box3().setFromObject(group);
    const size = box.getSize(new Three.Vector3());
    const center = box.getCenter(new Three.Vector3());
    const maxSize = Math.max(size.x, size.y, size.z);
    const targetSize = 2.5; // 目标大小
    const scale = targetSize / (maxSize > 1 ? maxSize : .5);
    group.scale.set(scale, scale, scale)
    this.controls.maxDistance = size.length() * 10
    this.camera.lookAt(center)
    this.camera.updateProjectionMatrix();
}

到此,模型就以完美的姿态展现在您的面前了。。。

四,组合模型加载(以obj和mtl 为例)

js 复制代码
loadGroupModel() {
    const { type, url } = this.config
    const typeList = type.split(',')
    let t1 = typeList[0]
    let t2 = typeList[1]
    const loader1 = modeLoader[t1]()
    const loader2 = modeLoader[t2]()
    loader1.load(url[0], (materials) => {
        materials.preload()
        loader2.setMaterials(materials)
        loader2.load(url[1], (object) => {
            scene.add(object)
            initSize(object)
        })
    })
}

全部代码

js 复制代码
import * as Three from 'three'
import { onMounted, reactive, defineComponent, h } from 'vue';
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader.js';
import { ThreeMFLoader } from 'three/examples/jsm/loaders/3MFLoader.js';
import { AMFLoader } from 'three/examples/jsm/loaders/AMFLoader.js';
import { GCodeLoader } from 'three/examples/jsm/loaders/GCodeLoader.js';
import { KMZLoader } from 'three/examples/jsm/loaders/KMZLoader.js';
import { PCDLoader } from 'three/examples/jsm/loaders/PCDLoader.js';
import { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader.js';
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
import { ColladaLoader } from 'three/examples/jsm/loaders/ColladaLoader.js';
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js';

class LoadModel {
    constructor(config, elementId) {
        this.config = config
        this.container = document.getElementById(elementId)
        // 相机
        this.camera
        // 场景
        this.scene
        //渲染器
        this.renderer
        // 控制器
        this.controls
        // 模型
        this.model

        this.modeLoader = {
            'gltf': () => { return new GLTFLoader() },
            'gcode': () => { return new GCodeLoader() },
            'glb': () => { return new GLTFLoader() },
            'fbx': () => { return new FBXLoader() },
            '3ds': () => { return new TDSLoader() },
            '3mf': () => { return new ThreeMFLoader() },
            'amf': () => { return new AMFLoader() },
            'kmz': () => { return new KMZLoader() },
            // 'md2' : () =>{ return new MD2Loader()},
            'obj': () => { return new OBJLoader() },
            'pcd': () => { return new PCDLoader() },
            'ply': () => { return new PLYLoader() },
            'stl': () => { return new STLLoader() },
            'dae': () => { return new ColladaLoader() },
            'mtl': () => { return new MTLLoader() },
        }

        this.init()
        this.setLight()
        this.loadModel()
    }

    animate() {
        requestAnimationFrame(() => this.animate())
        this.renderer.render(this.scene, this.camera)
        this.controls.update()
    }

    init() {
        this.camera = new Three.PerspectiveCamera(45, this.container.clientWidth / this.container.clientHeight, 0.1, 1000)
        this.camera.position.set(0, 0, 100)
        this.camera.updateProjectionMatrix();
        this.scene = new Three.Scene()
        const color = new Three.Color(0x800080);
        color.convertSRGBToLinear();
        this.renderer = new Three.WebGLRenderer({
            antialias: true,
        })
        this.renderer.setSize(this.container.clientWidth, this.container.clientHeight)
        this.renderer.gammaFactor = 2.2;
        this.renderer.outputEncoding = Three.sRGBEncoding
        this.renderer.setPixelRatio(window.devicePixelRatio);  // 设置设备像素比
        this.container.appendChild(this.renderer.domElement)
        this.controls = new OrbitControls(this.camera, this.renderer.domElement)
        this.controls.enableDamping = true
        this.animate()
    }

    setLight() {
        const light1 = new Three.DirectionalLight(0xffffff, 1)
        light1.position.set(0, 0, 1)
        this.scene.add(light1)
        const light2 = new Three.DirectionalLight(0xffffff, 1)
        light2.position.set(0, 2, 100)
        this.scene.add(light2)
        const light3 = new Three.DirectionalLight(0xffffff, 1)
        light3.position.set(0, 0, -10)
        this.scene.add(light3)
        const ambientLight = new Three.AmbientLight(0xffffff, 1)
        this.scene.add(ambientLight)
    }

    // 设置模型大小
    initSize(obj) {
        let group = obj;
        group.updateMatrixWorld()
        const box = new Three.Box3().setFromObject(group);
        const size = box.getSize(new Three.Vector3());
        const center = box.getCenter(new Three.Vector3());
        const maxSize = Math.max(size.x, size.y, size.z);
        const targetSize = 2.5; // 目标大小
        const scale = targetSize / (maxSize > 1 ? maxSize : .5);
        group.scale.set(scale, scale, scale)
        this.controls.maxDistance = size.length() * 10
        this.camera.lookAt(center)
        this.camera.updateProjectionMatrix();
    }


    getDracoLoader() {
        const dracoloader = new DRACOLoader()
        dracoloader.setDecoderPath("/draco/")
        dracoloader.setDecoderConfig({ type: "js" })
        dracoloader.preload()
        return dracoloader
    }

    loadModel() {
        const { type, url } = this.config
        const loader = this.modeLoader[type]()
        if(['gltf','glb'].includes(type)){
            const dracoloader = this.getDracoLoader()
            loader.setDRACOLoader(dracoloader)
        }
        loader.load(url, (object) => {
            let obj = object
            if (['stl', 'ply'].includes(type)) {
                let material = new Three.MeshPhongMaterial({
                    color: 0x4eacc2,
                    specular: 0x111111,
                    shininess: 200
                })
                obj = new Three.Mesh(object, material);
            } else if (['glb', 'gltf', 'kmz', 'dae'].includes(type)) {
                obj = object.scene
            }
            this.scene.add(obj)
            this.initSize(obj)
        })
    }

    loadGroupModel() {
        const { type, url } = this.config
        const typeList = type.split(',')
        let t1 = typeList[0]
        let t2 = typeList[1]
        const loader1 = modeLoader[t1]()
        const loader2 = modeLoader[t2]()
        loader1.load(url[0], (materials) => {
            materials.preload()
            loader2.setMaterials(materials)
            loader2.load(url[1], (object) => {
                scene.add(object)
                initSize(object)
            })
        })
    }
}

export default LoadModel

结语

既然选择了远方,便只顾风雨兼程。

相关推荐
优雅永不过时·11 天前
原生Three.js 和 Cesium.js 案例 。 智慧城市 数字孪生常用功能列表
前端·javascript·低代码·编辑器·智慧城市·webgl·three.js
一粒马豆1 个月前
three.js用粒子使用canvas生成的中文字符位图材质
3d·three.js·canvas·中文字符·粒子动画
一粒马豆1 个月前
three.js实现裸眼双目平行立体视觉
3d·vr·three.js·裸眼双目平行立体视觉
咔咔库奇1 个月前
【three.js】纹理贴图
开发语言·javascript·three.js·贴图·three
程序员小淞1 个月前
1、如何本地部署Threejs官网文档
前端·three.js
布兰妮甜1 个月前
Three.js 扩展与插件:增强3D开发的利器
javascript·3d·three.js·扩展与插件
布兰妮甜1 个月前
Three.js 性能优化:打造流畅高效的3D应用
javascript·3d·性能优化·three.js
MossGrower1 个月前
58. Three.js案例-创建一个带有红蓝配置的半球光源的场景
three.js·webglrender·hemispherelig·spheregeometry
新中地GIS开发老师1 个月前
80个Three.js 3D模型资源
javascript·数码相机·3d·arcgis·three.js·gis开发·地信
布兰妮甜1 个月前
Three.js 数学工具:构建精确3D世界的基石
javascript·3d·three.js·数学工具