经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » three.js » 查看文章
three.js镜头追踪的移动效果实例
来源:jb51  时间:2022/8/22 14:54:48  对本文有异议

达到效果

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

实现思路

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

实现难点

1、折现变曲线

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

难点解答:

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

2、镜头朝向不受控

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

难点解答:

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

3、镜头位置绑定不受控

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

难点解答:

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

结果这是一个假的难点,镜头位置是受控的,感觉不受控是因为,设置了相机距离原点的最近距离!!! 导致转弯时距离太近镜头会往回退着转弯,碰到旁边的东西啊,哭唧唧。

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

4、镜头抖动

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

难点解答:

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

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

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

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

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

最终实现方法

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

公共代码如下:

  1. // 外层相机,原始相机
  2. let camera = null
  3. // 内层相机和相机辅助线
  4. let cameraTest = null
  5. let cameraHelper = null
  6. // 控制器
  7. let controls = null
  8. // 折线点的集合和索引
  9. let testList = []
  10. let testIndex = 0
  11. initCamera () {
  12. // 原始相机
  13. camera = new THREE.PerspectiveCamera(45, div3D.clientWidth / div3D.clientHeight, 0.1, 1000)
  14. camera.position.set(16, 6, 10)
  15. // scene.add(camera)
  16. // camera.lookAt(new THREE.Vector3(0, 0, 0))
  17. // 设置第二个相机
  18. cameraTest = new THREE.PerspectiveCamera(45, div3D.clientWidth / div3D.clientHeight, 0.1, 1000)
  19. cameraTest.position.set(0, 0.6, 0)
  20. cameraTest.lookAt(new THREE.Vector3(0, 0, 0))
  21. cameraTest.rotation.x = 0
  22. // 照相机帮助线
  23. cameraHelper = new THREE.CameraHelper(cameraTest)
  24. scene.add(cameraTest)
  25. scene.add(cameraHelper)
  26. }
  27. // 初始化控制器
  28. initControls () {
  29. controls = new OrbitControls(camera, renderer.domElement)
  30. }

方法一:镜头沿线推进

  1. inspectCurveList () {
  2. let curve = new THREE.CatmullRomCurve3([
  3. new THREE.Vector3(2.9, 0.6, 7),
  4. new THREE.Vector3(2.9, 0.6, 1.6),
  5. new THREE.Vector3(2.89, 0.6, 1.6), // 用于直角转折
  6. new THREE.Vector3(2.2, 0.6, 1.6),
  7. new THREE.Vector3(2.2, 0.6, 1.59), // 用于直角转折
  8. new THREE.Vector3(2.2, 0.6, -5),
  9. new THREE.Vector3(2.21, 0.6, -5), // 用于直角转折
  10. new THREE.Vector3(8, 0.6, -5),
  11. new THREE.Vector3(8, 0.6, -5.01), // 用于直角转折
  12. new THREE.Vector3(8, 0.6, -17),
  13. new THREE.Vector3(7.99, 0.6, -17), // 用于直角转折
  14. new THREE.Vector3(-1, 0.6, -17),
  15. // new THREE.Vector3(-2, 0.6, -17.01), // 用于直角转折
  16. new THREE.Vector3(-3, 0.6, -20.4),
  17. new THREE.Vector3(-2, 0.6, 5)
  18. ])
  19. let geometry = new THREE.Geometry()
  20. let gap = 1000
  21. for (let i = 0; i < gap; i++) {
  22. let index = i / gap
  23. let point = curve.getPointAt(index)
  24. let position = point.clone()
  25. curveList.push(position)
  26. geometry.vertices.push(position)
  27. }
  28. // geometry.vertices = curve.getPoints(500)
  29. // curveList = geometry.vertices
  30. // let material = new THREE.LineBasicMaterial({color: 0x3cf0fa})
  31. // let line = new THREE.Line(geometry, material) // 连成线
  32. // line.name = 'switchInspectLine'
  33. // scene.add(line) // 加入到场景中
  34. }
  35. // 模仿管道的镜头推进
  36. if (curveList.length !== 0) {
  37. if (curveIndex < curveList.length - 20) {
  38. // 推进里层相机
  39. /* cameraTest.position.set(curveList[curveIndex].x, curveList[curveIndex].y, curveList[curveIndex].z)
  40. controls = new OrbitControls(cameraTest, labelRenderer.domElement) */
  41. // 推进外层相机
  42. // camera.position.set(curveList[curveIndex].x, curveList[curveIndex].y + 1, curveList[curveIndex].z)
  43. controls.object.position.set(curveList[curveIndex].x, curveList[curveIndex].y, curveList[curveIndex].z)
  44. controls.target = curveList[curveIndex + 20]
  45. // controls.target = new THREE.Vector3(curveList[curveIndex + 2].x, curveList[curveIndex + 2].y, curveList[curveIndex + 2].z)
  46. curveIndex += 1
  47. } else {
  48. curveList = []
  49. curveIndex = 0
  50. this.inspectSwitch = false
  51. this.addRoomLabel()
  52. this.removeLabel()
  53. // 移除场景中的线
  54. // let removeLine = scene.getObjectByName('switchInspectLine')
  55. // if (removeLine !== undefined) {
  56. // scene.remove(removeLine)
  57. // }
  58. // 还原镜头位置
  59. this.animateCamera({x: 16, y: 6, z: 10}, {x: 0, y: 0, z: 0})
  60. }
  61. }

方法二:使用tween动画

  1. inspectTween () {
  2. let wayPoints = [
  3. {
  4. point: {x: 2.9, y: 0.6, z: 1.6},
  5. camera: {x: 2.9, y: 0.6, z: 7},
  6. time: 3000
  7. },
  8. {
  9. point: {x: 2.2, y: 0.6, z: 1.6},
  10. camera: {x: 2.9, y: 0.6, z: 1.6},
  11. time: 5000
  12. },
  13. {
  14. point: {x: 2.2, y: 0.6, z: -5},
  15. camera: {x: 2.2, y: 0.6, z: 1.6},
  16. time: 2000
  17. },
  18. {
  19. point: {x: 8, y: 0.6, z: -5},
  20. camera: {x: 2.2, y: 0.6, z: -5},
  21. time: 6000
  22. },
  23. {
  24. point: {x: 8, y: 0.6, z: -17},
  25. camera: {x: 8, y: 0.6, z: -5},
  26. time: 3000
  27. },
  28. {
  29. point: {x: -2, y: 0.6, z: -17},
  30. camera: {x: 8, y: 0.6, z: -17},
  31. time: 3000
  32. },
  33. {
  34. point: {x: -2, y: 0.6, z: -20.4},
  35. camera: {x: -2, y: 0.6, z: -17},
  36. time: 3000
  37. },
  38. {
  39. point: {x: -2, y: 0.6, z: 5},
  40. camera: {x: -3, y: 0.6, z: -17},
  41. time: 3000
  42. },
  43. // {
  44. // point: {x: -2, y: 0.6, z: 5},
  45. // camera: {x: -2, y: 0.6, z: -20.4}
  46. // },
  47. {
  48. point: {x: 0, y: 0, z: 0},
  49. camera: {x: -2, y: 0.6, z: 5},
  50. time: 3000
  51. }
  52. ]
  53. this.animateInspect(wayPoints, 0)
  54. }
  55. animateInspect (point, k) {
  56. let self = this
  57. let time = 3000
  58. if (point[k].time) {
  59. time = point[k].time
  60. }
  61. let count = point.length
  62. let target = point[k].point
  63. let position = point[k].camera
  64. let tween = new TWEEN.Tween({
  65. px: camera.position.x, // 起始相机位置x
  66. py: camera.position.y, // 起始相机位置y
  67. pz: camera.position.z, // 起始相机位置z
  68. tx: controls.target.x, // 控制点的中心点x 起始目标位置x
  69. ty: controls.target.y, // 控制点的中心点y 起始目标位置y
  70. tz: controls.target.z // 控制点的中心点z 起始目标位置z
  71. })
  72. tween.to({
  73. px: position.x,
  74. py: position.y,
  75. pz: position.z,
  76. tx: target.x,
  77. ty: target.y,
  78. tz: target.z
  79. }, time)
  80. tween.onUpdate(function () {
  81. camera.position.x = this.px
  82. camera.position.y = this.py
  83. camera.position.z = this.pz
  84. controls.target.x = this.tx
  85. controls.target.y = this.ty
  86. controls.target.z = this.tz
  87. // controls.update()
  88. })
  89. tween.onComplete(function () {
  90. // controls.enabled = true
  91. if (self.inspectSwitch && k < count - 1) {
  92. self.animateInspect(point, k + 1)
  93. } else {
  94. self.inspectSwitch = false
  95. self.addRoomLabel()
  96. self.removeLabel()
  97. }
  98. // callBack && callBack()
  99. })
  100. // tween.easing(TWEEN.Easing.Cubic.InOut)
  101. tween.start()
  102. },

方法比较

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

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

其他方法

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

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

  1. // 获取折线点数组
  2. testInspect () {
  3. // 描折线点,为了能使一条折线能直角转弯,特添加“用于直角转折”的辅助点,尝试将所有标为“用于直角转折”的点去掉,折线马上变曲线。
  4. let curve = new THREE.CatmullRomCurve3([
  5. new THREE.Vector3(2.9, 0.6, 7),
  6. new THREE.Vector3(2.9, 0.6, 1.6),
  7. new THREE.Vector3(2.89, 0.6, 1.6), // 用于直角转折
  8. new THREE.Vector3(2.2, 0.6, 1.6),
  9. new THREE.Vector3(2.2, 0.6, 1.59), // 用于直角转折
  10. new THREE.Vector3(2.2, 0.6, -5),
  11. new THREE.Vector3(2.21, 0.6, -5), // 用于直角转折
  12. new THREE.Vector3(8, 0.6, -5),
  13. new THREE.Vector3(8, 0.6, -5.01), // 用于直角转折
  14. new THREE.Vector3(8, 0.6, -17),
  15. new THREE.Vector3(7.99, 0.6, -17), // 用于直角转折
  16. new THREE.Vector3(-2, 0.6, -17),
  17. new THREE.Vector3(-2, 0.6, -17.01), // 用于直角转折
  18. new THREE.Vector3(-2, 0.6, -20.4),
  19. new THREE.Vector3(-2, 0.6, 5),
  20. ])
  21. let material = new THREE.LineBasicMaterial({color: 0x3cf0fa})
  22. let geometry = new THREE.Geometry()
  23. geometry.vertices = curve.getPoints(1500)
  24. let line = new THREE.Line(geometry, material) // 连成线
  25. scene.add(line) // 加入到场景中
  26. testList = geometry.vertices
  27. }
  28. // 场景动画-推进相机
  29. animate () {
  30. // 模仿管道的镜头推进
  31. if (testList.length !== 0) {
  32. if (testIndex < testList.length - 2) {
  33. // 推进里层相机
  34. // cameraTest.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
  35. // controls = new OrbitControls(cameraTest, labelRenderer.domElement)
  36. // controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
  37. // testIndex += 1
  38. // 推进外层相机
  39. camera.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
  40. controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
  41. testIndex += 1
  42. } else {
  43. testList = []
  44. testIndex = 0
  45. }
  46. }
  47. }

说明:

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

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

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

  1. // 获取折线点数组
  2. testInspect () {
  3. let points = [ [2.9, 7],
  4. [2.9, 1.6],
  5. [2.2, 1.6],
  6. [2.2, -5],
  7. [8, -5],
  8. [8, -17],
  9. [-2, -17],
  10. [-2, -20.4],
  11. [-2, 5]
  12. ]
  13. testList = this.linePointList(points, 0.6)
  14. }
  15. linePointList (xz, y) {
  16. let allPoint = []
  17. for (let i = 0; i < xz.length - 1; i++) {
  18. if (xz[i][0] === xz[i + 1][0]) {
  19. let gap = (xz[i][1] - xz[i + 1][1]) / 100
  20. for (let j = 0; j < 100; j++) {
  21. allPoint.push(new THREE.Vector3(xz[i][0], y, xz[i][1] - gap * j))
  22. }
  23. } else {
  24. let gap = (xz[i][0] - xz[i + 1][0]) / 100
  25. for (let j = 0; j < 100; j++) {
  26. allPoint.push(new THREE.Vector3(xz[i][0] - gap * j, y, xz[i][1]))
  27. }
  28. }
  29. }
  30. return allPoint
  31. }
  32. // 场景动画-推进相机
  33. animate () {
  34. // 模仿管道的镜头推进
  35. if (testList.length !== 0) {
  36. if (testIndex < testList.length - 2) {
  37. // 推进里层相机
  38. // cameraTest.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
  39. // controls = new OrbitControls(cameraTest, labelRenderer.domElement)
  40. // controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
  41. // testIndex += 1
  42. // 推进外层相机
  43. camera.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
  44. controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
  45. testIndex += 1
  46. } else {
  47. testList = []
  48. testIndex = 0
  49. }
  50. }
  51. }

说明:

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

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

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

  1. // 获取折线点数组
  2. testInspect () {
  3. let points = [
  4. [2.9, 7],
  5. [2.9, 1.6],
  6. [2.2, 1.6],
  7. [2.2, -5],
  8. [8, -5],
  9. [8, -17],
  10. [-2, -17],
  11. [-2, -20.4],
  12. [-2, 5]
  13. ]
  14. this.tweenCameraTest(points, 0) // tween动画-控制里层相机
  15. // this.tweenCamera(points, 0) // tween动画-控制外层相机
  16. }
  17. // tween动画-控制里层相机
  18. tweenCameraTest (point, k) {
  19. let self = this
  20. let count = point.length
  21. let derection = 0
  22. if (cameraTest.position.x === point[k][0]) {
  23. // x相同
  24. if (cameraTest.position.z - point[k][1] &gt; 0) {
  25. derection = 0
  26. } else {
  27. derection = Math.PI
  28. }
  29. } else {
  30. // z相同
  31. if (cameraTest.position.x - point[k][0] &gt; 0) {
  32. derection = Math.PI / 2
  33. } else {
  34. derection = - Math.PI / 2
  35. }
  36. }
  37. cameraTest.rotation.y = derection
  38. let tween = new TWEEN.Tween({
  39. px: cameraTest.position.x, // 起始相机位置x
  40. py: cameraTest.position.y, // 起始相机位置y
  41. pz: cameraTest.position.z // 起始相机位置z
  42. })
  43. tween.to({
  44. px: point[k][0],
  45. py: 0.6,
  46. pz: point[k][1]
  47. }, 3000)
  48. tween.onUpdate(function () {
  49. cameraTest.position.x = this.px
  50. cameraTest.position.y = this.py
  51. cameraTest.position.z = this.pz
  52. })
  53. tween.onComplete(function () {
  54. if (k &lt; count - 1) {
  55. self.tweenCameraTest(point, k + 1)
  56. } else {
  57. console.log('结束了!!!!!!')
  58. }
  59. // callBack &amp;&amp; callBack()
  60. })
  61. // tween.easing(TWEEN.Easing.Cubic.InOut)
  62. tween.start()
  63. }
  64. // tween动画-控制外层相机
  65. tweenCamera (point, k) {
  66. let self = this
  67. let count = point.length
  68. let derection = 0
  69. if (camera.position.x === point[k][0]) {
  70. // x相同
  71. if (camera.position.z - point[k][1] &gt; 0) {
  72. derection = 0
  73. } else {
  74. derection = Math.PI
  75. }
  76. } else {
  77. // z相同
  78. if (camera.position.x - point[k][0] &gt; 0) {
  79. derection = Math.PI / 2
  80. } else {
  81. derection = - Math.PI / 2
  82. }
  83. }
  84. camera.rotation.y = derection
  85. let tween = new TWEEN.Tween({
  86. px: camera.position.x, // 起始相机位置x
  87. py: camera.position.y, // 起始相机位置y
  88. pz: camera.position.z // 起始相机位置z
  89. })
  90. tween.to({
  91. px: point[k][0],
  92. py: 0.6,
  93. pz: point[k][1]
  94. }, 3000)
  95. tween.onUpdate(function () {
  96. camera.position.x = this.px
  97. camera.position.y = this.py
  98. camera.position.z = this.pz
  99. })
  100. tween.onComplete(function () {
  101. if (k &lt; count - 1) {
  102. self.tweenCamera(point, k + 1)
  103. } else {
  104. console.log('结束了!!!!!!')
  105. }
  106. // callBack &amp;&amp; callBack()
  107. })
  108. // tween.easing(TWEEN.Easing.Cubic.InOut)
  109. tween.start()
  110. }

说明:

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

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

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

  1. // 获取折线点数组
  2. testInspect () {
  3. // 描折线点,为了能使一条折线能直角转弯,特添加“用于直角转折”的辅助点,尝试将所有标为“用于直角转折”的点去掉,折线马上变曲线。
  4. let curve = new THREE.CatmullRomCurve3([
  5. new THREE.Vector3(2.9, 0.6, 7),
  6. new THREE.Vector3(2.9, 0.6, 1.6),
  7. new THREE.Vector3(2.89, 0.6, 1.6), // 用于直角转折
  8. new THREE.Vector3(2.2, 0.6, 1.6),
  9. new THREE.Vector3(2.2, 0.6, 1.59), // 用于直角转折
  10. new THREE.Vector3(2.2, 0.6, -5),
  11. new THREE.Vector3(2.21, 0.6, -5), // 用于直角转折
  12. new THREE.Vector3(8, 0.6, -5),
  13. new THREE.Vector3(8, 0.6, -5.01), // 用于直角转折
  14. new THREE.Vector3(8, 0.6, -17),
  15. new THREE.Vector3(7.99, 0.6, -17), // 用于直角转折
  16. new THREE.Vector3(-2, 0.6, -17),
  17. new THREE.Vector3(-2, 0.6, -17.01), // 用于直角转折
  18. new THREE.Vector3(-2, 0.6, -20.4),
  19. new THREE.Vector3(-2, 0.6, 5),
  20. ])
  21. let material = new THREE.LineBasicMaterial({color: 0x3cf0fa})
  22. let geometry = new THREE.Geometry()
  23. let gap = 500
  24. for (let i = 0; i < gap; i++) {
  25. let index = i / gap
  26. let point = curve.getPointAt(index)
  27. let position = point.clone()
  28. testList.push(position) // 通过此方法获取点比curve.getPoints(1500)更好,不信你试试,用getPoints获取,镜头会有明显的俯视效果不知为何。
  29. geometry.vertices.push(position)
  30. }
  31. let line = new THREE.Line(geometry, material) // 连成线
  32. scene.add(line) // 加入到场景中
  33. }
  34. // 场景动画-推进外层相机
  35. animate () {
  36. // 模仿管道的镜头推进
  37. if (testList.length !== 0) {
  38. if (testIndex < testList.length - 2) {
  39. // 推进里层相机
  40. // cameraTest.position.set(testList[testIndex].x, testList[testIndex].y, testList[testIndex].z)
  41. // controls = new OrbitControls(cameraTest, labelRenderer.domElement)
  42. // 推进外层相机
  43. // camera.position.set(testList[testIndex].x, testList[testIndex].y + 0.01, testList[testIndex].z)
  44. controls.object.position.set(testList[testIndex].x, testList[testIndex].y + 0.01, testList[testIndex].z) // 稍微讲相机位置上移,就不会出现似乎乱切镜头穿过旁边物体的效果。
  45. controls.target = testList[testIndex + 2]
  46. // controls.target = new THREE.Vector3(testList[testIndex + 2].x, testList[testIndex + 2].y, testList[testIndex + 2].z)
  47. testIndex += 1
  48. } else {
  49. testList = []
  50. testIndex = 0
  51. }
  52. }
  53. }

说明:

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

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

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

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号