Skip to content

34. 后期处理与描边发光效果

Published:

上节实现了点击选中 3D 场景中的物体的功能,那选中之后,我们想给它加一个高亮的描边呢?

或者之前加载的马的模型,我们想让它发光呢?

image.png

这种就需要用到 Three.js 的后期处理 Post Processing 功能了。

就像我们拍照之后要用 ps 处理之后才会更好看一样。

后期处理功能就是对 camera 拍出来的 3D 场景的照片作进一步的处理。

实现后期处理是用 EffectComposer 这个类:

image.png

它可以组装一条处理链,用多个后期效果依次处理。

我们来用一下:

npx create-vite post-processing

image.png

创建 vite 项目,进入项目安装依赖:

npm install
npm install --save three
npm install --save-dev @types/three

然后改下 src/main.js

import './style.css';
import * as THREE from 'three';
import {
    OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh from './mesh.js';

const scene = new THREE.Scene();

scene.add(mesh);

const axesHelper = new THREE.AxesHelper(500);
scene.add(axesHelper);

const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(300, 200, 400);
scene.add(directionalLight);

const ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(ambientLight);

const width = window.innerWidth;
const height = window.innerHeight;

const camera = new THREE.PerspectiveCamera(60, width / height, 1, 10000);
camera.position.set(0, 500, 500);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height)

function render() {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

render();

document.body.append(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);

创建 Scene、Light、Camera、Renderer。

style.css

body {
    margin: 0;
}

然后写下 src/mesh.js

import * as THREE from 'three';

function createMesh(color, x) {
    const geometry = new THREE.DodecahedronGeometry(100);
    const material = new THREE.MeshPhongMaterial({
        color: color
    });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.x = x;
    return mesh;    
}

const mesh = createMesh('orange', 0);
const mesh2 = createMesh('skyblue', 300);
const mesh3 = createMesh('lightgreen', -300);

const group = new THREE.Group();
group.add(mesh);
group.add(mesh2);
group.add(mesh3);

export default group;

我们用这个十二面几何体,它还可以细分更多的面:

2025-04-02 14.49.06.gif

跑一下:

npm run dev

image.png

image.png

然后我们加上射线和点击的处理:

image.png

renderer.domElement.addEventListener('click', (e) => {
  const y = -((e.offsetY / height) * 2 - 1);
  const x = (e.offsetX / width) * 2 - 1;

  const rayCaster = new THREE.Raycaster();
  rayCaster.setFromCamera(new THREE.Vector2(x, y), camera);

  const intersections = rayCaster.intersectObjects(mesh.children);

  intersections.forEach(item => {
    item.object.material.color.set('blue')
  });
});

试一下:

2025-04-02 15.05.53.gif

没啥问题。

但是这样选中的效果不明显,我们用后期处理给它加上高亮发光的效果。

引入这三个类:

image.png

import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';

初始化一下:

image.png

const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);

const v = new THREE.Vector2(window.innerWidth, window.innerWidth);
const outlinePass = new OutlinePass(v, scene, camera);
composer.addPass(outlinePass);

function render() {
    composer.render();
    // renderer.render(scene, camera);
    requestAnimationFrame(render);
}

render();

我们把之前的 renderer.render 注释掉了,换成了 composer.render:

image.png

EffectComposer 可以组织多个 Pass

第一个 Pass 是 RenderPass 就是渲染 3D 场景

第二个 Pass 我们用 OutlinePass 给选中的物体添加描边

它的第一个参数是宽高比,我们用网页宽高比。

然后在点击选中物体的时候,把它加到 outlinePass 的 selectedObjects 数组:

image.png

if(intersections.length) {
    outlinePass.selectedObjects = [intersections[0].object];
}

看下效果:

2025-04-02 17.14.44.gif

现在点击选中物体的时候,就有描边的效果了。

这个 outlinePass 还支持一些参数:

image.png

outlinePass.visibleEdgeColor.set('orange');

outlinePass.edgeStrength = 10;
outlinePass.edgeThickness = 10;
outlinePass.pulsePeriod = 1;

visibleEdgeColor 是颜色。

pulsePeriod 是闪烁频率,每 1 秒闪烁一次。

edgeThickness 是描边厚度,edgeStrength 是亮度。

看一下:

2025-04-02 17.37.33.gif

当你想在 3D 场景中高亮一些物体的时候,就可以用这个描边 Pass。

当然,还有很多其他 Pass。

比如我们把之前马的模型拿过来,让它发光:

image.png

创建 mesh2.js

image.png

这里模型要加到 public 目录下。

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const loader = new GLTFLoader();

const mesh = new THREE.Group();

loader.load("./Horse.gltf", function (gltf) {
    console.log(gltf);
    mesh.add(gltf.scene);

    gltf.scene.scale.set(50, 50, 50);

    gltf.scene.traverse(obj => {
        if(obj.isMesh) {
            if(obj.name === 'Cylinder') {
                obj.material.color = new THREE.Color('white');
            } else if(obj.name === 'Cylinder_1') {
                obj.material.color = new THREE.Color('pink');
            }
        }
    });
})

export default mesh;

把马放大 50 倍,模型加载进来太小了。

2025-04-02 17.49.50.gif

然后除了描边的 Pass,我们再加一个发光的 Pass:

image.png

image.png

import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
const bloomPass = new UnrealBloomPass(v);
bloomPass.strength = 0.5;
composer.addPass(bloomPass);

添加 UnrealBloomPass,调一下发光强度。

2025-04-02 18.12.35.gif

现在就有一种发光的感觉了。

然后我们再改一下,点击选中的时候再添加这个 Pass:

image.png

  if(intersections.length) {
    outlinePass.selectedObjects = [intersections[0].object];

    if(!composer.passes.includes(bloomPass)) {
      composer.addPass(bloomPass);
    }
  } else {
    outlinePass.selectedObjects = [];
    composer.removePass(bloomPass);
  }

没点中就去掉这个 Pass,并且把描边 Pass 的 selectedObjects 置空。

2025-04-02 18.18.36.gif

可以看到,现在确实是点击选中的时候才会发光了。

但是点击马尾和马的身体分别是不同部分描边。

因为马包含两个 mesh。

如果我们想不管点击马的任何部位,都是整体描边呢?

可以这样处理:

image.png

obj.target = gltf.scene;

遍历模型的所有对象的时候,给所有 mesh 加个 target 属性

然后点击的时候添加到 selectedObjects 的是这个 obj.target

image.png

现在点击啥部分都是整体描边了:

2025-04-02 18.27.58.gif

案例代码上传了小册仓库

总结

这节我们学了后期处理,它可以通过一系列 Pass 添加后期效果,比如发光、描边等。

创建 EffectComposer 对象,调用 addPass 方法添加了 3 个 Pass:

RenderPass 渲染场景,OutlinePass 描边,UnrealBloomPass 发光

注意要把渲染循环里的 renderer.render 换成 composor.render

后期处理的 Pass 还有很多,下节我们来学下其他的。

评论