经常会遇到一种Three.js开发需求,点击交互某个模型对象时,需要拉近到模型面前.这个时候就需要了解控制器和摄像机的工作原理.
- 控制器OrbitControls 当传入摄像机对象后,根据鼠标动态改变摄像机参数
- 摄像机camera 当在场景对象中创建的透视相机(PerspectiveCamera),这一投影模式被用来模拟人眼所看到的景象,它是3D场景的渲染中使用得最普遍的投影模式。
视觉中心点修改
改变控制器的围绕中西点,可以理解为将被点击的物体的中心点设置为控制器的围绕中心点,这样当镜头拉近至目标对象的位置的时候,旋转镜头就可以围绕着该点进行旋转
这里我们把点击的目标对象intsersects[0].object,的位置设置给控制器(controls)的target属性,耗时3秒执行完毕.
new TWEEN.Tween(controls.target)
.to(intsersects[0].object.position, 3000)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start();
镜头位置修改
修改了视觉的中心点之后,我们还需要进一步的把镜头移动到目标位置的边上.并且让镜头对着需要观察的物体
//- 镜头
new TWEEN.Tween(camera.position)
.to({ x: intsersects[0].object.position.x + 10, y: intsersects[0].object.position.y, z: intsersects[0].object.position.z +10}, 3000)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.onUpdate(() => {
camera.lookAt(intsersects[0].object.position)
})
.onComplete(function () {
camera.lookAt(intsersects[0].object.position)
}).start();
两点之间的距离
这里我只是简单的把目标位置的X轴和Z轴+10进行做镜头偏移,但是实际的场景中会发现,这里有个问题.当镜头在目标位置的不同方向其实是需要往不同的轴进行偏移的,如果只固定把X和Z轴加10,那么有的角度就会出现镜头俯冲过头,然后再转回来对准物体,给人一种晕眩的感觉.目前我在项目中的处理方式是:
camera.position.distanceTo(datas[0].position)
用镜头的位置调用distanceTo方法,传入目标位置,得到一个返回值,就是目标到镜头的距离.然后就出现了我通过X和Z的组合出4种坐标情况,然后找出距离最短的一个情况决定X和Z轴是+10还是-10.虽然方法有点蠢,但是从视觉体验上来讲,完全是能够解决相机拉近时的俯冲过度再转头造成的晕眩感.当然更细化一下方向会发现,X和Z的组合是可以组合出8种情况的.具体的细节优化不在这里讲解.
渲染
最后需要在render函数中,更新控制器即可
function render() {
//执行渲染操作 指定场景、相机作为参数
renderer.render(scene, camera);
requestAnimationFrame(render)
TWEEN.update();
controls.update(); //- 更新控制器
}
关于版本的异常报错问题
由于我的项目是在R119.1版本,在写镜头拉近的时候是没有问题的.而demo最开始在R100版本上写的时候发现在render或者补间动画的回调函数里使用控制器的update函数,总是会报错.且执行该函数后,后续会影响控制器的鼠标对模型的交互操作.然而作为历史版本,我没有进一步去研究解决办法,因此此处只是提一下,在R119.1版本上是一切正常的
番外篇 - 镜头拉近后控制器拾取控制器权
该问题是出现在我的项目中,demo中并没有复现,主要分析是因为拉近后的近距离观察导致的.但在demo中,模型修改到很小依然没有问题.此处先记录解决方案
解决思路:
1.持续判断target位置
2.根据target位置重新设置target的坐标
//- 渲染函数
function render() {
//执行渲染操作 指定场景、相机作为参数
renderer.render(scene, camera);
requestAnimationFrame(render)
TWEEN.update();
controls.update();
// - 重新计算坐标点
if (calculateCamera2TargetDistance() < 10.0) {
setNewTarget();
}
}
// - 计算target位置
function calculateCamera2TargetDistance() {
return Math.sqrt(Math.pow(controls.target.x - controls.object.position.x, 2)
+ Math.pow(controls.target.z - controls.object.position.z, 2)
+ Math.pow(controls.target.y - controls.object.position.y, 2)
);
}
function calculateCamera2TargetAngle() {
return Math.atan2((controls.target.z - controls.object.position.z), controls.target.x - controls.object.position.x);
}
//- 重新设置target
function setNewTarget() {
let angleDetla = calculateCamera2TargetAngle();
let radius = 0.8;
let tx = controls.target.x + Math.cos(angleDetla) * radius;
let ty = controls.target.z + Math.sin(angleDetla) * radius;
controls.target.x = tx;
controls.target.z = ty;
}