(二)Three.js光線檢測(cè)
摘要:使用three.js中的光線檢測(cè) Raycaster() ,實(shí)現(xiàn)一下效果:
- 通過點(diǎn)擊處的坐標(biāo),修改攝像機(jī)位置,實(shí)現(xiàn)攝像機(jī)由遠(yuǎn)及近的過渡動(dòng)態(tài)效果(由遠(yuǎn)景到近景)
1、鼠標(biāo)點(diǎn)擊—攝像機(jī)過渡動(dòng)畫
1.1 THREE.Raycaster對(duì)象
官網(wǎng):Raycaster – three.js docs (threejs.org)
因?yàn)槭褂檬髽?biāo)對(duì)模型點(diǎn)擊獲取,那么,再three中可以使用Raycaster()光線檢測(cè)來實(shí)現(xiàn)。再three官網(wǎng)上對(duì)Raycaster的解釋為“此類旨在協(xié)助進(jìn)行光線投射。Raycasting用于鼠標(biāo)拾?。ㄓ?jì)算鼠標(biāo)在3D空間中的哪些對(duì)象)等”,其原理便是
THREE.Raycaster對(duì)象從屏幕上的點(diǎn)擊位置向場(chǎng)景中發(fā)射一束光線,
與攝像機(jī)的位置形成一條光線,在這條光線路徑上的物體,都會(huì)被檢測(cè)到
1.1.1參數(shù)
Raycaster( origin : Vector3, direction : Vector3, near : Float, far : Float )
origin —— 光線投射的原點(diǎn)向量。
direction —— 向射線提供方向的方向向量,應(yīng)當(dāng)被標(biāo)準(zhǔn)化。
near —— 返回的所有結(jié)果比near遠(yuǎn)。near不能為負(fù)值,其默認(rèn)值為0。
far —— 返回的所有結(jié)果都比far近。far不能小于near,其默認(rèn)值為Infinity(正無窮。)
1.2 使用到的方法
1.2.1 setFromCamera()
這個(gè)方法中有兩個(gè)變量,
第一個(gè)是在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)中鼠標(biāo)的二維坐標(biāo) —— X分量與Y分量應(yīng)當(dāng)在-1到1之間;
第二個(gè)是場(chǎng)景攝像機(jī)
.setFromCamera ( coords : Vector2, camera : Camera ) : undefined
coords —— 在標(biāo)準(zhǔn)化設(shè)備坐標(biāo)中鼠標(biāo)的二維坐標(biāo) —— X分量與Y分量應(yīng)當(dāng)在-1到1之間。
camera —— 射線所來源的攝像機(jī)。
1.2.2intersectObject()
這個(gè)方法是用來檢測(cè)與射線相交的物體,返回值是一個(gè)Array數(shù)組;
檢測(cè)所有在射線與物體之間,包括或不包括后代的相交部分。返回結(jié)果時(shí),相交部分將按距離進(jìn)行排序,最近的位于第一個(gè)。
.intersectObject ( object : Object3D, recursive : Boolean, optionalTarget : Array ) : Array
object —— 檢查與射線相交的物體。
recursive —— 若為true,則同時(shí)也會(huì)檢查所有的后代。否則將只會(huì)檢查對(duì)象本身。默認(rèn)值為true。
optionalTarget — (可選)設(shè)置結(jié)果的目標(biāo)數(shù)組。如果不設(shè)置這個(gè)值,則一個(gè)新的Array會(huì)被實(shí)例化;如果設(shè)置了這個(gè)值,
則在每次調(diào)用之前必須清空這個(gè)數(shù)組(例如:array.length = 0;)。
返回的數(shù)組如下所示:

參數(shù)解釋:
distance —— 射線投射原點(diǎn)和相交部分之間的距離。
point —— 相交部分的點(diǎn)(世界坐標(biāo))
face —— 相交的面
faceIndex —— 相交的面的索引
object —— 相交的物體
uv —— 相交部分的點(diǎn)的UV坐標(biāo)。
uv2 —— Second set of U,V coordinates at point of intersection
instanceId – The index number of the instance where the ray intersects the InstancedMesh
當(dāng)計(jì)算這條射線是否和物體相交的時(shí)候,Raycaster將傳入的對(duì)象委托給raycast方法。
這將可以讓mesh對(duì)于光線投射的響應(yīng)不同于lines和pointclouds。
請(qǐng)注意:對(duì)于網(wǎng)格來說,面必須朝向射線的原點(diǎn),以便其能夠被檢測(cè)到。 用于交互的射線穿過面的背側(cè)時(shí),將不會(huì)被檢測(cè)到。
如果需要對(duì)物體中面的兩側(cè)進(jìn)行光線投射, 你需要將material中的side屬性設(shè)置為THREE.DoubleSide。
1.2.3 intersectObjects()
這個(gè)方法和上面的方法相差不多,本方法是用來檢測(cè)一組物體;
檢測(cè)所有在射線與這些物體之間,包括或不包括后代的相交部分。返回結(jié)果時(shí),相交部分將按距離進(jìn)行排序,最近的位于第一個(gè)),相交部分和.intersectObject所返回的格式是相同的。
.intersectObjects ( objects : Array, recursive : Boolean, optionalTarget : Array ) : Array
objects —— 檢測(cè)和射線相交的一組物體。
recursive —— 若為true,則同時(shí)也會(huì)檢測(cè)所有物體的后代。否則將只會(huì)檢測(cè)對(duì)象本身的相交部分。默認(rèn)值為true。
optionalTarget —— (可選)設(shè)置結(jié)果的目標(biāo)數(shù)組。如果不設(shè)置這個(gè)值,則一個(gè)新的Array會(huì)被實(shí)例化;如果設(shè)置了這個(gè)值,
則在每次調(diào)用之前必須清空這個(gè)數(shù)組(例如:array.length = 0;)。
1.3 gsap中的TweenMax動(dòng)畫
中文網(wǎng)址:TweenMax中文手冊(cè)_TweenMax中文網(wǎng)
使用TweenMax動(dòng)畫,控制攝像機(jī)的變換速度,達(dá)到想要的平和效果;
TweenLite.fromTo('div', 5, {opacity:1}, {opacity:0});
//動(dòng)畫目標(biāo):div
//起始狀態(tài):opacity:1
//終點(diǎn)狀態(tài):opacity:0
//補(bǔ)間:5秒完成狀態(tài)改變
1.4主要代碼
1.4.1設(shè)置光線檢測(cè)
//光線檢測(cè),獲取點(diǎn)擊物體的坐標(biāo)值
rayClick() {
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const camera = this.camera;
const scene = this.scene;
//對(duì)頁(yè)面進(jìn)行鼠標(biāo)點(diǎn)擊事件綁定
window.addEventListener("mouseup", mouseup);
//添加點(diǎn)擊方法
function mouseup(e) {
// 將鼠標(biāo)位置歸一化為設(shè)備坐標(biāo)。x 和 y 方向的取值范圍是 (-1 to +1)
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
// 通過攝像機(jī)和鼠標(biāo)位置更新射線
//這里的攝像機(jī)要將外部定義的攝像機(jī)通過新的變量接受到,再次賦值使用,同下方的scene
//因?yàn)槭髽?biāo)點(diǎn)擊事件的this指的是windows,不是這個(gè)場(chǎng)景Scene,解決辦法
//可以在 const mouse = new THREE.Vector2(); 后重新賦值一下:const _this = this;
//在點(diǎn)擊函數(shù)中就可以使用_this.scene、_this.camera
raycaster.setFromCamera(mouse, camera);
// 計(jì)算物體和射線的焦點(diǎn)
const intersects = raycaster.intersectObjects(scene.children);
console.log(intersects);
//選中后進(jìn)行操作
if (intersects.length) {
var selected = intersects[0];
//點(diǎn)擊世界中的物體,改變攝像機(jī)位置到物體前,實(shí)現(xiàn)從遠(yuǎn)景到近景的切換效果
TweenMax.to(camera.position, 2, {
x: selected.point.x + 50,
y: selected.point.y,
z: selected.point.z + 100,
ease:Expo.easeInOut,
onComplete: function (){}
})
console.log("x坐標(biāo)" + selected.point.x);
console.log("y坐標(biāo)" + selected.point.y);
console.log("z坐標(biāo)" + selected.point.z);
}
}
}
1.5完整代碼
html部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three實(shí)現(xiàn)攝像機(jī)動(dòng)畫</title>
<link rel="stylesheet" href="./assets/css/index.css">
</head>
<body>
<canvas id="canvasScene"></canvas>
<script src="./js/index.js" type="module"></script>
</body>
</html>
index.js部分
import Scene from "./Scene";
const canvasEL = document.getElementById('canvasScene');
new Scene(canvasEL);
Scene.js部分
import * as THREE from "three";
//導(dǎo)入鼠標(biāo)控制器控件
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
//導(dǎo)入fbx模型加載器
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
//導(dǎo)入TweenMax動(dòng)畫控件
import { TweenMax } from "gsap/gsap-core";
import { Expo } from "gsap";
export default class Scene {
canvas;
scene;
camera;
render;
controls;
light;
constructor(el) {
this.canvas = el;
this.init();
}
init() {
this.setRender();
this.setScene();
this.setCamera();
this.setControls();
this.setLight();
this.animate();
this.setFbx();
this.rayClick();
}
setScene() {
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x002222);
}
// 設(shè)置相機(jī)
setCamera() {
this.camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
3000
);
this.camera.position.set(300, 200, 1000);
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.scene.add(this.camera);
}
// 設(shè)置渲染器
setRender() {
this.render = new THREE.WebGL1Renderer({
canvas: this.canvas,
//設(shè)置抗鋸齒
antialias: true,
});
//設(shè)置渲染編碼
this.render.outputEncoding = THREE.sRGBEncoding;
//設(shè)置渲染寬高
this.render.setSize(window.innerWidth, window.innerHeight);
//監(jiān)聽頁(yè)面大小變化,修改器的寬高、攝像機(jī)的比例
window.addEventListener("resize", () => {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.render.setSize(window.innerWidth, window.innerHeight);
});
}
//設(shè)置控制器
setControls() {
this.controls = new OrbitControls(this.camera, this.render.domElement);
}
//設(shè)置燈光
setLight() {
this.light = new THREE.SpotLight();
this.light.position.set(100, 500, 300);
this.scene.add(this.light);
}
//設(shè)置渲染函數(shù)
animate = () => {
this.render.render(this.scene, this.camera);
window.requestAnimationFrame(this.animate);
};
//添加fbx模型
setFbx() {
const fbxLoader = new FBXLoader();
fbxLoader.load("./model/house.fbx", (house) => {
const scale = 0.05;
house.scale.set(scale, scale, scale);
this.scene.add(house);
house.position.set(0, 0, 0);
});
}
//光線檢測(cè),獲取點(diǎn)擊物體的坐標(biāo)值
rayClick() {
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const camera = this.camera;
const scene = this.scene;
//對(duì)頁(yè)面進(jìn)行鼠標(biāo)點(diǎn)擊事件綁定
window.addEventListener("mouseup", mouseup);
//添加點(diǎn)擊方法
function mouseup(e) {
// 將鼠標(biāo)位置歸一化為設(shè)備坐標(biāo)。x 和 y 方向的取值范圍是 (-1 to +1)
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
// 通過攝像機(jī)和鼠標(biāo)位置更新射線
//這里的攝像機(jī)要將外部定義的攝像機(jī)通過新的變量接受到,再次賦值使用,同下方的scene
raycaster.setFromCamera(mouse, camera);
// 計(jì)算物體和射線的焦點(diǎn)
const intersects = raycaster.intersectObjects(scene.children);
console.log(intersects);
//選中后進(jìn)行操作
if (intersects.length) {
var selected = intersects[0];
//點(diǎn)擊世界中的物體,改變攝像機(jī)位置到物體前,實(shí)現(xiàn)從遠(yuǎn)景到近景的切換效果
TweenMax.to(camera.position, 2, {
x: selected.point.x + 50,
y: selected.point.y,
z: selected.point.z + 100,
ease:Expo.easeInOut,
onComplete: function (){}
})
console.log("x坐標(biāo)" + selected.point.x);
console.log("y坐標(biāo)" + selected.point.y);
console.log("z坐標(biāo)" + selected.point.z);
}
}
}
}