后期处理
后期处理是在场景渲染完成后对图像进行额外处理的技术。通过后期处理,可以实现模糊、发光、色彩校正、景深等各种电影级的视觉效果。本章将介绍 Three.js 的后期处理系统及其应用。
后期处理基础
工作原理
后期处理的基本流程是:
- 将场景渲染到纹理(Render Target)而不是屏幕
- 对纹理应用各种效果
- 将最终结果输出到屏幕
Three.js 使用 EffectComposer 来管理这个流程,它将多个处理步骤(Pass)串联起来。
基本设置
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
// 创建后期处理组合器
const composer = new EffectComposer(renderer);
// 添加渲染通道(必须作为第一个通道)
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 在渲染循环中使用 composer
function animate() {
requestAnimationFrame(animate);
composer.render(); // 使用 composer 而不是 renderer.render
}
animate();
RenderPass 是必须的,它负责将场景渲染到纹理。之后的通道则对这张纹理进行处理。
常用后期处理效果
UnrealBloomPass 发光效果
发光效果让场景中明亮的区域产生光晕,常用于霓虹灯、激光等效果:
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);
参数详解:
- 强度(strength):发光的亮度,值越大越亮
- 半径(radius):发光的扩散范围
- 阈值(threshold):只有亮度超过这个值的区域才会发光
// 调整发光效果
bloomPass.strength = 2;
bloomPass.radius = 0.5;
bloomPass.threshold = 0.6;
// 运行时修改
bloomPass.enabled = true; // 启用/禁用效果
FilmPass 老电影效果
模拟老电影的噪点和扫描线:
import { FilmPass } from 'three/addons/postprocessing/FilmPass.js';
const filmPass = new FilmPass(
0.35, // 噪点强度
0.5, // 扫描线强度
2048, // 扫描线数量
false // 是否灰度
);
composer.addPass(filmPass);
GlitchPass 故障效果
产生随机的画面故障:
import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';
const glitchPass = new GlitchPass();
composer.addPass(glitchPass);
// 控制故障频率
glitchPass.goWild = true; // 持续故障
glitchPass.goWild = false; // 偶发故障
ShaderPass 自定义着色器效果
使用内置或自定义着色器:
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { VignetteShader } from 'three/addons/shaders/VignetteShader.js';
// 暗角效果
const vignettePass = new ShaderPass(VignetteShader);
vignettePass.uniforms.offset.value = 1.0;
vignettePass.uniforms.darkness.value = 1.5;
composer.addPass(vignettePass);
常用内置着色器
Three.js 提供了多种内置着色器效果:
import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js';
import { SobelOperatorShader } from 'three/addons/shaders/SobelOperatorShader.js';
import { SepiaShader } from 'three/addons/shaders/SepiaShader.js';
import { ColorCorrectionShader } from 'three/addons/shaders/ColorCorrectionShader.js';
import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
// 伽马校正
const gammaPass = new ShaderPass(GammaCorrectionShader);
// 边缘检测(素描效果)
const sobelPass = new ShaderPass(SobelOperatorShader);
sobelPass.uniforms.resolution.value.set(
1 / window.innerWidth,
1 / window.innerHeight
);
// 怀旧色调
const sepiaPass = new ShaderPass(SepiaShader);
sepiaPass.uniforms.amount.value = 0.8;
// 色彩校正
const colorCorrectionPass = new ShaderPass(ColorCorrectionShader);
colorCorrectionPass.uniforms.powRGB.value.set(2, 2, 2);
colorCorrectionPass.uniforms.mulRGB.value.set(1, 1, 1);
// 抗锯齿(FXAA)
const fxaaPass = new ShaderPass(FXAAShader);
fxaaPass.uniforms.resolution.value.set(
1 / window.innerWidth,
1 / window.innerHeight
);
高级效果
景深效果
模拟真实相机的景深效果,聚焦区域清晰,其他区域模糊:
import { BokehPass } from 'three/addons/postprocessing/BokehPass.js';
const bokehPass = new BokehPass(scene, camera, {
focus: 10, // 聚焦距离
aperture: 0.0001, // 光圈大小
maxblur: 0.01 // 最大模糊量
});
composer.addPass(bokehPass);
// 运行时修改
bokehPass.uniforms.focus.value = 20;
bokehPass.uniforms.aperture.value = 0.0002;
环境光遮蔽(SSAO)
模拟真实的阴影遮蔽效果:
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);
屏幕空间反射(SSR)
实现实时反射效果:
import { SSRPass } from 'three/addons/postprocessing/SSRPass.js';
const ssrPass = new SSRPass({
renderer,
scene,
camera,
width: window.innerWidth,
height: window.innerHeight,
selects: [] // 需要反射的物体
});
ssrPass.maxDistance = 0.1;
ssrPass.thickness = 0.02;
composer.addPass(ssrPass);
轮廓线效果
为物体添加描边轮廓:
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
const outlinePass = new OutlinePass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
scene,
camera
);
outlinePass.edgeStrength = 3; // 边缘强度
outlinePass.edgeGlow = 0.5; // 发光强度
outlinePass.edgeThickness = 1; // 边缘厚度
outlinePass.pulsePeriod = 2; // 脉冲周期
outlinePass.visibleEdgeColor.set('#ffffff');
outlinePass.hiddenEdgeColor.set('#000000');
// 设置要描边的物体
const selectedObjects = [mesh1, mesh2];
outlinePass.selectedObjects = selectedObjects;
composer.addPass(outlinePass);
颜色查找表(LUT)
使用 LUT 进行专业级调色:
import { LUTPass } from 'three/addons/postprocessing/LUTPass.js';
import { LUT3dlLoader } from 'three/addons/loaders/LUT3dlLoader.js';
const loader = new LUT3dlLoader();
loader.load('color.lut', (lutTexture) => {
const lutPass = new LUTPass({
lut: lutTexture
});
composer.addPass(lutPass);
});
组合多个效果
将多个效果组合起来,创建复杂的视觉效果:
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js';
import { VignetteShader } from 'three/addons/shaders/VignetteShader.js';
const composer = new EffectComposer(renderer);
// 1. 场景渲染
composer.addPass(new RenderPass(scene, camera));
// 2. 发光效果
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
0.5, 0.4, 0.85
);
composer.addPass(bloomPass);
// 3. 暗角效果
const vignettePass = new ShaderPass(VignetteShader);
vignettePass.uniforms.offset.value = 1.0;
vignettePass.uniforms.darkness.value = 1.2;
composer.addPass(vignettePass);
// 4. 伽马校正
composer.addPass(new ShaderPass(GammaCorrectionShader));
// 5. 抗锯齿
const fxaaPass = new ShaderPass(FXAAShader);
fxaaPass.uniforms.resolution.value.set(
1 / window.innerWidth,
1 / window.innerHeight
);
composer.addPass(fxaaPass);
自定义后期处理着色器
创建自定义的后期处理效果:
基本结构
const CustomShader = {
uniforms: {
tDiffuse: { value: null }, // 输入纹理(必须)
uTime: { value: 0 },
uResolution: { value: new THREE.Vector2() }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float uTime;
uniform vec2 uResolution;
varying vec2 vUv;
void main() {
vec4 color = texture2D(tDiffuse, vUv);
// 在这里添加效果
color.rgb = 1.0 - color.rgb; // 反色效果
gl_FragColor = color;
}
`
};
const customPass = new ShaderPass(CustomShader);
composer.addPass(customPass);
波纹扭曲效果
const RippleShader = {
uniforms: {
tDiffuse: { value: null },
uTime: { value: 0 },
uAmplitude: { value: 0.01 },
uFrequency: { value: 10.0 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float uTime;
uniform float uAmplitude;
uniform float uFrequency;
varying vec2 vUv;
void main() {
vec2 uv = vUv;
// 波纹扭曲
uv.x += sin(uv.y * uFrequency + uTime) * uAmplitude;
uv.y += cos(uv.x * uFrequency + uTime) * uAmplitude;
gl_FragColor = texture2D(tDiffuse, uv);
}
`
};
马赛克效果
const PixelateShader = {
uniforms: {
tDiffuse: { value: null },
uResolution: { value: new THREE.Vector2() },
uPixelSize: { value: 8.0 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform vec2 uResolution;
uniform float uPixelSize;
varying vec2 vUv;
void main() {
vec2 uv = vUv;
uv = floor(uv * uResolution / uPixelSize) * uPixelSize / uResolution;
gl_FragColor = texture2D(tDiffuse, uv);
}
`
};
色差效果
const ChromaticAberrationShader = {
uniforms: {
tDiffuse: { value: null },
uAmount: { value: 0.005 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float uAmount;
varying vec2 vUv;
void main() {
vec2 offset = (vUv - 0.5) * uAmount;
float r = texture2D(tDiffuse, vUv + offset).r;
float g = texture2D(tDiffuse, vUv).g;
float b = texture2D(tDiffuse, vUv - offset).b;
gl_FragColor = vec4(r, g, b, 1.0);
}
`
};
渲染目标
后期处理使用渲染目标来存储中间结果:
基本概念
// 创建渲染目标
const renderTarget = new THREE.WebGLRenderTarget(width, height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
type: THREE.HalfFloatType
});
// 渲染到目标
renderer.setRenderTarget(renderTarget);
renderer.render(scene, camera);
renderer.setRenderTarget(null); // 恢复默认
// 使用渲染结果
const texture = renderTarget.texture;
多渲染目标(MRT)
同时输出多个纹理:
const renderTarget = new THREE.WebGLMultipleRenderTargets(
width, height, 3 // 3 个输出纹理
);
// 在着色器中使用 gl_FragData
// gl_FragData[0] = colorOutput;
// gl_FragData[1] = normalOutput;
// gl_FragData[2] = depthOutput;
性能优化
效果选择
后期处理效果会增加 GPU 负担,需要权衡:
// 根据设备性能选择效果
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
if (!isMobile) {
// 桌面端使用更多效果
composer.addPass(bloomPass);
composer.addPass(ssaoPass);
}
// 移动端只使用必要的效果
composer.addPass(new ShaderPass(GammaCorrectionShader));
分辨率调整
降低后期处理的分辨率:
// 降低渲染目标分辨率
const scale = 0.5; // 一半分辨率
composer.setSize(
window.innerWidth * scale,
window.innerHeight * scale
);
禁用不需要的效果
// 运行时禁用效果
bloomPass.enabled = false;
// 根据距离或场景动态启用/禁用
function updatePostProcessing(camera) {
const distance = camera.position.length();
bloomPass.enabled = distance < 50;
}
完整示例
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js';
import { VignetteShader } from 'three/addons/shaders/VignetteShader.js';
// 场景设置
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000011);
const camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.1,
100
);
camera.position.set(0, 5, 15);
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);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// 创建发光物体
const geometry = new THREE.IcosahedronGeometry(1, 1);
const material = new THREE.MeshBasicMaterial({
color: 0xff0000
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// 创建多个发光球
const colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff];
for (let i = 0; i < 20; i++) {
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(0.2, 16, 16),
new THREE.MeshBasicMaterial({
color: colors[Math.floor(Math.random() * colors.length)]
})
);
sphere.position.set(
(Math.random() - 0.5) * 20,
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 20
);
scene.add(sphere);
}
// 地面
const groundGeometry = new THREE.PlaneGeometry(50, 50);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x111122,
metalness: 0.8,
roughness: 0.2
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -3;
scene.add(ground);
// 光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xffffff, 1, 100);
pointLight.position.set(0, 10, 0);
scene.add(pointLight);
// 后期处理
const composer = new EffectComposer(renderer);
// 场景渲染
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 发光效果
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, // 强度
0.4, // 半径
0.85 // 阈值
);
composer.addPass(bloomPass);
// 暗角
const vignettePass = new ShaderPass(VignetteShader);
vignettePass.uniforms.offset.value = 1.0;
vignettePass.uniforms.darkness.value = 1.2;
composer.addPass(vignettePass);
// 伽马校正
composer.addPass(new ShaderPass(GammaCorrectionShader));
// 抗锯齿
const fxaaPass = new ShaderPass(FXAAShader);
fxaaPass.uniforms.resolution.value.set(
1 / window.innerWidth,
1 / window.innerHeight
);
composer.addPass(fxaaPass);
// 动画
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const elapsed = clock.getElapsedTime();
// 旋转主物体
mesh.rotation.x = elapsed;
mesh.rotation.y = elapsed * 0.5;
controls.update();
composer.render();
}
animate();
// 响应窗口变化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
fxaaPass.uniforms.resolution.value.set(
1 / window.innerWidth,
1 / window.innerHeight
);
});
// UI 控制
const gui = {
bloomStrength: 1.5,
bloomRadius: 0.4,
bloomThreshold: 0.85,
vignetteOffset: 1.0,
vignetteDarkness: 1.2
};
// 可以添加 dat.gui 或其他 UI 库来控制这些参数
// bloomPass.strength = gui.bloomStrength;
// bloomPass.radius = gui.bloomRadius;
// bloomPass.threshold = gui.bloomThreshold;
// vignettePass.uniforms.offset.value = gui.vignetteOffset;
// vignettePass.uniforms.darkness.value = gui.vignetteDarkness;
小结
后期处理可以大幅提升视觉效果:
- EffectComposer 是后期处理的核心,管理所有处理通道
- RenderPass 是必须的第一个通道,渲染场景到纹理
- UnrealBloomPass 创建发光效果,适合霓虹、科幻风格
- ShaderPass 可以应用各种着色器效果
- 自定义着色器 可以创建独特的后期处理效果
- 性能优化 需要根据设备能力选择合适的效果
后期处理是创造电影级视觉效果的重要工具,但需要注意性能影响,根据实际需求选择合适的效果组合。