【three.js】后期处理outlinePass描边实现点击选中物体效果

在 Three.js 中,通过后期处理技术可以实现各种视觉效果,其中包括描边(Outline)效果,用于突出显示或选中特定物体。本文将重点介绍如何使用 Three.js 中的 OutlinePass 后期处理效果来实现点击选中物体的效果,并对实现过程进行详细讲解。

1. 导入 Three.js 和 OutlinePass相关库

安装three.js

1
npm install three

首先,我们需要导入 Three.js 库以及 OutlinePass 后期处理效果:

1
2
3
4
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 的场景、相机和渲染器,并设置相机的位置和渲染器的大小:

1
2
3
4
5
6
7
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 对象并设置其参数,例如轮廓颜色、边缘强度等:

1
2
3
4
5
6
7
8
9
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<!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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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;
        });
    }
}
comments powered by Disqus