{T}

后处理效果

后处理(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

属性/方法类型说明
rendererWebGLRenderer关联的渲染器
readBufferWebGLRenderTarget读取缓冲区
writeBufferWebGLRenderTarget写入缓冲区
passesArray通道数组
addPass(pass)Method添加通道
removePass(pass)Method移除通道
render()Method执行渲染
setSize(width, height)Method设置大小
setPixelRatio(ratio)Method设置像素比
reset()Method重置缓冲区
dispose()Method释放资源

Pass 基类

属性/方法类型说明
enabledBoolean是否启用
needsSwapBoolean是否需要交换缓冲区
clearBoolean是否清除
renderToScreenBoolean是否渲染到屏幕
setSize(width, height)Method设置大小
render(renderer, writeBuffer, readBuffer, deltaTime, maskActive)Method渲染方法

常用 Pass 参数

UnrealBloomPass

参数类型范围说明
strengthNumber0 - 10泛光强度
radiusNumber0 - 1泛光半径
thresholdNumber0 - 1亮度阈值

BokehPass

参数类型范围说明
focusNumber0 - 100焦点距离
apertureNumber0 - 0.1光圈大小
maxblurNumber0 - 0.1最大模糊

FilmPass

参数类型范围说明
noiseIntensityNumber0 - 1噪点强度
scanlineIntensityNumber0 - 1扫描线强度
scanlineCountNumber0 - 2000扫描线数量
grayscaleBoolean-是否灰度

配置参数详解

缓冲区配置

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));

相关链接