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();
}
效能提示
- 限制 pass 數量:每個 pass 都會進行一次全螢幕渲染
- 降低解析度:對模糊 pass 使用較小的渲染目標
- 關閉未使用的效果:切換 pass 的啟用狀態
- 使用 FXAA 而非 MSAA:較便宜的反鋸齒方式
- 使用開發者工具分析:檢查 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- 渲染器設定






