Three.js 後製特效(Post‑Processing) – EffectComposer 完全指南
簡介
在 3D 網頁應用中,渲染畫面本身往往只能提供基本的光影與材質,要讓作品更具視覺衝擊力,常需要在渲染結果上再加上一層或多層的後製特效(post‑processing)。Three.js 官方提供的 EffectComposer 正是為了這個目的而設計的,它把 渲染管線抽象成可插拔的 Pass(通道),讓開發者可以像拼接濾鏡一樣,靈活地加入 Bloom、FXAA、色彩校正、景深等效果。
本篇文章將從 概念、安裝、常見 Pass 的使用方式,到 實作細節、常見陷阱與最佳實踐,一步步帶你掌握 EffectComposer,並提供多個完整範例,讓你能在自己的 Three.js 專案中快速上手後製特效。
核心概念
1. EffectComposer 的工作原理
EffectComposer 本質上是一個 渲染目標(RenderTarget)堆疊,它會:
- 先將場景渲染到一個離屏的 RenderTarget(稱為 read buffer)。
- 依序執行每個 Pass,每個 Pass 會把 read buffer 的內容輸出到 write buffer,然後交換兩個緩衝區(ping‑pong)以供下一個 Pass 使用。
- 最後一個 Pass 的結果會 渲染回螢幕(或自訂的 RenderTarget)。
圖示
Scene --> RenderPass --> Composer (ping-pong) --> [Pass A] --> [Pass B] --> ... --> screen
2. 常見的 Pass
| Pass 類型 | 主要功能 | 常用情境 |
|---|---|---|
RenderPass |
把原始場景渲染到緩衝區 | 基礎渲染,所有後製的起點 |
ShaderPass |
使用自訂或內建的 Shader 進行像素級處理 | 色彩校正、模糊、噪點等 |
UnrealBloomPass |
高品質的發光(Bloom)效果 | 光源、魔法、科幻 UI |
FXAAShader(ShaderPass) |
抗鋸齒(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(如 BokehPass、SSAOPass)依賴深度貼圖,若在建立 WebGLRenderer 時未設定 depthBuffer:true,會得到全黑或錯誤結果。 |
建立 renderer 時使用 new THREE.WebGLRenderer({ antialias:true, depth:true }),或在 EffectComposer 的 RenderTarget 加上 depthBuffer:true。 |
| Pass 順序錯誤 | 例如先做 Bloom 再做色彩校正,可能會讓 Bloom 效果被過度抑制。 | 先 確定特效的「前置」與「後置」關係,常見順序:Render → DepthPass (optional) → SSAO → Bloom → ColorCorrection → FXAA → UI |
| ShaderPass 的 uniform 更新不及時 | 在動畫中改變 uniform(如時間、顏色)卻忘記在渲染迴圈裡更新,導致畫面不變。 | 在 animate() 中 每幀更新 如 shaderPass.uniforms.time.value = clock.getElapsedTime(); |
最佳實踐小結
- 一次只保留必要的 Pass:過多 Pass 會拖慢渲染,尤其在行動裝置。
- 使用
WebGLRenderTarget的type: THREE.HalfFloatType,在支援的硬體上可減少記憶體占用且保留較高色深。 - 把 UI(HTML、CSS)層放在 Canvas 之上,而不是透過
ShaderPass處理,避免不必要的額外渲染成本。 - 利用
composer.readBuffer/composer.writeBuffer在自訂 Pass 中直接取樣,避免重複renderer.render()。 - 在開發階段使用
debugPass,如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 網頁體驗更具視覺衝擊與沉浸感吧! 🚀