在 Three.js 中,通过后期处理技术可以实现各种视觉效果,其中包括描边(Outline)效果,用于突出显示或选中特定物体。本文将重点介绍如何使用 Three.js 中的 OutlinePass 后期处理效果来实现点击选中物体的效果,并对实现过程进行详细讲解。
1. 导入 Three.js 和 OutlinePass相关库
安装three.js
npm install three
首先,我们需要导入 Three.js 库以及 OutlinePass 后期处理效果:
import * as THREE from 'three';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass';
2. 创建基本场景和相机等
接下来,我们创建 Three.js 的场景、相机和渲染器,并设置相机的位置和渲染器的大小:
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建并添加物体...
3. 创建 OutlinePass
然后,我们创建 OutlinePass 对象并设置其参数,例如轮廓颜色、边缘强度等:
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.visibleEdgeColor.set(0xff0000); // 可见边缘颜色
outlinePass.hiddenEdgeColor.set(0xffffff); // 隐藏边缘颜色
outlinePass.edgeStrength = 5; // 边缘强度
outlinePass.edgeThickness = 1; // 边缘厚度
4. 点击选中物体效果
接下来,我们监听鼠标点击事件,使用 Raycaster 来检测选中的物体,并根据选中状态添加或移除 OutlinePass:
document.addEventListener('click', onMouseClick, false);
function onMouseClick(event) {
event.preventDefault();
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
const object = intersects[0].object;
// 更新当前选中的物体
if (!object.userData.hasOutline) {
object.userData.hasOutline = true;
outlinePass.selectedObjects = scene.children.filter(item => item.userData.hasOutline);
composer.addPass(outlinePass);
} else {
object.userData.hasOutline = false;
outlinePass.selectedObjects = scene.children.filter(item => item.userData.hasOutline);
composer.removePass(outlinePass);
}
}
}
5. 总结
通过以上步骤,我们成功实现了使用 Three.js 中的 OutlinePass 后期处理效果来实现点击选中物体的效果。当点击物体时,物体将被突出显示或取消突出显示,从而实现了视觉上的选中效果。这种方法适用于需要交互性的 3D 场景,例如游戏或模型浏览器等。
完整代码
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>three.js outlinePass select example</title>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<script type="module" src="/main.js"></script>
</body>
</html>
main.js
import * as THREE from 'three';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass';
// 创建场景、相机、渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建渲染通道
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.visibleEdgeColor.set(0xff0000);
outlinePass.hiddenEdgeColor.set(0xffffff);
outlinePass.edgeStrength = 5;
outlinePass.edgeThickness = 1;
// 添加方向光
const light = new THREE.DirectionalLight(0xffffff, 3);
light.position.set(1, 1, 1);
scene.add(light);
// 创建立方体并添加到场景中
const geometry = new THREE.BoxGeometry();
const material1 = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
const material2 = new THREE.MeshPhongMaterial({ color: 0x0000ff });
const cube1 = new THREE.Mesh(geometry, material1);
const cube2 = new THREE.Mesh(geometry, material2);
cube1.position.x = 1;
cube2.position.x = -1;
scene.add(cube1);
scene.add(cube2);
// 监听窗口大小变化事件
window.addEventListener('resize', () => {
// 更新相机宽高比
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
// 更新渲染器尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
// 更新轮廓效果尺寸
outlinePass.setSize(window.innerWidth, window.innerHeight);
});
// 渲染循环
function animate() {
requestAnimationFrame(animate);
cube1.rotation.x += 0.01;
cube1.rotation.y += 0.01;
cube2.rotation.x += 0.01;
cube2.rotation.y += 0.01;
composer.render();
}
animate();
// 添加点击事件监听
document.addEventListener('click', onMouseClick, false);
// 点击事件处理函数
function onMouseClick(event) {
event.preventDefault();
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
const object = intersects[0].object;
if (!object.userData.hasOutline) {
object.userData.hasOutline = true;
outlinePass.selectedObjects = scene.children.filter(item => item.userData.hasOutline);
composer.addPass(outlinePass);
} else {
object.userData.hasOutline = false;
outlinePass.selectedObjects = scene.children.filter(item => item.userData.hasOutline);
}
} else {
outlinePass.selectedObjects = [];
composer.removePass(outlinePass);
scene.children.forEach(function(obj) {
obj.userData.hasOutline = false;
});
}
}