本文 AI 產出,尚未審核

Three.js 後製特效(Post‑Processing) – EffectComposer 完全指南


簡介

在 3D 網頁應用中,渲染畫面本身往往只能提供基本的光影與材質,要讓作品更具視覺衝擊力,常需要在渲染結果上再加上一層或多層的後製特效(post‑processing)。Three.js 官方提供的 EffectComposer 正是為了這個目的而設計的,它把 渲染管線抽象成可插拔的 Pass(通道),讓開發者可以像拼接濾鏡一樣,靈活地加入 Bloom、FXAA、色彩校正、景深等效果。

本篇文章將從 概念、安裝、常見 Pass 的使用方式,到 實作細節、常見陷阱與最佳實踐,一步步帶你掌握 EffectComposer,並提供多個完整範例,讓你能在自己的 Three.js 專案中快速上手後製特效。


核心概念

1. EffectComposer 的工作原理

EffectComposer 本質上是一個 渲染目標(RenderTarget)堆疊,它會:

  1. 先將場景渲染到一個離屏的 RenderTarget(稱為 read buffer)。
  2. 依序執行每個 Pass,每個 Pass 會把 read buffer 的內容輸出到 write buffer,然後交換兩個緩衝區(ping‑pong)以供下一個 Pass 使用。
  3. 最後一個 Pass 的結果會 渲染回螢幕(或自訂的 RenderTarget)。

圖示

Scene --> RenderPass --> Composer (ping-pong) --> [Pass A] --> [Pass B] --> ... --> screen

2. 常見的 Pass

Pass 類型 主要功能 常用情境
RenderPass 把原始場景渲染到緩衝區 基礎渲染,所有後製的起點
ShaderPass 使用自訂或內建的 Shader 進行像素級處理 色彩校正、模糊、噪點等
UnrealBloomPass 高品質的發光(Bloom)效果 光源、魔法、科幻 UI
FXAAShaderShaderPass 抗鋸齒(FXAA) 提升低解析度或較舊 GPU 的畫面銳利度
SSAOPass 屏蔽環境光遮蔽(Screen‑Space AO) 增加陰影深度感
OutlinePass 物件輪廓描邊 互動式選取、強調目標

:Three.js 官方範例(examples/jsm/postprocessing/)提供了上述大部分 Pass 的實作,你可以直接引用或自行改寫 Shader。

3. 建立 Composer 的基本步驟

import * as THREE from 'three';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass }    from 'three/examples/jsm/postprocessing/RenderPass.js';

// 1. 建立 renderer、scene、camera(此處省略)
// 2. 建立 Composer
const composer = new EffectComposer(renderer);

// 3. 加入 RenderPass(必須是第一個 Pass)
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);

// 4. 之後可以依序加入其他 Pass

小技巧:如果你想在不同解析度下執行特效,可以在建立 EffectComposer 時傳入自訂的 WebGLRenderTarget,或使用 composer.setSize(width, height) 動態調整。


程式碼範例

以下提供 五個實用範例,每個範例都會說明為什麼要使用該 Pass,以及如何正確配置。

範例 1️⃣ 基本 Bloom + FXAA

import * as THREE from 'three';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass }    from 'three/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
import { ShaderPass }    from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { FXAAShader }    from 'three/examples/jsm/shaders/FXAAShader.js';

// --- 基本設定(scene、camera、renderer)略 ---

// 1. Composer
const composer = new EffectComposer(renderer);

// 2. RenderPass:渲染原始場景
composer.addPass(new RenderPass(scene, camera));

// 3. UnrealBloomPass:發光
const bloomPass = new UnrealBloomPass(
    new THREE.Vector2(window.innerWidth, window.innerHeight),
    1.5,   // strength
    0.4,   // radius
    0.85   // threshold
);
composer.addPass(bloomPass);

// 4. FXAA 抗鋸齒(使用 ShaderPass 包裝)
const fxaaPass = new ShaderPass(FXAAShader);
fxaaPass.material.uniforms['resolution'].value.set(
    1 / window.innerWidth,
    1 / window.innerHeight
);
composer.addPass(fxaaPass);

// 5. 渲染循環
function animate() {
    requestAnimationFrame(animate);
    // 任何動畫或控制器更新...
    composer.render();
}
animate();

說明:Bloom 能讓光源或高光部分「散發光暈」,FXAA 則在最後一步平滑鋸齒,兩者結合在低解析度裝置上效果特別好。


範例 2️⃣ 自訂色彩校正(ColorCorrectionPass)

import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { ColorCorrectionShader } from 'three/examples/jsm/shaders/ColorCorrectionShader.js';

// ColorCorrectionShader 已內建於 three.js
const colorPass = new ShaderPass(ColorCorrectionShader);
colorPass.uniforms['powRGB'].value = new THREE.Vector3(1.2, 1.1, 1.0); // 增亮
colorPass.uniforms['mulRGB'].value = new THREE.Vector3(1.0, 0.95, 0.9); // 調整色溫
composer.addPass(colorPass);

重點ShaderPass 允許你 把任何自訂 GLSL Shader 包裝成 Pass。只要在 uniforms 中設定所需參數,即可即時調整畫面色調。


範例 3️⃣ 景深(Depth of Field)— BokehPass

import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js';

const bokehPass = new BokehPass(scene, camera, {
    focus:      1.0,   // 焦點距離
    aperture:   0.025, // 光圈大小,越大越模糊
    maxblur:    0.01,  // 最大模糊半徑
    width:      window.innerWidth,
    height:     window.innerHeight
});
composer.addPass(bokehPass);

應用:在角色或前景物件上使用景深,能營造出 「鏡頭聚焦」 的電影感。BokehPass 內部會根據深度紋理自動算出模糊程度。


範例 4️⃣ 交互式輪廓描邊(OutlinePass)

import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';

// 假設你已經有一個選取的物件陣列 selectedObjects
const outlinePass = new OutlinePass(
    new THREE.Vector2(window.innerWidth, window.innerHeight),
    scene,
    camera
);
outlinePass.selectedObjects = selectedObjects; // 設定要描邊的物件
outlinePass.edgeStrength = 3.0;
outlinePass.edgeGlow = 0.5;
outlinePass.edgeThickness = 1.0;
outlinePass.visibleEdgeColor.set('#ff0000'); // 紅色輪廓
outlinePass.hiddenEdgeColor.set('#190a05');

composer.addPass(outlinePass);

實務:在建模工具、3D 選單或遊戲 UI 中,即時顯示被選取物件的輪廓,可大幅提升使用者體驗。


範例 5️⃣ 多 Pass 組合:Bloom → 色彩校正 → 影像翻轉(Custom Shader)

// 1. 自訂翻轉 Shader
const FlipShader = {
    uniforms: {
        tDiffuse: { value: null },
        flipY:    { value: true }
    },
    vertexShader: `
        varying vec2 vUv;
        void main() {
            vUv = uv;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
        }
    `,
    fragmentShader: `
        uniform sampler2D tDiffuse;
        uniform bool flipY;
        varying vec2 vUv;
        void main() {
            vec2 uv = vUv;
            if(flipY) uv.y = 1.0 - uv.y;
            gl_FragColor = texture2D(tDiffuse, uv);
        }
    `
};

const flipPass = new ShaderPass(FlipShader);
flipPass.uniforms['flipY'].value = false; // 只在需要時改成 true

// 2. 加入順序:Render → Bloom → ColorCorrection → Flip
composer.addPass(new RenderPass(scene, camera));
composer.addPass(bloomPass);           // 前面的 BloomPass 仍可使用
composer.addPass(colorPass);           // 前面的 ColorCorrectionPass
composer.addPass(flipPass);            // 最後翻轉畫面(例如鏡射效果)

技巧:Pass 的 執行順序 直接決定最終視覺結果。若要在 Bloom 前先做顏色校正,請把 colorPass 放在 bloomPass 前;若想在所有特效完成後再做最終的「畫面翻轉」或「水印」等處理,則放在最末端。


常見陷阱與最佳實踐

陷阱 原因 解決方式
渲染解析度不一致 EffectComposer 預設使用螢幕大小,如果在視窗縮放時未呼叫 composer.setSize(),特效會失真或出現黑邊。 window.resize 事件中 同步更新 renderer、composer、FXAA uniform
composer.setSize(width, height);<br>fxaaPass.material.uniforms['resolution'].value.set(1/width, 1/height);
多 Pass 造成記憶體泄漏 每個 Pass 會產生額外的 RenderTarget,若在動畫或場景切換時不釋放,GPU 記憶體會不斷累積。 在不需要時 呼叫 composer.dispose(),或手動 dispose() 個別 Pass、RenderTarget。
深度緩衝區未啟用 某些 Pass(如 BokehPassSSAOPass)依賴深度貼圖,若在建立 WebGLRenderer 時未設定 depthBuffer:true,會得到全黑或錯誤結果。 建立 renderer 時使用 new THREE.WebGLRenderer({ antialias:true, depth:true }),或在 EffectComposerRenderTarget 加上 depthBuffer:true
Pass 順序錯誤 例如先做 Bloom 再做色彩校正,可能會讓 Bloom 效果被過度抑制。 確定特效的「前置」與「後置」關係,常見順序:
Render → DepthPass (optional) → SSAO → Bloom → ColorCorrection → FXAA → UI
ShaderPass 的 uniform 更新不及時 在動畫中改變 uniform(如時間、顏色)卻忘記在渲染迴圈裡更新,導致畫面不變。 animate()每幀更新shaderPass.uniforms.time.value = clock.getElapsedTime();

最佳實踐小結

  1. 一次只保留必要的 Pass:過多 Pass 會拖慢渲染,尤其在行動裝置。
  2. 使用 WebGLRenderTargettype: THREE.HalfFloatType,在支援的硬體上可減少記憶體占用且保留較高色深。
  3. 把 UI(HTML、CSS)層放在 Canvas 之上,而不是透過 ShaderPass 處理,避免不必要的額外渲染成本。
  4. 利用 composer.readBuffer / composer.writeBuffer 在自訂 Pass 中直接取樣,避免重複 renderer.render()
  5. 在開發階段使用 debug Pass,如 PassDebug(自行實作)顯示每個 Pass 的輸出,快速定位問題。

實際應用場景

場景 可能使用的 Pass 組合 效果說明
線上 3D 產品展示 Render → Bloom → FXAA → ColorCorrection 讓產品表面光澤更突出,同時保持畫面銳利。
WebGL 遊戲(第一人稱射擊) Render → SSAOPass → BokehPass → Bloom → FXAA 增強環境光遮蔽與景深,營造沉浸感;Bloom 為火光、爆炸增添光暈。
互動式資料視覺化 Render → OutlinePass → FXAA 用輪廓高亮選取的資料點,提升可讀性。
AR/VR 作品 Render → SSAOPass → ToneMappingPass → FXAA 在 VR 中需要較低延遲,使用輕量的 SSAO 與色調映射,最後加 FXAA 抗鋸齒。
3D 影片後製 Render → Bloom → ColorCorrection → Custom Film Grain Shader → FXAA 產出電影感的畫面,加入膠片顆粒與柔和的發光。

案例說明:假設你在開發一個「星際探索」的 WebGL 小遊戲,玩家在太空中飛行,場景充滿星光與星雲。你可以使用 UnrealBloomPass 為星星加上柔和光暈,OutlinePass 為被選中的星球描邊,最後再以 FXAAShader 抗鋸齒,確保即使在手機上也不會出現鋸齒感。


總結

EffectComposer 為 Three.js 的 後製特效提供了模組化、可堆疊的管線,讓開發者能夠像使用 Photoshop 濾鏡一樣,靈活組合 Bloom、FXAA、景深、輪廓描邊等效果。掌握以下重點,就能在專案中安全、有效地使用它:

  • 先加 RenderPass,作為所有後製的基礎。
  • 依需求選擇 Pass,注意順序(先深度相關 → 再光暈 → 最後抗鋸齒)。
  • 在視窗變更或解析度調整時,務必同步更新 Composer 與相關 uniform。
  • 記得釋放資源dispose()),避免 GPU 記憶體泄漏。
  • 測試不同裝置,根據效能調整 Pass 數量或使用 HalfFloatType 減少負擔。

透過本文的概念說明與五個實作範例,你已具備在 Three.js 中建立 高品質、可擴充的後製特效管線 的能力。接下來,就把這些技巧運用到你的作品裡,讓 3D 網頁體驗更具視覺衝擊與沉浸感吧! 🚀