前言
在Web
开发中,Three.js
是一个极为强大的库,它让开发者能够轻松地在浏览器中创建和展示3D
图形。随着3D
技术在网页设计、游戏开发、数据可视化等领域的广泛应用,用户与3D场景的交互变得日益重要。而要实现这种交互,一个核心的技术就是光线投射(Raycasting
)。通过Three.js
提供的Raycaster
类,我们可以检测鼠标或触摸事件在3D
空间中的对应位置,进而实现点击、悬停等交互效果。本文将深入探讨如何使用Three.js
的Raycaster
来实现3D
场景的交互事件。
光线投射原理及其属性介绍
通过Raycaster拾取模型进行轮廓高亮走这里>>>
什么是Raycasting?
Raycasting
是一种计算机图形学技术,用于确定从一个点(通常是观察者的位置或屏幕上的某一点)发射出的光线与场景中物体的交点。在3D
应用中,这一技术常用于模拟光照效果、碰撞检测以及用户交互。简单来说,当你在屏幕上点击或触摸时,Three.js
会从该点向场景发射一条虚拟的射线,然后检查这条射线与场景中哪些对象相交,从而判断用户点击了哪个对象。
这个类用于进行raycasting
(光线投射)。 光线投射用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体)。
Three.js
中的Raycaster
在Three.js
中,Raycaster
类是实现这一功能的关键。它允许你创建一个射线,并提供方法来检测这个射线与场景中对象的交点。以下是使用Raycaster
的基本步骤:
Raycaster
实例解释
javascript
new THREE.Raycaster(origin, direction, near, far)
参数说明:
origin
- 光线投射的原点,Vector3
类型。
direction
- 射线的方向,Vector
3类型。
near
- 投射近点,不能为负值,应该小于far
,其默认值为0
。
far
投射远点,不能小于near
,其默认值为无穷大。
射线交叉对象
创建的光线投射对象有一个intersectObject()
方法用来获取射线交叉的对象,使用方法如下
javascript
const raycaster = new THREE.Raycaster(origin, direction, near, far)
const arr= raycaster.intersectObjects(object, recursive,optionalTarget)
raycaster.intersectObjects()
参数
object
- 要检查的是否与射线相交的对象,Object3D
类型。
recursive
- 是否检查所有后代,可选默认为false
,Boolean
类型。
optionalTarget
- 可选参数,放置结果的目标数组。Array
类型。若使用这个参数返回检查结果则在每次调用之前必须清空这个数组。
raycaster.intersectObjects()
的返回值
distance
- 射线投射原点和相交部分之间的距离。
point
- 相交部分的坐标。
face
- 相交的面。
faceIndex
- 相交的面的索引。
object
- 相交的物体。
uv
- 相交部分的点的UV坐标。
光线投射示例
示例步骤
步骤一、创建射线
javascript
const raycaster = new THREE.Raycaster()
步骤二、用一个二维向量保存鼠标点击画布上的位置
javascript
const mouse = new THREE.Vector2(1, 1)
步骤三、监听窗口事件,将x
,y
轴归一化坐标,通过摄像机和鼠标的位置更新色线,计算物体和射线的焦点能不能碰到物体,碰到物体后随机改变射线照射物体的颜色。
javascript
window.addEventListener("click",(e)=>{
//设置鼠标向量的x,y值,将XY轴归一化,X从-1到1,Y为从-1到1,所以除以2
mouse.x = (e.clientX/window.innerWidth)*2-1
mouse.y = -(e.clientY/window.innerHeight)*2+1
// 通过摄像机和鼠标的位置,更新射线
raycaster.setFromCamera(mouse,this.camera)
//计算物体和射线的焦点能不能碰到物体
const intersects = raycaster.intersectObjects([sphere1,sphere2,sphere3])
if(intersects.length>0){
intersects[0].object.material.color.set(this.color16())
}
})
代码解释:
javascript
raycaster.setFromCamera(mouse,this.camera)
每次渲染循环中,你需要更新射线的起点(通常是相机的位置)和方向(通常是基于鼠标坐标计算出的向量):这里,mouse
是归一化设备坐标(即范围在(-1, -1)
到(1, 1)
之间的坐标),可以通过监听鼠标或触摸事件并使用THREE.Vector2和renderer.domElement.clientWidth/Height
进行转换得到。
javascript
const intersects = raycaster.intersectObjects(scene.children, true);
使用raycaster.intersectObjects()
方法来检测射线与场景中对象的交点:此方法返回一个数组,包含了所有与射线相交的对象信息。如果数组不为空,说明有对象被选中,你可以根据需要处理这些交点信息。
什么是归一化坐标:归一化坐标,是一个二维坐标,仅有X/Y两个维度,且X和Y的取值范围均为[-1, 1]
,坐标原点位于three.js
所创建的canvas
的中心处。
归一化坐标公式:
javascript
mouse.x = ((event.clientX - container.getBoundingClientRect().left) / container.getBoundingClientRect().width) * 2 - 1;
mouse.y = - ((event.clientY - container.getBoundingClientRect().top) / container.getBoundingClientRect().height) * 2 + 1;
以上示例完整代码:
javascript
<template>
<div id="container">
</div>
</template>
<script>
import * as THREE from 'three'
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
//导入场景模型加载器
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js"
//导入模型解压器
import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader.js"
export default {
name: 'HomeView',
components: {
},
mounted(){
this.init()
},
data(){
return {
camera: null, //相机对象
scene: null, //场景对象
renderer: null, //渲染器对象
mesh: null, //网格模型对象Mesh
mesh2:null,
controls:null, //轨道控制器
material2:null, //父元素
planeMesh:null, //平面
rgbeLoacer:null,
}
},
methods:{
//随机生成十六进制颜色
color16(){//十六进制颜色随机
var r = Math.floor(Math.random()*256);
var g = Math.floor(Math.random()*256);
var b = Math.floor(Math.random()*256);
var color = '#'+r.toString(16)+g.toString(16)+b.toString(16);
return color;
},
init(){
let container = document.body;
//创建一个场景
this.scene = new THREE.Scene()
//透视摄像机
this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,700)
//创建渲染器
this.renderer = new THREE.WebGLRenderer();
//渲染器尺寸
this.renderer.setSize( window.innerWidth, window.innerHeight );
// 创建三个球
const sphere1 = new THREE.Mesh(
new THREE.SphereGeometry(1,32,32),
new THREE.MeshBasicMaterial({
color:0x00ff00
})
)
sphere1.position.x = -3
this.scene.add(sphere1)
const sphere2 = new THREE.Mesh(
new THREE.SphereGeometry(1,32,32),
new THREE.MeshBasicMaterial({
color:0xff0000
})
)
sphere2.position.x = 0
this.scene.add(sphere2)
const sphere3 = new THREE.Mesh(
new THREE.SphereGeometry(1,32,32),
new THREE.MeshBasicMaterial({
color:0x0000ff
})
)
sphere3.position.x = 3
this.scene.add(sphere3)
//创建射线
const raycaster = new THREE.Raycaster()
//用一个二维向量保存鼠标点击画布上的位置
const mouse = new THREE.Vector2(1, 1)
window.addEventListener("click",(e)=>{
//设置鼠标向量的x,y值,将XY轴归一化,X从-1到1,Y为从-1到1,所以除以2
mouse.x = (e.clientX/window.innerWidth)*2-1
mouse.y = -(e.clientY/window.innerHeight)*2+1
console.log(mouse.x,mouse.y)
// 通过摄像机和鼠标的位置,更新涉嫌
raycaster.setFromCamera(mouse,this.camera)
//计算物体和射线的焦点能不能碰到物体
const intersects = raycaster.intersectObjects([sphere1,sphere2,sphere3])
console.log("intersects",intersects)
if(intersects.length>0){
intersects[0].object.material.color.set(this.color16())
}
})
this.scene.background=new THREE.Color(0x999999)
// 设置相机位置
this.camera.position.z = 15;
this.camera.position.y =2;
this.camera.position.x = 2;
// 看的方向
this.camera.lookAt(0,0,0)
//添加世界坐标辅助器
const axesHelper = new THREE.AxesHelper(3)
this.scene.add( axesHelper );
//添加轨道控制器
this.controls = new OrbitControls(this.camera,this.renderer.domElement)
//添加阻尼带有惯性
this.controls.enableDamping = true
//设置阻尼系数
this.controls.dampingFactor = 0.05
//元素中插入canvas对象
container.appendChild(this.renderer.domElement);
if ( WebGL.isWebGLAvailable() ) {
this.animate();
} else {
const warning = WebGL.getWebGLErrorMessage();
document.getElementById( document.body ).appendChild( warning );
}
},
//旋转起来
animate() {
this.controls.update()
requestAnimationFrame( this.animate );
// this.mesh.rotation.x += 0.01;
// this.mesh.rotation.y += 0.01;
this.renderer.render( this.scene, this.camera );
}
}
}
</script>
总结
通过Three.js
的Raycaster
,我们能够以直观且高效的方式实现3D
场景中的交互事件。无论是简单的点击反馈,还是复杂的拖拽操作,Raycasting
都是构建互动式3D
体验不可或缺的一部分。掌握这项技术,无疑能极大提升你的3D
应用或游戏的用户体验。希望本文能为你开启探索Three.js
交互世界的大门,让你的3D
项目更加生动有趣。