threejs-postprocessing

threejs-postprocessing

热门

Three.js 后期处理 - EffectComposer、泛光、景深、屏幕特效。用于添加视觉效果、色彩校正、模糊、发光或创建自定义屏幕空间着色器。

2616Star
300Fork
更新于 2026/7/9
SKILL.md
readonly只读
name
threejs-postprocessing
description

Three.js 后期处理 - EffectComposer、泛光、景深、屏幕特效。用于添加视觉效果、色彩校正、模糊、发光或创建自定义屏幕空间着色器。

Three.js 后期处理

快速开始

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

// 设置 composer
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);

// 动画循环 - 使用 composer 而不是 renderer
function animate() {
  requestAnimationFrame(animate);
  composer.render(); // 不是 renderer.render()
}

EffectComposer 设置

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.addPass(effectPass);

// 最后一遍应渲染到屏幕
effectPass.renderToScreen = true; // 默认最后一遍为 true

// 处理窗口大小变化
function onResize() {
  const width = window.innerWidth;
  const height = window.innerHeight;

  camera.aspect = width / height;
  camera.updateProjectionMatrix();

  renderer.setSize(width, height);
  composer.setSize(width, height);
}

常见效果

泛光(发光)

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.0;
bloomPass.threshold = 0.5;
bloomPass.radius = 0.8;

选择性泛光

仅对特定物体应用泛光。

import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";

// 图层设置
const BLOOM_LAYER = 1;
const bloomLayer = new THREE.Layers();
bloomLayer.set(BLOOM_LAYER);

// 标记需要泛光的物体
glowingMesh.layers.enable(BLOOM_LAYER);

// 非泛光物体的暗色材质
const darkMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
const materials = {};

function darkenNonBloomed(obj) {
  if (obj.isMesh && !bloomLayer.test(obj.layers)) {
    materials[obj.uuid] = obj.material;
    obj.material = darkMaterial;
  }
}

function restoreMaterial(obj) {
  if (materials[obj.uuid]) {
    obj.material = materials[obj.uuid];
    delete materials[obj.uuid];
  }
}

// 自定义渲染循环
function render() {
  // 渲染泛光通道
  scene.traverse(darkenNonBloomed);
  composer.render();
  scene.traverse(restoreMaterial);

  // 在泛光之上渲染最终场景
  renderer.render(scene, camera);
}

FXAA(抗锯齿)

import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
import { FXAAShader } from "three/addons/shaders/FXAAShader.js";

const fxaaPass = new ShaderPass(FXAAShader);
fxaaPass.material.uniforms["resolution"].value.set(
  1 / window.innerWidth,
  1 / window.innerHeight,
);

composer.addPass(fxaaPass);

// 窗口大小变化时更新
function onResize() {
  fxaaPass.material.uniforms["resolution"].value.set(
    1 / window.innerWidth,
    1 / window.innerHeight,
  );
}

SMAA(更好的抗锯齿)

import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js";

const smaaPass = new SMAAPass(
  window.innerWidth * renderer.getPixelRatio(),
  window.innerHeight * renderer.getPixelRatio(),
);

composer.addPass(smaaPass);

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

// 输出模式
ssaoPass.output = SSAOPass.OUTPUT.Default;
// SSAOPass.OUTPUT.Default - 最终合成输出
// SSAOPass.OUTPUT.SSAO - 仅 AO
// SSAOPass.OUTPUT.Blur - 模糊后的 AO
// SSAOPass.OUTPUT.Depth - 深度缓冲
// SSAOPass.OUTPUT.Normal - 法线缓冲

景深(DOF)

import { BokehPass } from "three/addons/postprocessing/BokehPass.js";

const bokehPass = new BokehPass(scene, camera, {
  focus: 10.0, // 对焦距离
  aperture: 0.025, // 光圈(越小景深越大)
  maxblur: 0.01, // 最大模糊量
});

composer.addPass(bokehPass);

// 动态更新对焦
bokehPass.uniforms["focus"].value = distanceToTarget;

胶片颗粒

import { FilmPass } from "three/addons/postprocessing/FilmPass.js";

const filmPass = new FilmPass(
  0.35, // 噪声强度
  0.5, // 扫描线强度
  648, // 扫描线数量
  false, // 灰度
);

composer.addPass(filmPass);

暗角

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.0; // 暗角强度

composer.addPass(vignettePass);

色彩校正

import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
import { ColorCorrectionShader } from "three/addons/shaders/ColorCorrectionShader.js";

const colorPass = new ShaderPass(ColorCorrectionShader);
colorPass.uniforms["powRGB"].value = new THREE.Vector3(1.2, 1.2, 1.2); // 幂次
colorPass.uniforms["mulRGB"].value = new THREE.Vector3(1.0, 1.0, 1.0); // 乘法

composer.addPass(colorPass);

Gamma 校正

import { GammaCorrectionShader } from "three/addons/shaders/GammaCorrectionShader.js";

const gammaPass = new ShaderPass(GammaCorrectionShader);
composer.addPass(gammaPass);

像素化

import { RenderPixelatedPass } from "three/addons/postprocessing/RenderPixelatedPass.js";

const pixelPass = new RenderPixelatedPass(6, scene, camera); // 6 = 像素大小

composer.addPass(pixelPass);

故障效果

import { GlitchPass } from "three/addons/postprocessing/GlitchPass.js";

const glitchPass = new GlitchPass();
glitchPass.goWild = false; // 持续故障

composer.addPass(glitchPass);

半色调

import { HalftonePass } from "three/addons/postprocessing/HalftonePass.js";

const halftonePass = new HalftonePass(window.innerWidth, window.innerHeight, {
  shape: 1, // 1 = 点, 2 = 椭圆, 3 = 线, 4 = 方形
  radius: 4, // 点大小
  rotateR: Math.PI / 12,
  rotateB: (Math.PI / 12) * 2,
  rotateG: (Math.PI / 12) * 3,
  scatter: 0,
  blending: 1,
  blendingMode: 1,
  greyscale: false,
});

composer.addPass(halftonePass);

轮廓

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;
outlinePass.edgeThickness = 1;
outlinePass.pulsePeriod = 0;
outlinePass.visibleEdgeColor.set(0xffffff);
outlinePass.hiddenEdgeColor.set(0x190a05);

// 选择要描边的物体
outlinePass.selectedObjects = [mesh1, mesh2];

composer.addPass(outlinePass);

自定义 ShaderPass

创建你自己的后期处理效果。

import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";

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() {
      vec2 uv = vUv;

      // 波浪扭曲
      uv.x += sin(uv.y * 10.0 + time) * 0.01 * intensity;

      vec4 color = texture2D(tDiffuse, uv);
      gl_FragColor = color;
    }
  `,
};

const customPass = new ShaderPass(CustomShader);
composer.addPass(customPass);

// 在动画循环中更新
customPass.uniforms.time.value = clock.getElapsedTime();

颜色反转着色器

const InvertShader = {
  uniforms: {
    tDiffuse: { value: null },
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform sampler2D tDiffuse;
    varying vec2 vUv;

    void main() {
      vec4 color = texture2D(tDiffuse, vUv);
      gl_FragColor = vec4(1.0 - color.rgb, color.a);
    }
  `,
};

色差

const ChromaticAberrationShader = {
  uniforms: {
    tDiffuse: { value: null },
    amount: { 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 amount;
    varying vec2 vUv;

    void main() {
      vec2 dir = vUv - 0.5;
      float dist = length(dir);

      float r = texture2D(tDiffuse, vUv - dir * amount * dist).r;
      float g = texture2D(tDiffuse, vUv).g;
      float b = texture2D(tDiffuse, vUv + dir * amount * dist).b;

      gl_FragColor = vec4(r, g, b, 1.0);
    }
  `,
};

组合多个效果

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 { VignetteShader } from "three/addons/shaders/VignetteShader.js";
import { GammaCorrectionShader } from "three/addons/shaders/GammaCorrectionShader.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 = 0.95;
vignettePass.uniforms["darkness"].value = 1.0;
composer.addPass(vignettePass);

// 4. Gamma 校正
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 renderTarget = new THREE.WebGLRenderTarget(512, 512);

// 将场景渲染到目标
renderer.setRenderTarget(renderTarget);
renderer.render(scene, camera);
renderer.setRenderTarget(null);

// 使用纹理
const texture = renderTarget.texture;
otherMaterial.map = texture;

多通道渲染

// 为不同场景/图层创建多个 composer
const bgComposer = new EffectComposer(renderer);
bgComposer.addPass(new RenderPass(bgScene, camera));

const fgComposer = new EffectComposer(renderer);
fgComposer.addPass(new RenderPass(fgScene, camera));
fgComposer.addPass(bloomPass);

// 在渲染循环中组合
function animate() {
  // 渲染背景,不清除
  renderer.autoClear = false;
  renderer.clear();

  bgComposer.render();

  // 在其上渲染前景
  renderer.clearDepth();
  fgComposer.render();
}

WebGPU 后期处理(Three.js r150+)

import { postProcessing } from "three/addons/nodes/Nodes.js";
import { pass, bloom, dof } from "three/addons/nodes/Nodes.js";

// 使用基于节点的系统
const scenePass = pass(scene, camera);
const bloomNode = bloom(scenePass, 0.5, 0.4, 0.85);

const postProcessing = new THREE.PostProcessing(renderer);
postProcessing.outputNode = bloomNode;

// 渲染
function animate() {
  postProcessing.render();
}

性能提示

  1. 限制通道数:每个通道增加一次全屏渲染
  2. 降低分辨率:对模糊通道使用较小的渲染目标
  3. 禁用未使用的效果:切换通道的启用/禁用
  4. 使用 FXAA 代替 MSAA:更经济的抗锯齿
  5. 使用开发者工具分析:检查 GPU 使用情况
// 禁用通道
bloomPass.enabled = false;

// 降低泛光分辨率
const bloomPass = new UnrealBloomPass(
  new THREE.Vector2(window.innerWidth / 2, window.innerHeight / 2),
  strength,
  radius,
  threshold,
);

// 仅在高性能场景下应用效果
const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);
if (!isMobile) {
  composer.addPass(expensivePass);
}

处理窗口大小变化

function onWindowResize() {
  const width = window.innerWidth;
  const height = window.innerHeight;
  const pixelRatio = renderer.getPixelRatio();

  camera.aspect = width / height;
  camera.updateProjectionMatrix();

  renderer.setSize(width, height);
  composer.setSize(width, height);

  // 更新通道特定的分辨率
  if (fxaaPass) {
    fxaaPass.material.uniforms["resolution"].value.set(
      1 / (width * pixelRatio),
      1 / (height * pixelRatio),
    );
  }

  if (bloomPass) {
    bloomPass.resolution.set(width, height);
  }
}

window.addEventListener("resize", onWindowResize);

参见

  • threejs-shaders - 自定义着色器开发
  • threejs-textures - 渲染目标
  • threejs-fundamentals - 渲染器设置