08 后期处理
学习 Three.js 中的后期处理效果
什么是后期处理
后期处理(Post-processing)是在渲染场景之后,对图像进行一系列视觉增强的技术。比如泛光(Bloom)让发光物体更亮、景深(Depth of Field)模拟相机焦点效果等。
EffectComposer
Three.js 的后期处理通过 EffectComposer 实现。
bash
pnpm add three1
js
import * as THREE from 'three'
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js'
// 1. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 1
document.body.appendChild(renderer.domElement)
// 2. 创建场景和相机
// ...
// 3. 创建 EffectComposer
const composer = new EffectComposer(renderer)
// 4. 添加渲染通道(必须)
const renderPass = new RenderPass(scene, camera)
composer.addPass(renderPass)
// 5. 添加效果通道
// ...
// 6. 添加输出通道(必须,用于输出到屏幕)
const outputPass = new OutputPass()
composer.addPass(outputPass)
// 7. 用 composer 替代 renderer.render
function animate() {
requestAnimationFrame(animate)
composer.render() // 替代 renderer.render(scene, camera)
}
animate()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
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
常用后期效果
Bloom(泛光)
让发光或明亮的区域产生光晕效果。
js
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, // 强度
0.4, // 半径
0.85 // 阈值(低于此值的像素不受影响)
)
composer.addPass(bloomPass)
// 参数调整
bloomPass.strength = 2 // 泛光强度
bloomPass.radius = 1 // 泛光半径
bloomPass.threshold = 0.5 // 阈值1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
FXAA(抗锯齿)
性能友好的抗锯齿效果。
js
import { FXAAShader } from 'three/addons/shaders/FXAAShader.js'
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'
const fxaaPass = new ShaderPass(FXAAShader)
const pixelRatio = renderer.getPixelRatio()
fxaaPass.material.uniforms['resolution'].value.set(
1 / (window.innerWidth * pixelRatio),
1 / (window.innerHeight * pixelRatio)
)
composer.addPass(fxaaPass)1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
SMAA(更好的抗锯齿)
js
import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js'
const smaaPass = new SMAAPass(
window.innerWidth * pixelRatio,
window.innerHeight * pixelRatio
)
composer.addPass(smaaPass)1
2
3
4
5
6
7
2
3
4
5
6
7
Depth of Field(景深)
模拟相机焦平面效果,焦点外区域模糊。
js
import { BokehPass } from 'three/addons/postprocessing/BokehPass.js'
const bokehPass = new BokehPass(scene, {
focus: 5.0, // 焦距
aperture: 0.0001, // 光圈(越大背景越模糊)
maxblur: 0.01 // 最大模糊程度
})
composer.addPass(bokehPass)
// 焦点跟随某个物体
function animate() {
bokehPass.uniforms.focus.value = camera.position.distanceTo(target.position)
composer.render()
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
SSAO(屏幕空间环境光遮蔽)
让物体缝隙、角落更暗,增加真实感。
js
import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js'
const ssaoPass = new SSAOPass(scene, camera, window.innerWidth, window.innerHeight)
ssaoPass.kernelRadius = 16
ssaoPass.minDistance = 0.005
ssaoPass.maxDistance = 0.1
composer.addPass(ssaoPass)1
2
3
4
5
6
7
2
3
4
5
6
7
Outline(轮廓描边)
选中物体时显示轮廓线。
js
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js'
const outlinePass = new OutlinePass(
window.innerWidth,
window.innerHeight,
scene,
camera
)
outlinePass.edgeStrength = 3 // 轮廓强度
outlinePass.edgeGlow = 1 // 发光强度
outlinePass.edgeThickness = 1 // 轮廓粗细
outlinePass.visibleEdgeColor.set('#00ffff') // 可见时颜色
outlinePass.hiddenEdgeColor.set('#ff00ff') // 遮挡时颜色
composer.addPass(outlinePass)
// 添加要描边的物体
outlinePass.selectedObjects = [mesh]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Color Correction(色彩校正)
调整整体色调、亮度、饱和度。
js
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'
const ColorCorrectionShader = {
uniforms: {
tDiffuse: { value: null },
brightness: { value: 0 },
contrast: { value: 1 },
saturation: { value: 1 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float brightness;
uniform float contrast;
uniform float saturation;
varying vec2 vUv;
void main() {
vec4 color = texture2D(tDiffuse, vUv);
color.rgb += brightness;
color.rgb = (color.rgb - 0.5) * contrast + 0.5;
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
color.rgb = mix(vec3(gray), color.rgb, saturation);
gl_FragColor = color;
}
`
}
const colorCorrectionPass = new ShaderPass(ColorCorrectionShader)
colorCorrectionPass.uniforms.brightness.value = 0.05
colorCorrectionPass.uniforms.contrast.value = 1.1
colorCorrectionPass.uniforms.saturation.value = 1.2
composer.addPass(colorCorrectionPass)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
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
响应窗口大小变化
js
window.addEventListener('resize', () => {
const width = window.innerWidth
const height = window.innerHeight
const pixelRatio = Math.min(window.devicePixelRatio, 2)
camera.aspect = width / height
camera.updateProjectionMatrix()
renderer.setSize(width, height)
composer.setSize(width, height)
// 更新各 pass 的尺寸
bloomPass.setSize(width, height)
})1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
性能优化
1. 降低分辨率
js
const composer = new EffectComposer(renderer)
// 使用一半分辨率渲染后期效果
composer.setSize(
window.innerWidth * 0.5,
window.innerHeight * 0.5
)1
2
3
4
5
6
2
3
4
5
6
2. 按需开启效果
js
let bloomEnabled = false
function animate() {
requestAnimationFrame(animate)
if (bloomEnabled) {
composer.render()
} else {
renderer.render(scene, camera)
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
3. 合适的 pass 顺序
RenderPass → (SSAO) → (Bloom) → (Outline) → (ColorCorrection) → OutputPass1
常用 pass 组合
泛光 + 抗锯齿
js
const renderPass = new RenderPass(scene, camera)
const bloomPass = new UnrealBloomPass(...)
const smaaPass = new SMAAPass(...)
const outputPass = new OutputPass()
composer.addPass(renderPass)
composer.addPass(bloomPass)
composer.addPass(smaaPass)
composer.addPass(outputPass)1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
选中高亮
js
const renderPass = new RenderPass(scene, camera)
const outlinePass = new OutlinePass(...)
const outputPass = new OutputPass()
composer.addPass(renderPass)
composer.addPass(outlinePass)
composer.addPass(outputPass)
// 鼠标点击设置描边
raycaster.setFromCamera(mouse, camera)
const intersects = raycaster.intersectObjects(objects)
if (intersects.length > 0) {
outlinePass.selectedObjects = [intersects[0].object]
} else {
outlinePass.selectedObjects = []
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16