【threejs教程2】threejs物体点击交互事件

1,427 阅读3分钟

点击.gif

【图片效果完整代码位于文章末】

1.基本原理

在Three.js中,为了实现鼠标与3D物体的点击交互,我们主要利用Raycaster类将鼠标在屏幕上的2D坐标转换为3D空间中的射线,然后检测这条射线是否与场景中的3D物体相交。当射线与物体相交时,即视为点击到了该物体。

2.实现步骤

1.创建两个可以被点击的物体

物体的创建基础祥见上一篇文章threejs基础开发应用示例,这里创建了两个方块,我们给他分别命名为“方块1”,“方块2”,并使他们反方向旋转。

function init() {
    	camera.position.set(0, 0, 5)
    	renderer.setSize(window.innerWidth, window.innerHeight)
    	document.body.appendChild(renderer.domElement)
    	const geometry = new THREE.BoxGeometry(1, 1, 1)
    	const material1 = new THREE.MeshBasicMaterial({
    		color: 0xff00a2d7,
    		transparent: true,
    		opacity: 0.5,
    	})
    	const material2 = new THREE.MeshBasicMaterial({
    		color: 0xffd3e3fd,
    		transparent: true,
    		opacity: 0.5,
    	})
    	const cube1 = new THREE.Mesh(geometry, material1)
    	const cube2 = new THREE.Mesh(geometry, material2)
    	scene.add(cube1, cube2)
    	cube1.position.set(0, 0, 0)
    	cube1.name = '方块1'
    	cube2.position.set(2, 0, 0)
    	cube2.name = '方块2'
    	cube1.position.x = -2
    	controls.update()
    	function animate() {
    		requestAnimationFrame(animate)
    		controls.update()
    		cube1.rotation.y += 0.01
    		cube2.rotation.y -= 0.01
    		renderer.render(scene, camera)
    	}
    	animate()
    }

2.初始化Raycaster与鼠标坐标

    const raycaster = new THREE.Raycaster()
    // 鼠标位置
    const mouse = new THREE.Vector2()

3.创建鼠标点击事件监听

我们需要创建一个鼠标点击事件的监听,然后将鼠标坐标归一化,从而设置射线的起点和方向。当射线与物体相交时,即可获取到鼠标点击的物体。通过对获取到的物体材质进行设置,即可改变物体的颜色等属性。

     // 鼠标点击事件监听
    window.addEventListener('click', mouseClick, false)
    function mouseClick(event) {
    	// 将鼠标坐标归一化
    	mouse.x = (event.clientX / window.innerWidth) * 2 - 1
    	mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
    	// 设置射线起点为鼠标位置,射线的方向为相机视角方向
    	raycaster.setFromCamera(mouse, camera)
    	// 计算射线相交
    	const intersects = raycaster.intersectObjects(scene.children, true)
    	if (intersects.length > 0) {
    		// 选中物体
    		const selectedObject = intersects[0].object
        alert(`点击了${selectedObject.name}`)
    		// 改变当前被点击物体的颜色
    		selectedObject.material.color.set(0xff62e258)
    	} 
    }

4.物体颜色的恢复

通过用一个变量记录上一个被点击的物体状态,可以在点击其他物体时恢复上一个被点击物体的颜色。具体实现如下

   // 记录上一个被点击的对象
    let lastSelectedObject = null
    function mouseClick(event) {
        // 将鼠标坐标归一化
    	mouse.x = (event.clientX / window.innerWidth) * 2 - 1
    	mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
    	// 设置射线起点为鼠标位置,射线的方向为相机视角方向
    	raycaster.setFromCamera(mouse, camera)
    	// 计算射线相交
    	const intersects = raycaster.intersectObjects(scene.children, true)
    	if (intersects.length > 0) {
        // 如果之前有选中的物体,将其颜色恢复为初始状态
    		if (lastSelectedObject) {
    			lastSelectedObject.material.color.set(
    				lastSelectedObject.initialColor
    			)
    		}
    		// 选中物体
    		const selectedObject = intersects[0].object
        alert(`点击了${selectedObject.name}`)
        // 记录当前选中物体的状态
    		selectedObject.initialColor = selectedObject.material.color.clone()
    		lastSelectedObject = selectedObject
    		// 改变当前被点击物体的颜色
    		selectedObject.material.color.set(0xff62e258)
    	} else {
    		// 如果没有新的物体被选中,恢复上一个选中物体的颜色(如果存在的话)
    		if (lastSelectedObject) {
    			lastSelectedObject.material.color.set(
    				lastSelectedObject.initialColor
    			)
    		}
    	}
    }

5.最终完整代码

再添加一些其他显示最终实现代码如下:

<template>
    	<div
    		style="
    			font-size: 24px;
    			color: #ffffff;
    			text-align: center;
    			position: absolute;
    			top: 20%;
    			left: 50%;
    			transform: translate(-50%, -50%);
    		"
    	>
    		{{ msg }}
    	</div>
    </template>
    <script setup>
    import * as THREE from 'three'
    import { onMounted, ref } from 'vue'
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
    const msg = ref('')
    const scene = new THREE.Scene()
    const camera = new THREE.PerspectiveCamera(
    	75,
    	window.innerWidth / window.innerHeight,
    	0.1,
    	1000
    )
    const renderer = new THREE.WebGLRenderer({ antialias: true })
    const controls = new OrbitControls(camera, renderer.domElement)
    onMounted(() => {
    	init()
    })
    function init() {
    	camera.position.set(0, 0, 5)
    	renderer.setSize(window.innerWidth, window.innerHeight)
    	document.body.appendChild(renderer.domElement)
    	const geometry = new THREE.BoxGeometry(1, 1, 1)
    	const material1 = new THREE.MeshBasicMaterial({
    		color: 0xff00a2d7,
    		transparent: true,
    		opacity: 0.5,
    	})
    	const material2 = new THREE.MeshBasicMaterial({
    		color: 0xffd3e3fd,
    		transparent: true,
    		opacity: 0.5,
    	})
    	const cube1 = new THREE.Mesh(geometry, material1)
    	const cube2 = new THREE.Mesh(geometry, material2)
    	scene.add(cube1, cube2)
    	cube1.position.set(0, 0, 0)
    	cube1.name = '方块1'
    	cube2.position.set(2, 0, 0)
    	cube2.name = '方块2'
    	cube1.position.x = -2
    	controls.update()
    	function animate() {
    		requestAnimationFrame(animate)
    		controls.update()
    		cube1.rotation.y += 0.01
    		cube2.rotation.y -= 0.01
    		renderer.render(scene, camera)
    	}
    	animate()
    }
    // 创建射线投射器
    const raycaster = new THREE.Raycaster()
    // 鼠标位置
    const mouse = new THREE.Vector2()
    // 记录上一个被点击的对象
    let lastSelectedObject = null
    // 鼠标点击事件监听
    window.addEventListener('click', mouseClick, false)
    function mouseClick(event) {
    	console.log('点击事件')
    	// 将鼠标坐标归一化
    	mouse.x = (event.clientX / window.innerWidth) * 2 - 1
    	mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
    	// 设置射线起点为鼠标位置,射线的方向为相机视角方向
    	raycaster.setFromCamera(mouse, camera)
    	// 计算射线相交
    	const intersects = raycaster.intersectObjects(scene.children, true)
    	if (intersects.length > 0) {
    		// 如果之前有选中的物体,将其颜色恢复为初始状态
    		if (lastSelectedObject) {
    			lastSelectedObject.material.color.set(
    				lastSelectedObject.initialColor
    			)
    		}
    		// 选中物体
    		const selectedObject = intersects[0].object
    		msg.value = `点击了${selectedObject.name}`
    		// 记录当前选中物体的状态
    		selectedObject.initialColor = selectedObject.material.color.clone()
    		lastSelectedObject = selectedObject
    		selectedObject.material.color.set(0xff62e258)
    	} else {
    		// 如果没有新的物体被选中,恢复上一个选中物体的颜色(如果存在的话)
    		if (lastSelectedObject) {
    			lastSelectedObject.material.color.set(
    				lastSelectedObject.initialColor
    			)
    			msg.value = ''
    		}
    	}
    }
    </script>