threejs-postprocessing

threejs-postprocessing

熱門

Three.js 後製處理 - EffectComposer、bloom、景深、螢幕特效。用於添加視覺效果、色彩校正、模糊、發光,或建立自訂螢幕空間著色器。

2616星標
300分支
更新於 2026/7/9
SKILL.md
唯讀
名稱
threejs-postprocessing
描述

Three.js 後製處理 - EffectComposer、bloom、景深、螢幕特效。用於添加視覺效果、色彩校正、模糊、發光,或建立自訂螢幕空間著色器。

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

// 加入 bloom
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);

// 第一個 pass:渲染場景
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);

// 加入更多 pass...
composer.addPass(effectPass);

// 最後一個 pass 應渲染到螢幕
effectPass.renderToScreen = true; // 最後一個 pass 的預設值

// 處理視窗大小變化
function onResize() {
  const width = window.innerWidth;
  const height = window.innerHeight;

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

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

常見效果

Bloom(發光)

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;

選擇性 Bloom

僅對特定物件套用 bloom。

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() {
  // 渲染 bloom pass
  scene.traverse(darkenNonBloomed);
  composer.render();
  scene.traverse(restoreMaterial);

  // 在 bloom 上渲染最終場景
  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. Bloom
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;

多 Pass 渲染

// 為不同場景/圖層建立多個 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. 限制 pass 數量:每個 pass 都會進行一次全螢幕渲染
  2. 降低解析度:對模糊 pass 使用較小的渲染目標
  3. 關閉未使用的效果:切換 pass 的啟用狀態
  4. 使用 FXAA 而非 MSAA:較便宜的反鋸齒方式
  5. 使用開發者工具分析:檢查 GPU 使用量
// 停用 pass
bloomPass.enabled = false;

// 降低 bloom 解析度
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);

  // 更新特定 pass 的解析度
  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 - 渲染器設定