赞
踩
在物联网、数字孪生Web3D可视化开发的项目中,往往需要调整相机视角或位置,近距离查看预览3D场景中的某个设备。
Web3D在线体验地址:http://www.webgl3d.cn/3D/gongchang/index.html
视频思路讲解:https://www.bilibili.com/video/BV1sj411H7sC/
首先用了tweenjs补间动画扩展库,辅助threejs实现相机位置的逐渐改变,生成相机的飞行动画,逐步飞行靠近某个设备。
const A = model.getObjectByName('设备A标注'); const pos = new THREE.Vector3(); A.getWorldPosition(pos); //获取三维场景中某个对象世界坐标 // 相机飞行到的位置和观察目标拉开一定的距离 const pos2 = pos.clone().addScalar(30);//向量的x、y、z坐标分别在pos基础上增加30 // 相机从当前位置camera.position飞行三维场景中某个世界坐标附近 new TWEEN.Tween({ // 相机开始坐标 x: camera.position.x, y: camera.position.y, z: camera.position.z, // 相机开始指向的目标观察点 tx: 0, ty: 0, tz: 0, }) .to({ // 相机结束坐标 x: pos2.x, y: pos2.y, z: pos2.z, // 相机结束指向的目标观察点 tx: pos.x, ty: pos.y, tz: pos.z, }, 2000) .onUpdate(function (obj) { // 动态改变相机位置 camera.position.set(obj.x, obj.y, obj.z); // 动态计算相机视线 camera.lookAt(obj.tx, obj.ty, obj.tz); }) .start();
更多内容,你可以查看Threejs中文网关于tweenjs的介绍,也可以查看下面对于tweenjs详细的手把手介绍。
TweenJS是一个由JavaScript语言编写的补间动画库,如果需要tweenjs辅助你生成动画,对于任何前端web项目,你都可以选择tweenjs库。
如果你使用three.js开发web3d项目,使用tween.js辅助three.js生成动画效果也是比较好的选择。
在工程化开发的时候可以通过npm命令行安装tween.js模块。
npm i @tweenjs/tween.js@^18
import TWEEN from '@tweenjs/tween.js';
tween.js-master
文件包/dist
目录下有多个js文件,如果你想script标签直接引入tween.umd.js
即可。
<script src="./tween.js-master/dist/tween.umd.js"></script>
.html学习环境模拟开发环境中引入方式,就可以和开发环境一样书写import TWEEN from '@tweenjs/tween.js'
。
<!-- type="importmap"功能:tween在html学习环境和开发环境一样写法 -->
<script type="importmap">
{
"imports": {
"@tweenjs/tween.js": "./tween.esm.js"
}
}
</script>
<script type="module">
import TWEEN from '@tweenjs/tween.js';
</script>
tweenjs功能从语法的角度讲,就是改变自己的参数对象。
const pos = {x: 0,y: 0};
const tween = new TWEEN.Tween(pos);//创建一段tween动画
//经过2000毫秒,pos对象的x和y属性分别从零变化为100、50
tween.to({x: 100,y: 50}, 2000);
//tween动画开始执行
tween.start();
在requestAnimationFrame动画中,tween更新.update()
,tween才能正常执行
function loop() {
requestAnimationFrame(loop);
}
loop();
function loop() {
TWEEN.update();//tween更新
requestAnimationFrame(loop);
}
浏览器控制台测试查看tweenjs是否逐渐改变pos对象的x和y属性
function loop() {
TWEEN.update();
// 测试tweenjs是否逐渐改变pos对象的x和y属性
console.log(pos.x,pos.y);
requestAnimationFrame(loop);
}
three.js模型的位置mesh.position
属性是一个具有.x
、.y
、.z
属性的对象,可以直接使用tweenjs直接改变。
//创建一段mesh平移的动画
const tween = new TWEEN.Tween(mesh.position);
//经过2000毫秒,pos对象的x和y属性分别从零变化为100、50
tween.to({x: 100,y: 50}, 2000);
//tween动画开始执行
tween.start();
最后不要忘记在渲染循环中更新TWEEN.update();
即可。
// 渲染循环
function render() {
TWEEN.update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
换个语法形式书写也可以,更简洁
const tween = new TWEEN.Tween(mesh.position).to({x: 100,y: 50}, 2000).start();
const tween = new TWEEN.Tween(mesh.position)
.to({x: 100,y: 50}, 2000)
.start();
模型的缩放属性mesh.scale
和.position
属性一样是一个具有.x
、.y
、.z
属性的对象,你也可以直接用tweenjs动画控制。
new TWEEN.Tween(mesh.scale).to({
x: 100,
y: 50
}, 2000).start();
下面给大家讲解如何通过tweenjs实现threejs相机动画,具体说就是使用tweenjs改变相机的位置camera.position
和视线方向。
引入tweenjs,并在requestAnimationFrame
动画中执行TWEEN.update();
更新。
import TWEEN from '@tweenjs/tween.js';
// 渲染循环
function render() {
TWEEN.update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
相机动画:从一个点移动到另一个点
camera.position.set(202, 123, 125);
new TWEEN.Tween(camera.position)
.to({x: 202,y: 123,z: 50}, 3000)
.start()
只改变相机位置,相机默认视线方向保持不变,如果你想重新计算相机视线方向,可以在相机位置改变的过程中不停地执行lookAt()
即可。
camera.position.set(202, 123, 125);
camera.lookAt(0, 0, 0);
new TWEEN.Tween(camera.position)
.to({x: 202,y: 123,z: -350}, 3000)
// tweenjs改变参数对象的过程中,.onUpdate方法会被重复调用执行
.onUpdate(function(){
camera.lookAt(0, 0, 0);
})
.start()
twwenjs库提供了onStart
、onUpdate
、onComplete
等用于控制动画执行的回调函数。
onStart
:动画开始执行触发onUpdate
:动画执行过程中,一直被调用执行onComplete
:动画正常执行完触发.onUpdate(function(obj){})
结构中,obj对应的是new TWEEN.Tween(pos)
的参数对象pos。
const tween = new TWEEN.Tween(pos).to({x: 0}, 4000)
// 开始执行:动画片段tween开始执行的时候触发onStart
.onStart(function(obj){
...
})
const R = 100; //相机圆周运动的半径
new TWEEN.Tween({angle:0})
.to({angle: Math.PI*2}, 16000)
.onUpdate(function(obj){
camera.position.x = R * Math.cos(obj.angle);
camera.position.z = R * Math.sin(obj.angle);
camera.lookAt(0, 0, 0);
})
.start()
继续上节课相机动画的讲解。
实际开发的的时候,一个较大的三维场景,有很多不同的设备或物品,你可能希望通过UI按钮点击切换到不同视角,观察某个区域,或者说放大观察某个特定的物品或设备。
切换相机位置和视角的按钮
<div class="pos">
<div id="A" class="bu">设备A</div>
<div id="B" class="bu" style="margin-left: 10px;">设备B</div>
<div id="car" class="bu" style="margin-left: 10px;">停车场</div>
<div id="all" class="bu" style="margin-left: 10px;">整体</div>
</div>
点击按钮A,相机运动到工厂中设备A附近,同时把相机观察目标,逐渐切换到设备A
import TWEEN from '@tweenjs/tween.js';
function render() {
TWEEN.update();
requestAnimationFrame(render);
}
render();
如果你希望相机移动到场景中某个位置附近,可以在Blender三维建模中,创建一个空对象进行标注,本节课模型用的是原来标注标签的空对象。当然你也可以直接读取某个模型的世界坐标。
获取某个对象世界坐标,作为相机lookAt指向的新目标观察点。
const A = model.getObjectByName('设备A标注');
const pos = new THREE.Vector3();
//获取三维场景中某个对象世界坐标
A.getWorldPosition(pos);
相机位置相对目标观察点,适当偏移,希望观察的范围大,就距离远一点,希望观察的设备显示效果大,就距离设备近一点。
// 向量的x、y、z坐标分别在pos基础上增加30
const pos2 = pos.clone().addScalar(30);
相机的位置逐渐改变,相机的观察目标也逐渐改变。
// 切换到设备A预览状态 document.getElementById('A').addEventListener('click', function () { const A = model.getObjectByName('设备A标注'); const pos = new THREE.Vector3(); A.getWorldPosition(pos); //获取三维场景中某个对象世界坐标 // 相机飞行到的位置和观察目标拉开一定的距离 const pos2 = pos.clone().addScalar(30);//向量的x、y、z坐标分别在pos基础上增加30 // 相机从当前位置camera.position飞行三维场景中某个世界坐标附近 new TWEEN.Tween({ // 相机开始坐标 x: camera.position.x, y: camera.position.y, z: camera.position.z, // 相机开始指向的目标观察点 tx: 0, ty: 0, tz: 0, }) .to({ // 相机结束坐标 x: pos2.x, y: pos2.y, z: pos2.z, // 相机结束指向的目标观察点 tx: pos.x, ty: pos.y, tz: pos.z, }, 2000) .onUpdate(function (obj) { // 动态改变相机位置 camera.position.set(obj.x, obj.y, obj.z); // 动态计算相机视线 camera.lookAt(obj.tx, obj.ty, obj.tz); }) .start(); })
学下下面内容可以参考前面:6.4. OrbitControls辅助设置相机参数
如果你在项目中使用了相机控件OrbitControls
,希望相机looAt()
指向的目标改变以后,该相机控件让然可以正常使用。需要在动画结束.onComplete()
的时候重新设置controls.target
,或者.onUpdate()
更新controls.target
。
.onUpdate(function (obj) {
...
camera.lookAt(obj.tx, obj.ty, obj.tz);
})
.onComplete(function(obj){
controls.target.set(obj.tx, obj.ty, obj.tz);
controls.update();
})
或者.onUpdate()
中,设置controls.target
,并执行controls.update()
,OrbitControls
相机控件内部也会执行相机的.looAt()
方法,完整相机视线重新计算,这样就不用执行camera.lookAt(obj.tx, obj.ty, obj.tz)
。
实际开发,相机目标观察点初始状态,不一定就是坐标原点,再设置动画初始目标观察点的时候,可以直接访问controls.target
的x、y、z属性获取。
.onUpdate(function (obj) {
// 动态改变相机位置
camera.position.set(obj.x, obj.y, obj.z);
// 动态计算相机视线
// camera.lookAt(obj.tx, obj.ty, obj.tz);
controls.target.set(obj.tx, obj.ty, obj.tz);
controls.update();
})
这样所有的按钮点击后,都可以调用该函数。
动画开始的相机位置和目标观察点,不要手写具体数字,通过相机对象camera.position
和相机控件对象读取controls.target
,这样不管你点击那个按钮,动画开始状态都是上次相机动画结束的状态。
// 相机动画函数,从A点飞行到B点,A点表示相机当前所处状态 // pos: 三维向量Vector3,表示动画结束相机位置 // target: 三维向量Vector3,表示相机动画结束lookAt指向的目标观察点 function createCameraTween(endPos,endTarget){ new TWEEN.Tween({ // 不管相机此刻处于什么状态,直接读取当前的位置和目标观察点 x: camera.position.x, y: camera.position.y, z: camera.position.z, tx: controls.target.x, ty: controls.target.y, tz: controls.target.z, }) .to({ // 动画结束相机位置坐标 x: endPos.x, y: endPos.y, z: endPos.z, // 动画结束相机指向的目标观察点 tx: endTarget.x, ty: endTarget.y, tz: endTarget.z, }, 2000) .onUpdate(function (obj) { // 动态改变相机位置 camera.position.set(obj.x, obj.y, obj.z); // 动态计算相机视线 // camera.lookAt(obj.tx, obj.ty, obj.tz); controls.target.set(obj.tx, obj.ty, obj.tz); controls.update();//内部会执行.lookAt() }) .start(); }
设置设备A、设备B、停车场、整体预览四个按钮对应的相机动画,这样你可以在4个按钮之间,随意切换相机的观察状态。
// 切换到设备A预览状态 document.getElementById('A').addEventListener('click', function () { const A = model.getObjectByName('设备A标注'); const pos = new THREE.Vector3(); A.getWorldPosition(pos); //获取三维场景中某个对象世界坐标 // 相机飞行到的位置和观察目标拉开一定的距离 const pos2 = pos.clone().addScalar(30); createCameraTween(pos2, controls.target) }) // 切换到设备B的预览状态 document.getElementById('B').addEventListener('click', function () { const B = model.getObjectByName('设备B标注'); const pos = new THREE.Vector3(); B.getWorldPosition(pos); //获取三维场景中某个对象世界坐标 // 相机飞行到的位置和观察目标拉开一定的距离 const pos2 = pos.clone().addScalar(30); // 相机从当前位置camera.position飞行三维场景中某个世界坐标附近 createCameraTween(pos2, controls.target) }) // 切换到设备停车场的预览状态 document.getElementById('car').addEventListener('click', function () { const car = model.getObjectByName('停车场标注'); const pos = new THREE.Vector3(); car.getWorldPosition(pos); //获取三维场景中某个对象世界坐标 // 相机飞行到的位置和观察目标拉开一定的距离 const pos2 = pos.clone().addScalar(30); // 相机从当前位置camera.position飞行三维场景中某个世界坐标附近 createCameraTween(pos2, pos) }) // 相机整体预览对应的位置和观察目标 const cameraPos0 = new THREE.Vector3(202, 123, 125) const target0 = new THREE.Vector3(0, 0, 0); // 切换整体预览状态 document.getElementById('all').addEventListener('click', function () { // 相机从当前位置camera.position回到整体预览状态 createCameraTween(cameraPos0, target0) })
本节课内容,其实和上节课的“3.点按钮,相机飞行靠近观察设备”基本相似,你可以当做练习题。
具体交互效果就是鼠标点击选中某个设备,相机靠近该设备,特别关注,设备在屏幕上呈现放大显示的效果。
下面在前面课程射线拾取模型弹出标签的代码基础上给大家讲解。演示文件已经提前引入tweenjs动画库和上节课封装的相机动画代码。
chooseObj
是鼠标单击射线拾取的模型对象,你可以获取该模型对象世界坐标对应的某个空对象位置,生成相机动画。
chooseObj
是鼠标单击射线拾取的模型对象,你可以获取该模型对象世界坐标,生成相机动画。不过有一点要注意在Blender中,你要设置好选中设备的局部坐标系,确保局部坐标系,在设备上某个位置,比如居中,比如顶部,根据需要自定义设置,不过不要偏差太大,比如设备的局部坐标系与设备本身距离很远。
const pos = new THREE.Vector3();
chooseObj.getWorldPosition(pos); //获取三维场景中某个对象世界坐标
通过一个空对象来掌控相机动画的观察目标。
const pos = new THREE.Vector3();
//获取三维场景中某个对象世界坐标
model.getObjectByName(chooseObj.name+'标注').getWorldPosition(pos);
生成相机动画
// 相机飞行到的位置和观察目标拉开一定的距离
const pos2 = pos.clone().addScalar(30);
// 相机从当前位置camera.position飞行三维场景中某个世界坐标附近
createCameraTween(pos2, pos)
原来标签关闭按钮代码
// 鼠标单击按钮,关闭HTML标签
document.getElementById('close').addEventListener('click', function () {
if (chooseObj) { //把原来选中模型对应的标签和发光描边隐藏
outlinePass.selectedObjects = []; //无发光描边
chooseObj.remove(tag); //从场景移除
}
})
关闭设备标签,相机回到整体预览状态
// 相机整体预览对应的位置和观察目标
const cameraPos0 = new THREE.Vector3(202, 123, 125)
const target0 = new THREE.Vector3(0, 0, 0);
// 鼠标单击按钮,关闭HTML标签
document.getElementById('close').addEventListener('click', function () {
if (chooseObj) { //把原来选中模型对应的标签和发光描边隐藏
outlinePass.selectedObjects = []; //无发光描边
chooseObj.remove(tag); //从场景移除
// 相机从当前位置camera.position回到整体预览状态
createCameraTween(cameraPos0, target0)
}
})
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。