本文将介绍

  1. 如何使用ThreeJS导入obj和带贴图mtl的外部obj模型;
  2. 使用AmbientLight为场景所有物体添加基础光源;
  3. 使用SpotLight为场景添加聚光灯效果,并为obj添加阴影效果;
  4. 使用SpotLightHelper,为聚光灯添加光源调试辅助线;
  5. 使用GUI,添加一些简单的属性操作obj模型;

本文代码结构将在之前的文章 VUE整合ThreeJS并创建一个带动画的简单场景 代码的基础上进行修改。

效果预览

一、导入外部模型OBJ

首先引入所需的包

import { MTLLoader, OBJLoader } from 'three-obj-mtl-loader'

准备好模型文件

obj是模型文件,mtl是贴图文理描述文件,png是mtl中引用的贴图文件;

这里需要注意的是,mtl中引用贴图文件的路径。如果导入后发现,模型是全白色或者全黑色的,那么很有可能是这个地方不对。mtl是可以修改的,因为我们是VUE,为了方便测试,就指定了一个vue能加载到的路径。

主要代码

      // 实例化obj loader
      this.objLoader = new OBJLoader()
      // 实例化mtl loader
      this.mtlLoader = new MTLLoader()
      const that = this

      // 加载mtl
      this.mtlLoader.load('objs/jeep/jeepCar.mtl', function(materials) {
        materials.preload()
        that.objLoader.setMaterials(materials)
        // 加载obj
        that.objLoader.load('objs/jeep/jeepCar.obj', function(obj) {
          // 模型文件太大,缩小一下比例,方便显示
          obj.scale.set(0.1, 0.1, 0.1)
          // 设置可以投影
          obj.children.forEach(item => {
            item.castShadow = true
            item.receiveShadow = true
          })
          that.jeepCar = obj
          // 添加到场景
          that.scene.add(that.jeepCar)
        })
      })

      // 加载不带mtl的obj
      const otherObjLoad = new OBJLoader()
      otherObjLoad.load('objs/jeep/jeepCar.obj', function(obj) {
        obj.scale.set(0.1, 0.1, 0.1)
        for (const k in obj.children) {
          obj.children[k].castShadow = true
          obj.children[k].receiveShadow = true
        }
        obj.scale.set(0.1, 0.1, 0.1)
        obj.position.x = 50
        obj.position.z = 30
        that.scene.add(obj)
      })
      // 创建一个几何平面,作为地板,400,400
      const planeGeometry = new THREE.PlaneGeometry(400, 400)
      // 创建带颜色材质,更换为MeshLambertMaterial材质,去掉网格结构
      const planeMaterial = new THREE.MeshLambertMaterial({ color: 'rgb(110,110,110)' })
      this.plane = new THREE.Mesh(planeGeometry, planeMaterial)

      // 平面开启接收阴影效果
      this.plane.receiveShadow = true

      // 设置平面角度和位置
      this.plane.rotation.x = -0.5 * Math.PI
      this.plane.position.x = 0
      this.plane.position.y = 0
      this.plane.position.z = 0
      // 添加平面
      this.scene.add(this.plane)

运行代码时,浏览器会报错:THREE.Loader: Handlers.get() has been removed. Use LoadingManager.getHandler() instead;

解决办法需要修改node_module > three-obj-mtl-loader > index.js代码,在第543行;如果你用的是webstorm,可以直接CTRL+G,定位543行;

注释掉

var loader = THREE.Loader.Handlers.get( url );

在第545行添加

var loader = manager.getHandler(url);

本文中使用的three-obj-mtl-loader的版本是1.0.3,不知道以后会不会更新版本来解决这个问题。如果有自动化部署的需求,可以将这个文件本地化临时解决一下。

 

浏览器警告addAttribute:

原因是BufferGeometry的.addAttribute()方法已经更名为.setAttribute();

要么修改源码、要么等新版本、要么关了浏览器警告。

二、添加光源

AmbientLight:基本光源,该光源的颜色会叠加到场景现有物体的颜色上,一般应用到全局,没有特别的来源方向,不会产生投影,不能作为场景唯一的光源。

SpotLight:聚光灯光源,类似台灯、吊灯、手电筒之类的光源,可以投影。使用SpotLight需要注意设置光的距离,否则即使发光了,因为距离不够而没到物体上,物体也不会产生阴影。

主要代码

      // 为场景所有物体添加基础光源
      const ambientLight = new THREE.AmbientLight(0xffffff, 2)
      this.scene.add(ambientLight)

      // 添加聚光灯光源
      const spotLight = new THREE.SpotLight(0xffffff, 2, 1000)
      spotLight.shadow.mapSize.set(2048, 2048)
      spotLight.position.set(-400, 100, -100)
      // 开启投影
      spotLight.castShadow = true
      this.scene.add(spotLight)
      // 添加聚光灯光源辅助线便于调试
      const spotLightHelper = new THREE.SpotLightHelper(spotLight)
      this.scene.add(spotLightHelper)

三、添加GUI菜单

先引入

import * as Dat from 'dat-gui'

我们可以吧吉普车的xy坐标,xyz角度放到ui上,这样就可以通过UI调整吉普车的坐标和角度

初始化代码

      // 为带贴图MTL的OBJ模型添加UI控制(xy坐标,xyz角度)
      this.controls = {
        positionX: -50,
        positionZ: -30,
        rotationX: 0,
        rotationY: 0,
        rotationZ: 0
      }

      const gui = new Dat.GUI()
      // 设置允许操作范围
      gui.add(this.controls, 'positionX', -200, 200)
      gui.add(this.controls, 'positionZ', -200, 200)
      gui.add(this.controls, 'rotationX', -360, 360)
      gui.add(this.controls, 'rotationY', -360, 360)
      gui.add(this.controls, 'rotationZ', -360, 360)

监听代码,需要放在render中

      // UI事件
      if (this.jeepCar) {
        this.jeepCar.position.x = this.controls['positionX']
        this.jeepCar.position.z = this.controls['positionZ']

        // 这里设置的角度,需要转换为弧度
        this.jeepCar.rotation.x = this.angle2Radian(this.controls['rotationX'])
        this.jeepCar.rotation.y = this.angle2Radian(this.controls['rotationY'])
        this.jeepCar.rotation.z = this.angle2Radian(this.controls['rotationZ'])
      }

四、完整代码

import * as THREE from 'three'
import Stats from 'stats-js'
import * as Dat from 'dat-gui'
import TrackballControls from 'three-trackballcontrols'
import { MTLLoader, OBJLoader } from 'three-obj-mtl-loader'

export default {
  name: 'LoadObjMtl',
  data() {
    return {
      step: 0
    }
  },
  mounted() {
    this.renderer = ''
    this.camera = ''
    this.scene = ''
    this.light = ''
    this.gui = ''
    this.axesHelper = ''
    this.stats = ''
    this.trackballControls = ''
    this.clock = ''
    this.jeepCar = ''
    this.controls = ''
    this.objLoader = ''
    this.mtlLoader = ''
    this.plane = ''

    // 执行
    this.execute()
    // 窗口大小变化
    window.onresize = this.onWindowResize
  },
  methods: {
    initScene() {
      this.scene = new THREE.Scene()
    },
    initCamera() {
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 5000)
      // 设置相机位置
      this.camera.position.x = -400
      this.camera.position.y = 400
      this.camera.position.z = 400
      // 设置相机指向的位置 默认0,0,0
      this.camera.lookAt(this.scene.position)
    },
    initHelper() {
      this.axesHelper = new THREE.AxesHelper(100)
      this.scene.add(this.axesHelper)
    },
    initRender() {
      this.renderer = new THREE.WebGLRenderer({ antialias: true })
      this.renderer.setSize(window.innerWidth, window.innerHeight)
      // 告诉渲染器需要阴影效果
      this.renderer.shadowMap.enabled = true
      this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
      // 设置背景色
      this.renderer.setClearColor(new THREE.Color('rgb(61,61,61)'))
      document.querySelector('#container').appendChild(this.renderer.domElement)
    },
    initStats() {
      this.stats = new Stats()
      document.body.appendChild(this.stats['dom'])
    },
    initModel() {
      // 实例化obj loader
      this.objLoader = new OBJLoader()
      // 实例化mtl loader
      this.mtlLoader = new MTLLoader()
      const that = this

      // 加载mtl
      this.mtlLoader.load('objs/jeep/jeepCar.mtl', function(materials) {
        materials.preload()
        that.objLoader.setMaterials(materials)
        // 加载obj
        that.objLoader.load('objs/jeep/jeepCar.obj', function(obj) {
          // 模型文件太大,缩小一下比例,方便显示
          obj.scale.set(0.1, 0.1, 0.1)
          // 设置可以投影
          obj.children.forEach(item => {
            item.castShadow = true
            item.receiveShadow = true
          })
          that.jeepCar = obj
          // 添加到场景
          that.scene.add(that.jeepCar)
        })
      })

      // 加载不带mtl的obj
      const otherObjLoad = new OBJLoader()
      otherObjLoad.load('objs/jeep/jeepCar.obj', function(obj) {
        obj.scale.set(0.1, 0.1, 0.1)
        for (const k in obj.children) {
          obj.children[k].castShadow = true
          obj.children[k].receiveShadow = true
        }
        obj.scale.set(0.1, 0.1, 0.1)
        obj.position.x = 50
        obj.position.z = 30
        that.scene.add(obj)
      })

      // 创建一个几何平面,作为地板,400,400
      const planeGeometry = new THREE.PlaneGeometry(400, 400)
      // 创建带颜色材质,更换为MeshLambertMaterial材质,去掉网格结构
      const planeMaterial = new THREE.MeshLambertMaterial({ color: 'rgb(110,110,110)' })
      this.plane = new THREE.Mesh(planeGeometry, planeMaterial)

      // 平面开启接收阴影效果
      this.plane.receiveShadow = true

      // 设置平面角度和位置
      this.plane.rotation.x = -0.5 * Math.PI
      this.plane.position.x = 0
      this.plane.position.y = 0
      this.plane.position.z = 0
      // 添加平面
      this.scene.add(this.plane)
    },
    initLight() {
      // 为场景所有物体添加基础光源
      const ambientLight = new THREE.AmbientLight(0xffffff, 2)
      this.scene.add(ambientLight)

      // 添加聚光灯光源
      const spotLight = new THREE.SpotLight(0xffffff, 2, 1000)
      spotLight.shadow.mapSize.set(2048, 2048)
      spotLight.position.set(-400, 100, -100)
      // 开启投影
      spotLight.castShadow = true
      this.scene.add(spotLight)
      // 添加聚光灯光源辅助线便于调试
      const spotLightHelper = new THREE.SpotLightHelper(spotLight)
      this.scene.add(spotLightHelper)
    },
    initGui() {
      // 为带贴图MTL的OBJ模型添加UI控制(xy坐标,xyz角度)
      this.controls = {
        positionX: -50,
        positionZ: -30,
        rotationX: 0,
        rotationY: 0,
        rotationZ: 0
      }

      const gui = new Dat.GUI()
      // 设置允许操作范围
      gui.add(this.controls, 'positionX', -200, 200)
      gui.add(this.controls, 'positionZ', -200, 200)
      gui.add(this.controls, 'rotationX', -360, 360)
      gui.add(this.controls, 'rotationY', -360, 360)
      gui.add(this.controls, 'rotationZ', -360, 360)
    },
    initControls() {
      this.trackballControls = new TrackballControls(this.camera, this.renderer.domElement)
      this.trackballControls.rotateSpeed = 1.0
      this.trackballControls.zoomSpeed = 1
      this.trackballControls.panSpeed = 1
      this.trackballControls.noZoom = false
      this.trackballControls.noPan = false
      this.trackballControls.staticMoving = true
      this.trackballControls.dynamicDampingFactor = 0.3
      this.trackballControls.keys = [65, 83, 68]
    },
    initClock() {
      this.clock = new THREE.Clock()
    },
    render() {
      this.trackballControls.update(this.clock.getDelta())
      this.stats.update()

      // UI事件
      if (this.jeepCar) {
        this.jeepCar.position.x = this.controls['positionX']
        this.jeepCar.position.z = this.controls['positionZ']

        // 这里设置的角度,需要转换为弧度
        this.jeepCar.rotation.x = this.angle2Radian(this.controls['rotationX'])
        this.jeepCar.rotation.y = this.angle2Radian(this.controls['rotationY'])
        this.jeepCar.rotation.z = this.angle2Radian(this.controls['rotationZ'])
      }

      // render using requestAnimationFrame
      requestAnimationFrame(this.render)
      this.renderer.render(this.scene, this.camera)
    },
    onWindowResize() {
      this.camera.aspect = window.innerWidth / window.innerHeight
      this.camera.updateProjectionMatrix()
      this.renderer.setSize(window.innerWidth, window.innerHeight)
    },
    // 角度转弧度(弧度 = π / 180 * 角度)
    angle2Radian(angle) {
      return Math.PI / 180 * angle
    },
    execute() {
      // 初始化场景
      this.initScene()
      // 初始化摄像头
      this.initCamera()
      // 初始化三维坐标系
      this.initHelper()
      // 初始化辅助UI
      this.initGui()
      // 初始化帧数显示工具
      this.initStats()
      // 初始化时钟工具
      this.initClock()
      // 初始化模型
      this.initModel()
      // 初始化渲染器
      this.initRender()
      // 初始化光源
      this.initLight()
      // 初始化控制器
      this.initControls()
      // 执行渲染
      this.render()
    }

  }
}

 

Logo

前往低代码交流专区

更多推荐