<
>

three.js镜头追踪的移动效果实例

2022-08-22 14:50:43 来源:易采站长站 作者:

目录
达到效果实现思路实现难点1、折现变曲线2、镜头朝向不受控3、镜头位置绑定不受控4、镜头抖动最终实现方法方法一:镜头沿线推进方法二:使用tween动画方法比较其他方法方法一:绘制一条折线+animate镜头推进方法二:绘制多条线段+animate镜头推进方法三:绘制多条线段+tween动画变化镜头方法四:优化方法一,绘制一条折线+animate镜头推进

达到效果

指定一条折线路径,镜头沿着路径向前移动,类似第一视角走在当前路径上。

实现思路

很简单画一条折线路径,将镜头位置动态绑定在当前路径上,同时设置镜头朝向路径正前方。

实现难点

1、折现变曲线

画一条折线路径,通常将每一个转折点标出来画出的THREE.Line,会变成曲线。

难点解答:

    1.1、以转折点分隔,一段一段的直线来画,上一个线段的终点是下一个线段的起点。1.2、画一条折线,在转折点处,通过多加一个点,构成一个特别细微的短弧线。

    2、镜头朝向不受控

    对于controls绑定的camera,修改camera的lookAt和rotation并无反应。

    难点解答:

    相机观察方向camera.lookAt设置无效需要设置controls.target

    3、镜头位置绑定不受控

    对于controls绑定的camera,动态修改camera的位置总存在一定错位。

    难点解答:

    苍天啊,这个问题纠结我好久,怎么设置都不对,即便参考上一个问题控制controls.object.position也不对。

    结果这是一个假的难点,镜头位置是受控的,感觉不受控是因为,设置了相机距离原点的最近距离!!!>

    // 设置相机距离原点的最近距离 即可控制放大限值
    // controls.minDistance = 4
    // 设置相机距离原点的最远距离 即可控制缩小限值
    controls.maxDistance = 40
    

    4、镜头抖动

    镜头抖动,怀疑是设置位置和朝向时坐标被四舍五入时,导致一会上一会下一会左一会右的抖动。

    难点解答:

    开始以为是我整个场景太小了,放大场景,拉长折线,拉远相机,并没有什么用。

    最后发现是在animate()动画中设置相机位置,y坐标加了0.01:

    controls.object.position.set(testList[testIndex].x, testList[testIndex].y + 0.01, testList[testIndex].z)
    

    相机位置坐标和相机朝向坐标不在同一平面,导致的抖动,将+0.01去掉就正常了。

    controls.object.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
    

    最终实现方法

    在此通过两个相机,先观察相机cameraTest的移动路径和转向,再切换成原始相机camera。

    公共代码如下:

    // 外层相机,原始相机
    let camera = null
    // 内层相机和相机辅助线
    let cameraTest = null
    let cameraHelper = null
    // 控制器
    let controls = null
    // 折线点的集合和索引
    let testList = []
    let testIndex = 0
    initCamera () {
      // 原始相机
      camera = new THREE.PerspectiveCamera(45, div3D.clientWidth / div3D.clientHeight, 0.1, 1000)
      camera.position.set(16, 6, 10)
      // scene.add(camera)
      // camera.lookAt(new THREE.Vector3(0, 0, 0))
      // 设置第二个相机
      cameraTest = new THREE.PerspectiveCamera(45, div3D.clientWidth / div3D.clientHeight, 0.1, 1000)
      cameraTest.position.set(0, 0.6, 0)
      cameraTest.lookAt(new THREE.Vector3(0, 0, 0))
      cameraTest.rotation.x = 0
      // 照相机帮助线
      cameraHelper = new THREE.CameraHelper(cameraTest)
      scene.add(cameraTest)
      scene.add(cameraHelper)
    }
    // 初始化控制器
    initControls () {
      controls = new OrbitControls(camera, renderer.domElement)
    }
    

    方法一:镜头沿线推进

    inspectCurveList () {
      let curve = new THREE.CatmullRomCurve3([
        new THREE.Vector3(2.9, 0.6, 7),
        new THREE.Vector3(2.9, 0.6, 1.6),
        new THREE.Vector3(2.89, 0.6, 1.6), // 用于直角转折
        new THREE.Vector3(2.2, 0.6, 1.6),
        new THREE.Vector3(2.2, 0.6, 1.59), // 用于直角转折
        new THREE.Vector3(2.2, 0.6, -5),
        new THREE.Vector3(2.21, 0.6, -5), // 用于直角转折
        new THREE.Vector3(8, 0.6, -5),
        new THREE.Vector3(8, 0.6, -5.01), // 用于直角转折
        new THREE.Vector3(8, 0.6, -17),
        new THREE.Vector3(7.99, 0.6, -17), // 用于直角转折
        new THREE.Vector3(-1, 0.6, -17),
        // new THREE.Vector3(-2, 0.6, -17.01), // 用于直角转折
        new THREE.Vector3(-3, 0.6, -20.4),
        new THREE.Vector3(-2, 0.6, 5)
      ])
      let geometry = new THREE.Geometry()
      let gap = 1000
      for (let i = 0; i < gap; i++) {
        let index = i / gap
        let point = curve.getPointAt(index)
        let position = point.clone()
        curveList.push(position)
        geometry.vertices.push(position)
      }
      // geometry.vertices = curve.getPoints(500)
      // curveList = geometry.vertices
      // let material = new THREE.LineBasicMaterial({color: 0x3cf0fa})
      // let line = new THREE.Line(geometry, material) // 连成线
      // line.name = 'switchInspectLine'
      // scene.add(line) // 加入到场景中
    }
    // 模仿管道的镜头推进
    if (curveList.length !== 0) {
    	if (curveIndex < curveList.length - 20) {
    	  // 推进里层相机
    	  /* cameraTest.position.set(curveList[curveIndex].x, curveList[curveIndex].y, curveList[curveIndex].z)
    	  controls = new OrbitControls(cameraTest, labelRenderer.domElement) */
    	  // 推进外层相机
    	  // camera.position.set(curveList[curveIndex].x, curveList[curveIndex].y + 1, curveList[curveIndex].z)
    	  controls.object.position.set(curveList[curveIndex].x, curveList[curveIndex].y, curveList[curveIndex].z)
    	  controls.target = curveList[curveIndex + 20]
    	  // controls.target = new THREE.Vector3(curveList[curveIndex + 2].x, curveList[curveIndex + 2].y, curveList[curveIndex + 2].z)
    	  curveIndex += 1
    	} else {
    	  curveList = []
    	  curveIndex = 0
    	  this.inspectSwitch = false
    	  this.addRoomLabel()
    	  this.removeLabel()
    	  // 移除场景中的线
    	  // let removeLine = scene.getObjectByName('switchInspectLine')
    	  // if (removeLine !== undefined) {
    	  //   scene.remove(removeLine)
    	  // }
    	  // 还原镜头位置
    	  this.animateCamera({x: 16, y: 6, z: 10}, {x: 0, y: 0, z: 0})
    	}
    }
    

    方法二:使用tween动画

    inspectTween () {
      let wayPoints = [
        {
          point: {x: 2.9, y: 0.6, z: 1.6},
          camera: {x: 2.9, y: 0.6, z: 7},
          time: 3000
        },
        {
          point: {x: 2.2, y: 0.6, z: 1.6},
          camera: {x: 2.9, y: 0.6, z: 1.6},
          time: 5000
        },
        {
          point: {x: 2.2, y: 0.6, z: -5},
          camera: {x: 2.2, y: 0.6, z: 1.6},
          time: 2000
        },
        {
          point: {x: 8, y: 0.6, z: -5},
          camera: {x: 2.2, y: 0.6, z: -5},
          time: 6000
        },
        {
          point: {x: 8, y: 0.6, z: -17},
          camera: {x: 8, y: 0.6, z: -5},
          time: 3000
        },
        {
          point: {x: -2, y: 0.6, z: -17},
          camera: {x: 8, y: 0.6, z: -17},
          time: 3000
        },
        {
          point: {x: -2, y: 0.6, z: -20.4},
          camera: {x: -2, y: 0.6, z: -17},
          time: 3000
        },
        {
          point: {x: -2, y: 0.6, z: 5},
          camera: {x: -3, y: 0.6, z: -17},
          time: 3000
        },
        // {
        //   point: {x: -2, y: 0.6, z: 5},
        //   camera: {x: -2, y: 0.6, z: -20.4}
        // },
        {
          point: {x: 0, y: 0, z: 0},
          camera: {x: -2, y: 0.6, z: 5},
          time: 3000
        }
      ]
      this.animateInspect(wayPoints, 0)
    }
    animateInspect (point, k) {
      let self = this
      let time = 3000
      if (point[k].time) {
        time = point[k].time
      }
      let count = point.length
      let target = point[k].point
      let position = point[k].camera
      let tween = new TWEEN.Tween({
        px: camera.position.x, // 起始相机位置x
        py: camera.position.y, // 起始相机位置y
        pz: camera.position.z, // 起始相机位置z
        tx: controls.target.x, // 控制点的中心点x 起始目标位置x
        ty: controls.target.y, // 控制点的中心点y 起始目标位置y
        tz: controls.target.z // 控制点的中心点z 起始目标位置z
      })
      tween.to({
        px: position.x,
        py: position.y,
        pz: position.z,
        tx: target.x,
        ty: target.y,
        tz: target.z
      }, time)
      tween.onUpdate(function () {
        camera.position.x = this.px
        camera.position.y = this.py
        camera.position.z = this.pz
        controls.target.x = this.tx
        controls.target.y = this.ty
        controls.target.z = this.tz
        // controls.update()
      })
      tween.onComplete(function () {
        // controls.enabled = true
        if (self.inspectSwitch && k < count - 1) {
          self.animateInspect(point, k + 1)
        } else {
          self.inspectSwitch = false
          self.addRoomLabel()
          self.removeLabel()
        }
        // callBack && callBack()
      })
      // tween.easing(TWEEN.Easing.Cubic.InOut)
      tween.start()
    },
    

    方法比较

      方法一:镜头控制简单,但是不够平滑。方法二:镜头控制麻烦,要指定当前点和目标点,镜头切换平滑但不严格受控。

      个人喜欢方法二,只要找好了线路上的控制点,动画效果更佳更容易控制每段动画的时间。

      其他方法

      过程中的使用过的其他方法,仅做记录用。

      方法一:绘制一条折线+animate镜头推进

      // 获取折线点数组
      testInspect () {
      	// 描折线点,为了能使一条折线能直角转弯,特添加“用于直角转折”的辅助点,尝试将所有标为“用于直角转折”的点去掉,折线马上变曲线。
      	let curve = new THREE.CatmullRomCurve3([
      	    new THREE.Vector3(2.9, 0.6, 7),
      	    new THREE.Vector3(2.9, 0.6, 1.6),
      	    new THREE.Vector3(2.89, 0.6, 1.6), // 用于直角转折
      	    new THREE.Vector3(2.2, 0.6, 1.6),
      	    new THREE.Vector3(2.2, 0.6, 1.59), // 用于直角转折
      	    new THREE.Vector3(2.2, 0.6, -5),
      	    new THREE.Vector3(2.21, 0.6, -5), // 用于直角转折
      	    new THREE.Vector3(8, 0.6, -5),
      	    new THREE.Vector3(8, 0.6, -5.01), // 用于直角转折
      	    new THREE.Vector3(8, 0.6, -17),
      	    new THREE.Vector3(7.99, 0.6, -17), // 用于直角转折
      	    new THREE.Vector3(-2, 0.6, -17),
      	    new THREE.Vector3(-2, 0.6, -17.01), // 用于直角转折
      	    new THREE.Vector3(-2, 0.6, -20.4),
      	    new THREE.Vector3(-2, 0.6, 5),
      	])
      	let material = new THREE.LineBasicMaterial({color: 0x3cf0fa})
      	let geometry = new THREE.Geometry()
      	geometry.vertices = curve.getPoints(1500)
      	let line = new THREE.Line(geometry, material) // 连成线
      	scene.add(line) // 加入到场景中
      	testList = geometry.vertices
      }
      // 场景动画-推进相机
      animate () {
        // 模仿管道的镜头推进
        if (testList.length !== 0) {
          if (testIndex < testList.length - 2) {
            // 推进里层相机
            // cameraTest.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
            // controls = new OrbitControls(cameraTest, labelRenderer.domElement)
            // controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
            // testIndex += 1
            // 推进外层相机
            camera.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
            controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
            testIndex += 1
          } else {
            testList = []
            testIndex = 0
          }
        }
      }
      

      说明:

      推进里层相机,相机移动和转向正常,且在直角转弯处,镜头转动>90°再切回90°;

      推进外层相机,镜头突然开始乱切(因为设置了最近距离),且在直角转弯处,镜头转动>90°再切回90°。

      方法二:绘制多条线段+animate镜头推进

      // 获取折线点数组
      testInspect () {
      	let points = [	    [2.9, 7],
      	    [2.9, 1.6],
      	    [2.2, 1.6],
      	    [2.2, -5],
      	    [8, -5],
      	    [8, -17],
      	    [-2, -17],
      	    [-2, -20.4],
      	    [-2, 5]
      	  ]
      	testList = this.linePointList(points, 0.6)
      }
      linePointList (xz, y) {
        let allPoint = []
        for (let i = 0; i < xz.length - 1; i++) {
          if (xz[i][0] === xz[i + 1][0]) {
            let gap = (xz[i][1] - xz[i + 1][1]) / 100
            for (let j = 0; j < 100; j++) {
              allPoint.push(new THREE.Vector3(xz[i][0], y, xz[i][1] - gap * j))
            }
          } else {
            let gap = (xz[i][0] - xz[i + 1][0]) / 100
            for (let j = 0; j < 100; j++) {
              allPoint.push(new THREE.Vector3(xz[i][0] - gap * j, y, xz[i][1]))
            }
          }
        }
        return allPoint
      }
      // 场景动画-推进相机
      animate () {
        // 模仿管道的镜头推进
        if (testList.length !== 0) {
          if (testIndex < testList.length - 2) {
            // 推进里层相机
            // cameraTest.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
            // controls = new OrbitControls(cameraTest, labelRenderer.domElement)
            // controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
            // testIndex += 1
            // 推进外层相机
            camera.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
            controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
            testIndex += 1
          } else {
            testList = []
            testIndex = 0
          }
        }
      }
      

      说明:

      推进里层相机,相机移动和转向正常,直角转弯处突兀,因为是多个线段拼接出来的点;

      推进外层相机,相机移动有些许错位(因为设置了最近距离),相机转向正常,但是直角转弯处突兀,因为是多个线段拼接出来的点。

      方法三:绘制多条线段+tween动画变化镜头

      // 获取折线点数组
      testInspect () {
      	let points = [
              [2.9, 7],
              [2.9, 1.6],
              [2.2, 1.6],
              [2.2, -5],
              [8, -5],
              [8, -17],
              [-2, -17],
              [-2, -20.4],
              [-2, 5]
            ]
          this.tweenCameraTest(points, 0) // tween动画-控制里层相机
          // this.tweenCamera(points, 0) // tween动画-控制外层相机
      }
      // tween动画-控制里层相机
      tweenCameraTest (point, k) {
        let self = this
        let count = point.length
        let derection = 0
        if (cameraTest.position.x === point[k][0]) {
          // x相同
          if (cameraTest.position.z - point[k][1] &gt; 0) {
            derection = 0
          } else {
            derection = Math.PI
          }
        } else {
          // z相同
          if (cameraTest.position.x - point[k][0] &gt; 0) {
            derection = Math.PI / 2
          } else {
            derection = - Math.PI / 2
          }
        }
        cameraTest.rotation.y = derection
        let tween = new TWEEN.Tween({
          px: cameraTest.position.x, // 起始相机位置x
          py: cameraTest.position.y, // 起始相机位置y
          pz: cameraTest.position.z // 起始相机位置z
        })
        tween.to({
          px: point[k][0],
          py: 0.6,
          pz: point[k][1]
        }, 3000)
        tween.onUpdate(function () {
          cameraTest.position.x = this.px
          cameraTest.position.y = this.py
          cameraTest.position.z = this.pz
        })
        tween.onComplete(function () {
          if (k &lt; count - 1) {
            self.tweenCameraTest(point, k + 1)
          } else {
            console.log('结束了!!!!!!')
          }
          // callBack &amp;&amp; callBack()
        })
        // tween.easing(TWEEN.Easing.Cubic.InOut)
        tween.start()
      }
      // tween动画-控制外层相机
      tweenCamera (point, k) {
        let self = this
        let count = point.length
        let derection = 0
        if (camera.position.x === point[k][0]) {
          // x相同
          if (camera.position.z - point[k][1] &gt; 0) {
            derection = 0
          } else {
            derection = Math.PI
          }
        } else {
          // z相同
          if (camera.position.x - point[k][0] &gt; 0) {
            derection = Math.PI / 2
          } else {
            derection = - Math.PI / 2
          }
        }
        camera.rotation.y = derection
        let tween = new TWEEN.Tween({
          px: camera.position.x, // 起始相机位置x
          py: camera.position.y, // 起始相机位置y
          pz: camera.position.z // 起始相机位置z
        })
        tween.to({
          px: point[k][0],
          py: 0.6,
          pz: point[k][1]
        }, 3000)
        tween.onUpdate(function () {
          camera.position.x = this.px
          camera.position.y = this.py
          camera.position.z = this.pz
        })
        tween.onComplete(function () {
          if (k &lt; count - 1) {
            self.tweenCamera(point, k + 1)
          } else {
            console.log('结束了!!!!!!')
          }
          // callBack &amp;&amp; callBack()
        })
        // tween.easing(TWEEN.Easing.Cubic.InOut)
        tween.start()
      }
      

      说明:

      控制里层相机使用tweenCameraTest()方法,相机移动正常,通过rotation.y控制直接转向,转弯时略突兀因为没有动画控制rotation.y转动;

      控制外层相机使用tweenCamera()方法,相机移动有些许错位(因为设置了最近距离),相机转向完全不受控,似乎始终看向坐标原点。

      方法四:优化方法一,绘制一条折线+animate镜头推进

      // 获取折线点数组
      testInspect () {
      	// 描折线点,为了能使一条折线能直角转弯,特添加“用于直角转折”的辅助点,尝试将所有标为“用于直角转折”的点去掉,折线马上变曲线。
      	let curve = new THREE.CatmullRomCurve3([
      	    new THREE.Vector3(2.9, 0.6, 7),
      	    new THREE.Vector3(2.9, 0.6, 1.6),
      	    new THREE.Vector3(2.89, 0.6, 1.6), // 用于直角转折
      	    new THREE.Vector3(2.2, 0.6, 1.6),
      	    new THREE.Vector3(2.2, 0.6, 1.59), // 用于直角转折
      	    new THREE.Vector3(2.2, 0.6, -5),
      	    new THREE.Vector3(2.21, 0.6, -5), // 用于直角转折
      	    new THREE.Vector3(8, 0.6, -5),
      	    new THREE.Vector3(8, 0.6, -5.01), // 用于直角转折
      	    new THREE.Vector3(8, 0.6, -17),
      	    new THREE.Vector3(7.99, 0.6, -17), // 用于直角转折
      	    new THREE.Vector3(-2, 0.6, -17),
      	    new THREE.Vector3(-2, 0.6, -17.01), // 用于直角转折
      	    new THREE.Vector3(-2, 0.6, -20.4),
      	    new THREE.Vector3(-2, 0.6, 5),
      	])
      	let material = new THREE.LineBasicMaterial({color: 0x3cf0fa})
          let geometry = new THREE.Geometry()
          let gap = 500
          for (let i = 0; i < gap; i++) {
              let index = i / gap
              let point = curve.getPointAt(index)
              let position = point.clone()
              testList.push(position) // 通过此方法获取点比curve.getPoints(1500)更好,不信你试试,用getPoints获取,镜头会有明显的俯视效果不知为何。
              geometry.vertices.push(position)
          }
          let line = new THREE.Line(geometry, material) // 连成线
          scene.add(line) // 加入到场景中
      }
      // 场景动画-推进外层相机
      animate () {
        // 模仿管道的镜头推进
        if (testList.length !== 0) {
          if (testIndex < testList.length - 2) {
            // 推进里层相机
            // cameraTest.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
            // controls = new OrbitControls(cameraTest, labelRenderer.domElement)
            // 推进外层相机
            // camera.position.set(testList[testIndex].x, testList[testIndex].y + 0.01, testList[testIndex].z)
            controls.object.position.set(testList[testIndex].x, testList[testIndex].y + 0.01, testList[testIndex].z) // 稍微讲相机位置上移,就不会出现似乎乱切镜头穿过旁边物体的效果。
            controls.target = testList[testIndex + 2]
            // controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
            testIndex += 1
          } else {
            testList = []
            testIndex = 0
          }
        }
      }
      

      说明:

      解决了,直角转弯处,镜头转动>90°再切回90°的问题。

      解决了,推进外层相机镜头乱切的问题。

      但是,相机移动在转弯时有明显的往后闪(因为设置了最近距离),并不是严格跟随折线前进。

      以上就是three.js镜头追踪的移动效果实例的详细内容,更多关于three.js镜头追踪移动的资料请关注易采站长站其它相关文章!

暂时禁止评论

微信扫一扫

易采站长站微信账号