后处理效果
后处理(Post-processing)是在场景渲染完成后对图像进行的额外处理,可以实现各种视觉效果,如模糊、发光、色彩调整、景深等。
系统架构
code
┌─────────────────────────────────────────────────────────────────────────┐
│ 后处理渲染管线 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 场景渲染 │───▶│ RenderPass │───▶│ 缓冲区 A │ │
│ │ Scene+Camera│ │ │ │ WebGLRenderTarget │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ BloomPass │───▶│ ShaderPass │───▶│ 缓冲区 B │ │
│ │ 泛光效果 │ │ 着色器处理 │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ FilmPass │───▶│ OutputPass │───▶│ 屏幕 │ │
│ │ 胶片效果 │ │ 输出 │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
数据流向:
场景 → RenderPass → 缓冲区 → [各种 Pass 处理] → OutputPass → 屏幕核心组件
| 组件 | 说明 | 职责 |
|---|---|---|
| EffectComposer | 后处理器 | 管理渲染通道队列、缓冲区 |
| RenderPass | 渲染通道 | 将场景渲染到缓冲区 |
| ShaderPass | 着色器通道 | 执行自定义着色器处理 |
| OutputPass | 输出通道 | 将结果输出到屏幕(Three.js r150+) |
| WebGLRenderTarget | 渲染目标 | 存储中间渲染结果 |
概述
后处理通过 EffectComposer 实现多个渲染通道(Pass)的串联,每个通道可以对图像进行特定的处理,最终输出到屏幕。
后处理优势
| 优势 | 说明 |
|---|---|
| 效果丰富 | 可实现发光、模糊、色彩校正等复杂效果 |
| 独立于场景 | 不影响原始场景设置 |
| 可组合 | 多个效果可以叠加使用 |
| 易于控制 | 可动态调整效果参数 |
应用场景
- 游戏开发:发光、景深、色彩调整
- 数据可视化:高光效果、边缘检测
- 建筑可视化:环境光遮蔽、色调映射
- 艺术效果:故障艺术、像素化
EffectComposer 基础
设置 EffectComposer
javascript
import * as THREE from 'three';
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 { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
// 创建场景、相机、渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// ==================== 创建后处理器 ====================
const composer = new EffectComposer(renderer);
// 1. 添加渲染通道(必须第一个)
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 2. 添加泛光通道
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, // 强度
0.4, // 半径
0.85 // 阈值
);
composer.addPass(bloomPass);
// 3. 添加输出通道(必须最后一个)
const outputPass = new OutputPass();
composer.addPass(outputPass);
// ==================== 渲染循环 ====================
function animate() {
requestAnimationFrame(animate);
// 使用 composer.render() 替代 renderer.render()
composer.render();
}
animate();缓冲区管理
javascript
// 自定义缓冲区设置
const composer = new EffectComposer(renderer, {
// 渲染目标选项
format: THREE.RGBAFormat,
type: THREE.HalfFloatType, // HDR 支持
stencilBuffer: false
});
// 获取/设置读写缓冲区
const readBuffer = composer.readBuffer;
const writeBuffer = composer.writeBuffer;
// 交换缓冲区
composer.swapBuffers();
// 重置大小
composer.setSize(width, height);多渲染目标(MRT)
javascript
// 使用多渲染目标存储多个数据
import { WebGLMultipleRenderTargets } from 'three';
// 创建 MRT
const targets = new WebGLMultipleRenderTargets(width, height, 3);
// 配置每个目标
targets.texture[0].name = 'diffuse'; // 漫反射
targets.texture[1].name = 'normal'; // 法线
targets.texture[2].name = 'depth'; // 深度
// 在着色器中使用
// gl_FragData[0] = diffuseColor;
// gl_FragData[1] = vec4(normal, 1.0);
// gl_FragData[2] = vec4(depth, 1.0);常用后处理通道
RenderPass
基础渲染通道,将场景渲染到缓冲区。必须作为第一个通道添加。
javascript
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 属性
renderPass.clearColor = new THREE.Color(0x000000); // 清除颜色
renderPass.clearAlpha = 0; // 清除透明度
renderPass.clearDepth = true; // 是否清除深度
renderPass.clearStencil = true; // 是否清除模板UnrealBloomPass
泛光效果,模拟发光物体周围的光晕,基于虚幻引擎的泛光算法。
javascript
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, // strength - 泛光强度
0.4, // radius - 泛光半径
0.85 // threshold - 亮度阈值(超过此值会发光)
);
composer.addPass(bloomPass);
// 动态调整参数
bloomPass.strength = 2.0; // 强度:0 - 3
bloomPass.radius = 0.5; // 半径:0 - 1
bloomPass.threshold = 0.8; // 阈值:0 - 1
// 选择性泛光(仅对特定物体)
const bloomLayer = new THREE.Layers();
bloomLayer.set(1);
// 物体设置到泛光层
glowingObject.layers.set(1);
// 使用覆盖材质模式
bloomPass.renderToScreen = false;BloomPass(简化版)
javascript
import { BloomPass } from 'three/addons/postprocessing/BloomPass.js';
const bloomPass = new BloomPass(
1.0, // strength
25, // kernelSize
4.0, // sigma
256 // resolution
);
composer.addPass(bloomPass);FilmPass
胶片效果,添加噪点和扫描线,模拟老电影效果。
javascript
import { FilmPass } from 'three/addons/postprocessing/FilmPass.js';
const filmPass = new FilmPass(
0.35, // noiseIntensity - 噪点强度
0.025, // scanlineIntensity - 扫描线强度
648, // scanlineCount - 扫描线数量
false // grayscale - 是否灰度
);
composer.addPass(filmPass);
// 动态调整
filmPass.uniforms.nIntensity.value = 0.5; // 噪点强度
filmPass.uniforms.sIntensity.value = 0.5; // 扫描线强度
filmPass.uniforms.sCount.value = 800; // 扫描线数量GlitchPass
故障效果,模拟画面干扰、数字故障。
javascript
import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';
const glitchPass = new GlitchPass();
composer.addPass(glitchPass);
// 控制故障
glitchPass.goWild = true; // 持续故障
glitchPass.curF = 0; // 当前故障帧
glitchPass.randX = 0; // 随机种子
// 自定义故障模式
glitchPass.uniforms['byp'].value = 0; // 0=启用, 1=禁用BokehPass
景深效果,模拟相机对焦,远处和近处物体模糊。
javascript
import { BokehPass } from 'three/addons/postprocessing/BokehPass.js';
const bokehPass = new BokehPass(scene, camera, {
focus: 1.0, // focus - 焦点距离
aperture: 0.025, // aperture - 光圈大小
maxblur: 0.01 // maxblur - 最大模糊程度
});
composer.addPass(bokehPass);
// 动态调整焦点
bokehPass.uniforms['focus'].value = 2.0;
bokehPass.uniforms['aperture'].value = 0.05;
bokehPass.uniforms['maxblur'].value = 0.02;SSAOPass
屏幕空间环境光遮蔽,增强场景深度感。
javascript
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);
// 输出模式
ssaoPass.output = SSAOPass.OUTPUT.Default; // 正常输出
ssaoPass.output = SSAOPass.OUTPUT.SSAO; // 仅 SSAO
ssaoPass.output = SSAOPass.OUTPUT.Blur; // 模糊结果
ssaoPass.output = SSAOPass.OUTPUT.Depth; // 深度图
ssaoPass.output = SSAOPass.OUTPUT.Normal; // 法线图SAOPass
简化环境光遮蔽,性能更好。
javascript
import { SAOPass } from 'three/addons/postprocessing/SAOPass.js';
const saoPass = new SAOPass(scene, camera);
saoPass.params.saoBias = 0.5;
saoPass.params.saoIntensity = 0.02;
saoPass.params.saoScale = 1;
saoPass.params.saoKernelRadius = 100;
saoPass.params.saoMinResolution = 0;
composer.addPass(saoPass);ShaderPass
自定义着色器通道,用于执行自定义 GLSL 着色器。
javascript
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { CopyShader } from 'three/addons/shaders/CopyShader.js';
// 复制着色器(最基础)
const copyPass = new ShaderPass(CopyShader);
composer.addPass(copyPass);
// 自定义着色器
const customShader = {
uniforms: {
tDiffuse: { value: null }, // 输入纹理(必须)
time: { value: 0 },
intensity: { value: 1.0 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float time;
uniform float intensity;
varying vec2 vUv;
void main() {
vec4 color = texture2D(tDiffuse, vUv);
// 自定义处理...
gl_FragColor = color;
}
`
};
const customPass = new ShaderPass(customShader);
composer.addPass(customPass);OutputPass
输出通道,处理最终的色彩空间转换(sRGB)和输出。
javascript
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
const outputPass = new OutputPass();
composer.addPass(outputPass);
// 通常是最后一个通道
// 自动处理:
// - sRGB 色彩空间转换
// - 伽马校正
// - 色调映射SavePass
保存当前渲染结果到纹理。
javascript
import { SavePass } from 'three/addons/postprocessing/SavePass.js';
const savePass = new SavePass();
composer.addPass(savePass);
// 获取保存的纹理
const savedTexture = savePass.renderTarget.texture;ClearPass
清除缓冲区。
javascript
import { ClearPass } from 'three/addons/postprocessing/ClearPass.js';
const clearPass = new ClearPass();
clearPass.clearColor = new THREE.Color(0x000000);
clearPass.clearAlpha = 1.0;
composer.addPass(clearPass);内置着色器效果
FXAA 抗锯齿
快速近似抗锯齿,性能好但质量一般。
javascript
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
const fxaaPass = new ShaderPass(FXAAShader);
fxaaPass.uniforms['resolution'].value.set(
1 / window.innerWidth,
1 / window.innerHeight
);
composer.addPass(fxaaPass);SMAA 抗锯齿
子像素形态学抗锯齿,质量更好。
javascript
import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
const smaaPass = new SMAAPass(window.innerWidth, window.innerHeight);
composer.addPass(smaaPass);色彩调整
javascript
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { ColorCorrectionShader } from 'three/addons/shaders/ColorCorrectionShader.js';
const colorCorrectionPass = new ShaderPass(ColorCorrectionShader);
// powRGB: 颜色幂次调整(类似 Photoshop 的曲线)
colorCorrectionPass.uniforms['powRGB'].value.set(1.2, 1.1, 1.0);
// mulRGB: 颜色乘数调整
colorCorrectionPass.uniforms['mulRGB'].value.set(1.1, 1.1, 1.0);
// addRGB: 颜色加法调整
colorCorrectionPass.uniforms['addRGB'].value.set(0.0, 0.0, 0.0);
composer.addPass(colorCorrectionPass);棕褐色滤镜
javascript
import { SepiaShader } from 'three/addons/shaders/SepiaShader.js';
const sepiaPass = new ShaderPass(SepiaShader);
sepiaPass.uniforms['amount'].value = 1.0; // 0 - 1
composer.addPass(sepiaPass);晕影效果
javascript
import { VignetteShader } from 'three/addons/shaders/VignetteShader.js';
const vignettePass = new ShaderPass(VignetteShader);
vignettePass.uniforms['offset'].value = 1.0; // 偏移量
vignettePass.uniforms['darkness'].value = 1.2; // 暗度
composer.addPass(vignettePass);亮度/对比度
javascript
import { BrightnessContrastShader } from 'three/addons/shaders/BrightnessContrastShader.js';
const bcPass = new ShaderPass(BrightnessContrastShader);
bcPass.uniforms['brightness'].value = 0.0; // -1 到 1
bcPass.uniforms['contrast'].value = 0.0; // -1 到 1
composer.addPass(bcPass);色调映射
javascript
import { ToneMapShader } from 'three/addons/shaders/ToneMapShader.js';
const toneMapPass = new ShaderPass(ToneMapShader);
toneMapPass.uniforms['averageLuminance'].value = 1.0;
toneMapPass.uniforms['luminanceKey'].value = 0.5;
toneMapPass.uniforms['maxLuminance'].value = 16.0;
toneMapPass.uniforms['minLuminance'].value = 0.01;
composer.addPass(toneMapPass);色相/饱和度
javascript
import { HueSaturationShader } from 'three/addons/shaders/HueSaturationShader.js';
const hsPass = new ShaderPass(HueSaturationShader);
hsPass.uniforms['hue'].value = 0.0; // 色相偏移
hsPass.uniforms['saturation'].value = 0.0; // 饱和度调整
composer.addPass(hsPass);RGB 位移
javascript
import { RGBShiftShader } from 'three/addons/shaders/RGBShiftShader.js';
const rgbShiftPass = new ShaderPass(RGBShiftShader);
rgbShiftPass.uniforms['amount'].value = 0.005; // 位移量
rgbShiftPass.uniforms['angle'].value = 0.0; // 角度
composer.addPass(rgbShiftPass);半色调效果
javascript
import { HalftoneShader } from 'three/addons/shaders/HalftoneShader.js';
const halftonePass = new ShaderPass(HalftoneShader);
halftonePass.uniforms['tSize'].value.set(256, 256);
halftonePass.uniforms['radius'].value = 4;
composer.addPass(halftonePass);自定义后处理着色器
创建自定义着色器通道
javascript
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
// 自定义着色器定义
const CustomShader = {
// 必须包含 tDiffuse uniform
uniforms: {
tDiffuse: { value: null }, // 输入纹理(EffectComposer 自动设置)
time: { value: 0 },
intensity: { value: 1.0 },
resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float time;
uniform float intensity;
uniform vec2 resolution;
varying vec2 vUv;
void main() {
// 采样原始颜色
vec4 color = texture2D(tDiffuse, vUv);
// 应用效果:波浪扭曲
float distortion = sin(vUv.x * 10.0 + time) * 0.01 * intensity;
vec2 uv = vUv + vec2(distortion, 0.0);
color = texture2D(tDiffuse, uv);
gl_FragColor = color;
}
`
};
const customPass = new ShaderPass(CustomShader);
composer.addPass(customPass);
// 更新 uniform
function animate() {
requestAnimationFrame(animate);
customPass.uniforms.time.value = performance.now() * 0.001;
composer.render();
}像素化效果
javascript
const PixelShader = {
uniforms: {
tDiffuse: { value: null },
resolution: { value: new THREE.Vector2(256, 256) },
pixelSize: { 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 resolution;
uniform float pixelSize;
varying vec2 vUv;
void main() {
// 计算像素化的 UV
vec2 pixelUv = floor(vUv * resolution / pixelSize) * pixelSize / resolution;
vec4 color = texture2D(tDiffuse, pixelUv);
gl_FragColor = color;
}
`
};
const pixelPass = new ShaderPass(PixelShader);
pixelPass.uniforms.resolution.value.set(window.innerWidth, window.innerHeight);
composer.addPass(pixelPass);色彩分离效果(色差)
javascript
const ChromaticAberrationShader = {
uniforms: {
tDiffuse: { value: null },
amount: { value: 0.005 },
angle: { value: 0.0 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float amount;
uniform float angle;
varying vec2 vUv;
void main() {
// 计算偏移方向
vec2 offset = amount * vec2(
cos(angle + vUv.y * 30.0),
sin(angle + vUv.x * 30.0)
);
// 分别采样 RGB 通道
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 chromaticPass = new ShaderPass(ChromaticAberrationShader);
composer.addPass(chromaticPass);边缘检测
javascript
const EdgeDetectionShader = {
uniforms: {
tDiffuse: { value: null },
resolution: { value: new THREE.Vector2() },
threshold: { value: 0.1 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform vec2 resolution;
uniform float threshold;
varying vec2 vUv;
void main() {
vec2 texel = vec2(1.0) / resolution;
// Sobel 算子
float tl = texture2D(tDiffuse, vUv + texel * vec2(-1, -1)).r;
float t = texture2D(tDiffuse, vUv + texel * vec2( 0, -1)).r;
float tr = texture2D(tDiffuse, vUv + texel * vec2( 1, -1)).r;
float l = texture2D(tDiffuse, vUv + texel * vec2(-1, 0)).r;
float r = texture2D(tDiffuse, vUv + texel * vec2( 1, 0)).r;
float bl = texture2D(tDiffuse, vUv + texel * vec2(-1, 1)).r;
float b = texture2D(tDiffuse, vUv + texel * vec2( 0, 1)).r;
float br = texture2D(tDiffuse, vUv + texel * vec2( 1, 1)).r;
// Sobel 梯度
float gx = -tl - 2.0*l - bl + tr + 2.0*r + br;
float gy = -tl - 2.0*t - tr + bl + 2.0*b + br;
float edge = sqrt(gx*gx + gy*gy);
// 应用阈值
edge = step(threshold, edge);
vec4 originalColor = texture2D(tDiffuse, vUv);
gl_FragColor = vec4(vec3(edge), 1.0);
}
`
};
const edgePass = new ShaderPass(EdgeDetectionShader);
edgePass.uniforms.resolution.value.set(window.innerWidth, window.innerHeight);
composer.addPass(edgePass);模糊效果
javascript
// 简单高斯模糊
const GaussianBlurShader = {
uniforms: {
tDiffuse: { value: null },
resolution: { value: new THREE.Vector2() },
direction: { value: new THREE.Vector2(1, 0) },
radius: { value: 1.0 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform vec2 resolution;
uniform vec2 direction;
uniform float radius;
varying vec2 vUv;
void main() {
vec4 color = vec4(0.0);
vec2 texelSize = vec2(1.0) / resolution * radius;
// 5x1 高斯核
float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
color += texture2D(tDiffuse, vUv) * weights[0];
for (int i = 1; i < 5; i++) {
vec2 offset = direction * texelSize * float(i);
color += texture2D(tDiffuse, vUv + offset) * weights[i];
color += texture2D(tDiffuse, vUv - offset) * weights[i];
}
gl_FragColor = color;
}
`
};
// 双向模糊(水平 + 垂直)
const blurPassH = new ShaderPass(GaussianBlurShader);
blurPassH.uniforms.direction.value.set(1, 0);
composer.addPass(blurPassH);
const blurPassV = new ShaderPass(GaussianBlurShader);
blurPassV.uniforms.direction.value.set(0, 1);
composer.addPass(blurPassV);后处理组合示例
电影效果
javascript
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { FilmPass } from 'three/addons/postprocessing/FilmPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { VignetteShader } from 'three/addons/shaders/VignetteShader.js';
import { ColorCorrectionShader } from 'three/addons/shaders/ColorCorrectionShader.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
const composer = new EffectComposer(renderer);
// 1. 基础渲染
composer.addPass(new RenderPass(scene, camera));
// 2. 色彩校正(模拟电影色调)
const colorPass = new ShaderPass(ColorCorrectionShader);
colorPass.uniforms['powRGB'].value.set(1.1, 1.0, 0.95); // 温暖色调
composer.addPass(colorPass);
// 3. 胶片效果
composer.addPass(new FilmPass(0.2, 0.1, 648, false));
// 4. 晕影
const vignettePass = new ShaderPass(VignetteShader);
vignettePass.uniforms['offset'].value = 1.2;
vignettePass.uniforms['darkness'].value = 1.2;
composer.addPass(vignettePass);
// 5. 输出
composer.addPass(new OutputPass());发光效果
javascript
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.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),
1.5, // strength
0.4, // radius
0.85 // threshold
);
composer.addPass(bloomPass);
// 3. 输出
composer.addPass(new OutputPass());
// 控制特定物体的发光
const emissiveMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000,
emissive: 0xff0000,
emissiveIntensity: 2
});夜视效果
javascript
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
const NightVisionShader = {
uniforms: {
tDiffuse: { value: null },
time: { value: 0 },
noiseIntensity: { value: 0.1 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float time;
uniform float noiseIntensity;
varying vec2 vUv;
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
void main() {
// 采样颜色
vec4 color = texture2D(tDiffuse, vUv);
// 转换为亮度
float luminance = dot(color.rgb, vec3(0.299, 0.587, 0.114));
// 放大亮度
luminance = pow(luminance, 0.5) * 1.5;
// 绿色调
vec3 nightColor = vec3(0.1, luminance, 0.1);
// 添加噪点
float noise = random(vUv + time) * noiseIntensity;
nightColor += noise;
// 晕影
float vignette = 1.0 - length(vUv - 0.5) * 0.8;
nightColor *= vignette;
gl_FragColor = vec4(nightColor, 1.0);
}
`
};
const nightVisionPass = new ShaderPass(NightVisionShader);
composer.addPass(nightVisionPass);综合效果
javascript
// 完整的游戏风格后处理
function setupPostProcessing(renderer, scene, camera) {
const composer = new EffectComposer(renderer);
// 1. 渲染
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 2. SSAO
const ssaoPass = new SSAOPass(scene, camera, window.innerWidth, window.innerHeight);
ssaoPass.kernelRadius = 16;
composer.addPass(ssaoPass);
// 3. 泛光
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
0.5, 0.4, 0.85
);
composer.addPass(bloomPass);
// 4. FXAA 抗锯齿
const fxaaPass = new ShaderPass(FXAAShader);
fxaaPass.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight);
composer.addPass(fxaaPass);
// 5. 色彩校正
const colorPass = new ShaderPass(ColorCorrectionShader);
colorPass.uniforms['powRGB'].value.set(1.0, 1.0, 1.05);
colorPass.uniforms['mulRGB'].value.set(1.1, 1.0, 1.0);
composer.addPass(colorPass);
// 6. 晕影
const vignettePass = new ShaderPass(VignetteShader);
vignettePass.uniforms['offset'].value = 1.0;
vignettePass.uniforms['darkness'].value = 1.2;
composer.addPass(vignettePass);
// 7. 输出
composer.addPass(new OutputPass());
return composer;
}API 参考
EffectComposer
| 属性/方法 | 类型 | 说明 |
|---|---|---|
renderer | WebGLRenderer | 关联的渲染器 |
readBuffer | WebGLRenderTarget | 读取缓冲区 |
writeBuffer | WebGLRenderTarget | 写入缓冲区 |
passes | Array | 通道数组 |
addPass(pass) | Method | 添加通道 |
removePass(pass) | Method | 移除通道 |
render() | Method | 执行渲染 |
setSize(width, height) | Method | 设置大小 |
setPixelRatio(ratio) | Method | 设置像素比 |
reset() | Method | 重置缓冲区 |
dispose() | Method | 释放资源 |
Pass 基类
| 属性/方法 | 类型 | 说明 |
|---|---|---|
enabled | Boolean | 是否启用 |
needsSwap | Boolean | 是否需要交换缓冲区 |
clear | Boolean | 是否清除 |
renderToScreen | Boolean | 是否渲染到屏幕 |
setSize(width, height) | Method | 设置大小 |
render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) | Method | 渲染方法 |
常用 Pass 参数
UnrealBloomPass
| 参数 | 类型 | 范围 | 说明 |
|---|---|---|---|
strength | Number | 0 - 10 | 泛光强度 |
radius | Number | 0 - 1 | 泛光半径 |
threshold | Number | 0 - 1 | 亮度阈值 |
BokehPass
| 参数 | 类型 | 范围 | 说明 |
|---|---|---|---|
focus | Number | 0 - 100 | 焦点距离 |
aperture | Number | 0 - 0.1 | 光圈大小 |
maxblur | Number | 0 - 0.1 | 最大模糊 |
FilmPass
| 参数 | 类型 | 范围 | 说明 |
|---|---|---|---|
noiseIntensity | Number | 0 - 1 | 噪点强度 |
scanlineIntensity | Number | 0 - 1 | 扫描线强度 |
scanlineCount | Number | 0 - 2000 | 扫描线数量 |
grayscale | Boolean | - | 是否灰度 |
配置参数详解
缓冲区配置
javascript
// 创建 EffectComposer 时的配置
const composer = new EffectComposer(renderer, {
// 格式
format: THREE.RGBAFormat, // 颜色格式
type: THREE.HalfFloatType, // 数据类型(HDR)
stencilBuffer: false, // 模板缓冲
depthBuffer: true, // 深度缓冲
// 多采样(抗锯齿)
samples: 8 // MSAA 采样数
});分辨率策略
javascript
// 全分辨率
composer.setSize(window.innerWidth, window.innerHeight);
// 半分辨率(性能优化)
composer.setSize(window.innerWidth * 0.5, window.innerHeight * 0.5);
// 自适应分辨率
function updateResolution() {
const scale = window.devicePixelRatio > 1 ? 0.5 : 1;
composer.setSize(
window.innerWidth * scale,
window.innerHeight * scale
);
renderer.setSize(window.innerWidth, window.innerHeight);
}渲染目标配置
javascript
// HDR 渲染目标
const hdrRenderTarget = new THREE.WebGLRenderTarget(width, height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
type: THREE.HalfFloatType,
stencilBuffer: false
});
// 使用自定义渲染目标
const composer = new EffectComposer(renderer, hdrRenderTarget);性能优化
减少后处理通道
javascript
// ❌ 不推荐:太多通道
composer.addPass(renderPass);
composer.addPass(ssaoPass);
composer.addPass(bloomPass);
composer.addPass(filmPass);
composer.addPass(glitchPass);
composer.addPass(colorPass);
// ✅ 推荐:必要的通道
composer.addPass(renderPass);
composer.addPass(bloomPass);
composer.addPass(outputPass);降低分辨率
javascript
// 方法一:整体降低
const scale = 0.5;
composer.setSize(
window.innerWidth * scale,
window.innerHeight * scale
);
renderer.setSize(window.innerWidth, window.innerHeight);
// 方法二:Pass 级别降低
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth * 0.5, window.innerHeight * 0.5)
);条件性后处理
javascript
// 根据设备性能调整
const isMobile = /Mobile/.test(navigator.userAgent);
const quality = isMobile ? 'low' : 'high';
if (quality === 'high') {
composer.addPass(bloomPass);
composer.addPass(ssaoPass);
composer.addPass(fxaaPass);
} else {
// 只使用基础渲染
renderer.render(scene, camera);
}
// 或使用 Pass 的 enabled 属性
bloomPass.enabled = !isMobile;
ssaoPass.enabled = !isMobile;Pass 复用
javascript
// 复用渲染目标
const sharedRenderTarget = new THREE.WebGLRenderTarget(width, height);
// 多个 Pass 使用相同资源时,避免重复创建内存管理
javascript
// 正确释放资源
function dispose() {
composer.passes.forEach(pass => {
if (pass.dispose) pass.dispose();
});
composer.dispose();
}
// 页面卸载时清理
window.addEventListener('beforeunload', dispose);常见问题
Q1: 后处理不显示效果?
A: 检查以下几点:
javascript
// 1. 确保 RenderPass 在最前面
composer.addPass(new RenderPass(scene, camera)); // 第一个
// 2. 确保 OutputPass 在最后面(r150+)
composer.addPass(new OutputPass()); // 最后一个
// 3. 确保使用 composer.render() 而非 renderer.render()
function animate() {
composer.render(); // ✅ 正确
// renderer.render(scene, camera); // ❌ 错误
}
// 4. 检查 Pass 是否启用
bloomPass.enabled = true; // 确保为 true
// 5. 检查参数是否正确
console.log(bloomPass.strength); // 确保参数有效Q2: 泛光效果不工作?
A: 可能原因及解决方案:
javascript
// 1. 阈值设置问题
bloomPass.threshold = 0.5; // 降低阈值
// 2. 物体颜色太暗
// 确保物体颜色足够亮(RGB 值 > 阈值)
const material = new THREE.MeshBasicMaterial({
color: 0xffffff, // 白色(最亮)
emissive: 0xffffff,
emissiveIntensity: 2 // 增加自发光
});
// 3. 强度太低
bloomPass.strength = 2.0;
// 4. 渲染顺序问题
// 确保泛光 Pass 在 RenderPass 之后Q3: 如何只对特定物体应用后处理?
A: 使用多层渲染:
javascript
// 方法一:使用 Layers
const bloomLayer = new THREE.Layers();
bloomLayer.set(1);
// 将发光物体设置到层 1
glowingObject.layers.enable(1);
// 自定义泛光材质
const bloomComposer = new EffectComposer(renderer);
// ... 设置只渲染层 1 的泛光
// 方法二:使用选择性渲染
const bloomPass = new UnrealBloomPass(...);
// 在渲染前设置物体可见性
scene.traverse(obj => {
if (obj.isMesh) {
obj.userData.originalVisible = obj.visible;
obj.visible = obj.layers.test(bloomLayer);
}
});Q4: 后处理后颜色变暗或变亮?
A: 色彩空间问题:
javascript
// 1. 确保渲染器设置正确
renderer.outputColorSpace = THREE.SRGBColorSpace;
// 2. 使用 OutputPass(r150+)
composer.addPass(new OutputPass());
// 3. 或手动处理
import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js';
const gammaPass = new ShaderPass(GammaCorrectionShader);
composer.addPass(gammaPass);
// 4. 检查材质设置
material.toneMapped = true;Q5: 景深效果不自然?
A: 调整参数:
javascript
const bokehPass = new BokehPass(scene, camera, {
focus: 10.0, // 焦点距离(与场景匹配)
aperture: 0.001, // 光圈(值越小景深越浅)
maxblur: 0.01 // 最大模糊
});
// 动态调整焦点(跟踪物体)
function updateFocus(targetObject) {
const distance = camera.position.distanceTo(targetObject.position);
bokehPass.uniforms['focus'].value = distance;
}Q6: 性能太差怎么办?
A: 优化策略:
javascript
// 1. 减少通道数量
// 只保留必要的效果
// 2. 降低分辨率
composer.setSize(width * 0.5, height * 0.5);
// 3. 使用简化版本
// SAOPass 替代 SSAOPass
// 4. 条件启用
if (performanceMode) {
bloomPass.enabled = false;
ssaoPass.enabled = false;
}
// 5. 降低 Pass 内部分辨率
bloomPass.resolution.set(width * 0.5, height * 0.5);
// 6. 减少采样
ssaoPass.samples = 8; // 减少采样数Q7: 如何实现后处理动画?
A: 动态更新参数:
javascript
// 更新 Pass uniform
function animate() {
const time = performance.now() * 0.001;
// 动态泛光
bloomPass.strength = 1 + Math.sin(time) * 0.5;
// 动态色彩
colorPass.uniforms['powRGB'].value.set(
1 + Math.sin(time) * 0.2,
1,
1 + Math.cos(time) * 0.2
);
// 动态晕影
vignettePass.uniforms['darkness'].value = 1 + Math.sin(time * 0.5) * 0.5;
composer.render();
}Q8: OutputPass 和直接输出有什么区别?
A:
javascript
// OutputPass 自动处理:
// 1. sRGB 色彩空间转换
// 2. 伽马校正
// 3. 色调映射
// 4. 正确的最终输出
// 不使用 OutputPass 可能导致:
// - 颜色偏暗或偏亮
// - 色彩不准确
// - 线性空间显示到屏幕
// Three.js r150+ 推荐使用 OutputPass
composer.addPass(new OutputPass());
// 旧版本使用:
import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js';
composer.addPass(new ShaderPass(GammaCorrectionShader));