【Three.js】知识梳理十三:Three.js射线拾取(Raycaster)与轮廓高亮(OutlinePass)

511 阅读4分钟

射线拾取和轮廓高亮是Three.js中两项非常有用的技术,分别用于检测用户与3D对象的交互以及对选中的物体进行高亮显示。通过本文的介绍,你可以在自己的Three.js项目中实现这些功能,增强用户体验和交互效果。

image.png

1.场景创建

在开始实现射线拾取和轮廓高亮之前,我们需要先设置一个基本的Three.js场景。以下是创建一个包含立方体的简单场景的代码。

示例代码

import * as THREE from 'three';
​
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建一个立方体
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 设置相机位置
camera.position.z = 5;
// 渲染循环
function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}
animate();

2.Raycaster射线拾取

射线拾取是一种用于检测3D空间中光线与对象交点的技术。通过在场景中发射一条从相机出发 并穿过鼠标点击位置的射线,我们可以检测到射线与哪些对象相交,从而实现用户与3D对象的交互。

为了实现射线拾取,我们需要监听鼠标点击事件,并在事件触发时创建一个射线投射器(THREE.Raycaster),然后计算射线与场景中对象的交点。

image.png

示例代码

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
​
function onMouseClick(event) {
  // 将鼠标位置转换为归一化设备坐标(NDC)
  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);
  if (intersects.length > 0) {
    // 如果有交点,更改第一个交点物体的颜色
    intersects[0].object.material.color.set(0xff0000);
  }
}
​
window.addEventListener('click', onMouseClick);

3.OutlinePass轮廓高亮

为了提高用户体验和交互效果,常常需要对选中的物体进行高亮显示。Three.js提供了一个名为OutlinePass的功能,它通过为选中的3D对象添加一层发光的边缘(轮廓),使得这些对象在场景中更加显眼。该效果常用于游戏、数据可视化和虚拟现实应用中,以帮助用户识别和选择物体。

image.png

OutlinePass提供了多种配置选项,可以调整轮廓的外观效果:

  • edgeStrength: 控制轮廓的强度。
  • edgeGlow: 控制轮廓的光晕效果。
  • edgeThickness: 控制轮廓的厚度。
  • visibleEdgeColor: 设置轮廓的颜色。
  • hiddenEdgeColor: 设置隐藏边缘的颜色。

示例代码

import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
​
// 假设已有的场景、相机和渲染器
const scene = existingScene;
const camera = existingCamera;
const renderer = existingRenderer;
​
// 创建后期处理Composer
const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
​
const outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera);
outlinePass.edgeStrength = 2.5; // 轮廓的强度
outlinePass.edgeGlow = 0.5; // 轮廓的光晕
outlinePass.edgeThickness = 2.0; // 轮廓的厚度
outlinePass.visibleEdgeColor.set('#ff0000'); // 轮廓的颜色
outlinePass.hiddenEdgeColor.set('#190a05'); // 隐藏边缘的颜色
composer.addPass(outlinePass);
​
// 直接设置需要高亮的物体
const objectToHighlight = existingObject; // 需要高亮的物体
outlinePass.selectedObjects = [objectToHighlight];
​
function animate() {
  requestAnimationFrame(animate);
  composer.render();
}
​
animate();

4.将射线拾取与轮廓高亮结合

为了将射线拾取与轮廓高亮结合起来,我们可以在鼠标点击事件中,将选中的物体传递给OutlinePass,从而实现点击物体时的高亮效果。以下是完整的代码示例。

image.png

示例代码

import * as THREE from 'three';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';

// 创建场景
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建一个立方体
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// 设置相机位置
camera.position.z = 5;

// 创建后期处理Composer
const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);

const outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera);
outlinePass.edgeStrength = 2.5; // 轮廓的强度
outlinePass.edgeGlow = 0.5; // 轮廓的光晕
outlinePass.edgeThickness = 2.0; // 轮廓的厚度
outlinePass.visibleEdgeColor.set('#ff0000'); // 轮廓的颜色
outlinePass.hiddenEdgeColor.set('#190a05'); // 隐藏边缘的颜色
composer.addPass(outlinePass);

// 创建射线投射器
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

function onMouseClick(event) {
  // 将鼠标位置转换为归一化设备坐标(NDC)
  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);

  if (intersects.length > 0) {
    // 设置轮廓高亮的物体
    outlinePass.selectedObjects = [intersects[0].object];
  } else {
    // 如果没有选中的物体,清空轮廓高亮
    outlinePass.selectedObjects = [];
  }
}

// 添加鼠标点击事件监听器
window.addEventListener('click', onMouseClick);

function animate() {
  requestAnimationFrame(animate);
  composer.render();
}

animate();