经常会遇到一种Three.js开发需求,点击交互某个模型对象时,需要拉近到模型面前.这个时候就需要了解控制器和摄像机的工作原理.

  • 控制器OrbitControls 当传入摄像机对象后,根据鼠标动态改变摄像机参数
  • 摄像机camera 当在场景对象中创建的透视相机(PerspectiveCamera),这一投影模式被用来模拟人眼所看到的景象,它是3D场景的渲染中使用得最普遍的投影模式。

当业务需求是拉近相机到某个点的时候,我们也需要做2件事

视觉中心点修改

改变控制器的围绕中西点,可以理解为将被点击的物体的中心点设置为控制器的围绕中心点,这样当镜头拉近至目标对象的位置的时候,旋转镜头就可以围绕着该点进行旋转
这里我们把点击的目标对象intsersects[0].object,的位置设置给控制器(controls)的target属性,耗时3秒执行完毕.

  1. new TWEEN.Tween(controls.target)
  2. .to(intsersects[0].object.position, 3000)
  3. .easing(TWEEN.Easing.Sinusoidal.InOut)
  4. .start();

镜头位置修改

修改了视觉的中心点之后,我们还需要进一步的把镜头移动到目标位置的边上.并且让镜头对着需要观察的物体

  1. //- 镜头
  2. new TWEEN.Tween(camera.position)
  3. .to({ x: intsersects[0].object.position.x + 10, y: intsersects[0].object.position.y, z: intsersects[0].object.position.z +10}, 3000)
  4. .easing(TWEEN.Easing.Sinusoidal.InOut)
  5. .onUpdate(() => {
  6. camera.lookAt(intsersects[0].object.position)
  7. })
  8. .onComplete(function () {
  9. camera.lookAt(intsersects[0].object.position)
  10. }).start();

两点之间的距离

这里我只是简单的把目标位置的X轴和Z轴+10进行做镜头偏移,但是实际的场景中会发现,这里有个问题.当镜头在目标位置的不同方向其实是需要往不同的轴进行偏移的,如果只固定把X和Z轴加10,那么有的角度就会出现镜头俯冲过头,然后再转回来对准物体,给人一种晕眩的感觉.目前我在项目中的处理方式是:

  1. camera.position.distanceTo(datas[0].position)

用镜头的位置调用distanceTo方法,传入目标位置,得到一个返回值,就是目标到镜头的距离.然后就出现了我通过X和Z的组合出4种坐标情况,然后找出距离最短的一个情况决定X和Z轴是+10还是-10.虽然方法有点蠢,但是从视觉体验上来讲,完全是能够解决相机拉近时的俯冲过度再转头造成的晕眩感.当然更细化一下方向会发现,X和Z的组合是可以组合出8种情况的.具体的细节优化不在这里讲解.

渲染

最后需要在render函数中,更新控制器即可

  1. function render() {
  2. //执行渲染操作 指定场景、相机作为参数
  3. renderer.render(scene, camera);
  4. requestAnimationFrame(render)
  5. TWEEN.update();
  6. controls.update(); //- 更新控制器
  7. }

关于版本的异常报错问题

由于我的项目是在R119.1版本,在写镜头拉近的时候是没有问题的.而demo最开始在R100版本上写的时候发现在render或者补间动画的回调函数里使用控制器的update函数,总是会报错.且执行该函数后,后续会影响控制器的鼠标对模型的交互操作.然而作为历史版本,我没有进一步去研究解决办法,因此此处只是提一下,在R119.1版本上是一切正常的

番外篇 - 镜头拉近后控制器拾取控制器权

该问题是出现在我的项目中,demo中并没有复现,主要分析是因为拉近后的近距离观察导致的.但在demo中,模型修改到很小依然没有问题.此处先记录解决方案
解决思路:
1.持续判断target位置
2.根据target位置重新设置target的坐标

  1. //- 渲染函数
  2. function render() {
  3. //执行渲染操作 指定场景、相机作为参数
  4. renderer.render(scene, camera);
  5. requestAnimationFrame(render)
  6. TWEEN.update();
  7. controls.update();
  8. // - 重新计算坐标点
  9. if (calculateCamera2TargetDistance() < 10.0) {
  10. setNewTarget();
  11. }
  12. }
  13. // - 计算target位置
  14. function calculateCamera2TargetDistance() {
  15. return Math.sqrt(Math.pow(controls.target.x - controls.object.position.x, 2)
  16. + Math.pow(controls.target.z - controls.object.position.z, 2)
  17. + Math.pow(controls.target.y - controls.object.position.y, 2)
  18. );
  19. }
  20. function calculateCamera2TargetAngle() {
  21. return Math.atan2((controls.target.z - controls.object.position.z), controls.target.x - controls.object.position.x);
  22. }
  23. //- 重新设置target
  24. function setNewTarget() {
  25. let angleDetla = calculateCamera2TargetAngle();
  26. let radius = 0.8;
  27. let tx = controls.target.x + Math.cos(angleDetla) * radius;
  28. let ty = controls.target.z + Math.sin(angleDetla) * radius;
  29. controls.target.x = tx;
  30. controls.target.z = ty;
  31. }