今天我们来实现在ThreeJs中实现常用的交互操作,包含原生HTML的DOM操作,以及画布中网格模型的点击拾取,重点理解射线拾取网格模型的概念。最后介绍了在ThreeJs中常用的Tween动画库。
操作DOM改变模型状态
假设有个场景需要点击按钮来改变我们画布中一个网格模型的颜色,很简单,与正常的DOM操作并无区别,只是操作的对象改成了网格模型
<div class="text-light-50 absolute" @click="btnClick">切换颜色</div>
const btnClick = () => {
mesh.material.color.setStyle('red');
};
点击左上角按钮成功改变颜色
然而需求中需要我们点击画面中的方块后进行一些操作该怎么进行呢,可能有小伙伴觉得为mesh添加点击点击事件,其实是不行的,因为此时mesh只是canvas画布中的一个元素,并非html中的dom节点。想要可以点击mesh进行操作,就需要引入我们的射线概念。
Raycaster射线拾取
首先我们为window对象添加一个全局的点击事件
const mesh = new THREE.Mesh(geometry, material) as any;
addEventListener('click', (event) => {
// 将屏幕坐标转为设备坐标
// 1.获取鼠标坐标
const px = event.clientX;
const py = event.clientY;
// 根据公式得到设备坐标,这里的innerWidt、innerHeight根据画布实际宽高设定
const x = (px / window.innerWidth) * 2 - 1;
const y = -(py / window.innerHeight) * 2 + 1;
const raycaster = new THREE.Raycaster();
// 这一步将在鼠标点击处创建一条射线
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
// intersect则是射线所经过拾取的元素,若没选中,则返回空数组
const intersect = raycaster.intersectObjects([mesh]);
console.log(intersect, 'intersect');
});
点击方块,返回结果如下
其中Object为选中的网格模型 point鼠标点击的坐标 uv为uv坐标
这时我们拿到了mesh对象,就可以做一些操作啦
addEventListener('click', (event) => {
const px = event.clientX;
const py = event.clientY;
const x = (px / window.innerWidth) * 2 - 1;
const y = -(py / window.innerHeight) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
const intersect = raycaster.intersectObjects([mesh]);
if (intersect.length > 0) {
const mesh = intersect[0].object;
(mesh as any).material.color.setStyle('yellow');
}
});
这里简单的改变下颜色
Tween动画库
搭配之前的射线拾取,我们来实现点击模型后移动的效果
1.安装
pnpm add @tweenjs/tween.js -S
addEventListener('click', (event) => {
const px = event.clientX;
const py = event.clientY;
const x = (px / window.innerWidth) * 2 - 1;
const y = -(py / window.innerHeight) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
const intersect = raycaster.intersectObjects([mesh]);
if (intersect.length > 0) {
const mesh = intersect[0].object;
(mesh as any).material.color.setStyle('yellow');
// 这里针对mesh的position属性进行动画效果
const tween1 = new TWEEN.Tween(mesh.position)
.to({
x: 100 // 改变x的坐标值
})
.easing(TWEEN.Easing.Linear.None);
tween1.start();
}
});
点击动画并没有生效,我们还需要再render函数中执行.update()方法
function render() {
TWEEN.update();
renderer.render(scene, camera);
css3Renderer.render(scene, camera);
requestAnimationFrame(render);
}
再次点击
让我们再添加一段动画,需求是在x轴移动完成后往y轴移动
const tween1 = new TWEEN.Tween(mesh.position)
.to({
x: 100
})
.easing(TWEEN.Easing.Linear.None);
const tween2 = new TWEEN.Tween(mesh.position)
.to({
z: 100
})
.easing(TWEEN.Easing.Linear.None);
tween1.start()
tween2.start()
此时发现,两段动画同时进行了,并没有实现我们的需求进行分段进行
这时,我们需要添加一个串联操作
const tween1 = new TWEEN.Tween(mesh.position)
.to({
x: 100
})
.easing(TWEEN.Easing.Linear.None);
const tween2 = new TWEEN.Tween(mesh.position)
.to({
z: 100
})
.easing(TWEEN.Easing.Linear.None);
tween1.chain(tween2)
tween1.start()
将两段动画通过chain串联起来,最后执行tween1即可,tween2也会紧随着执行
结语
好了,有关ThreeJs入门阶段的教学就差不多结束了,一共四篇,算是在实战项目中比较常用的技巧,大家想要深入学习的话还是推荐查看官方的文档,并且结合提供的案例进行练习,希望大家共同进步,在当前较为恶劣的环境中多掌握一项技能傍身。