关于 threejs-postprocessing
面向 three.js r186 开发者的后期处理参考技能,介绍 WebGL 的 EffectComposer 通道链和 WebGPU 的 TSL 渲染管线,并提供常见图像效果示例。
主要功能
- 01EffectComposer 通道链配置
- 02Bloom、景深与 SSAO
- 03轮廓、颗粒、故障与像素化效果
- 04自定义 ShaderPass 效果
- 05WebGPU TSL 渲染管线
- 06尺寸调整与输出色彩处理
适用场景
- 01为渲染场景添加 Bloom
- 02应用景深或环境光遮蔽
- 03构建自定义图像效果
- 04在 WebGPURenderer 中使用后期处理
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cloudai-x/ threejs-skills --skill threejs-postprocessing安装后在 Claude Code 中输入/threejs-postprocessing确认已加载。
详细介绍
Three.js 后期处理
面向 three.js r186。示例使用 WebGLRenderer;WebGPU 和 TSL 部分使用 WebGPURenderer。
快速开始
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";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 5);
// A bright emissive sphere gives bloom something to pick up
const mesh = new THREE.Mesh(
new THREE.SphereGeometry(1, 32, 16),
new THREE.MeshStandardMaterial({ color: 0x222222, emissive: 0xffffff, emissiveIntensity: 2 }),
);
scene.add(mesh);
// The composer replaces renderer.render(). Render the scene, then effects,
// then OutputPass last: it applies renderer.toneMapping and outputColorSpace once.
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
// bloomPass.strength, .radius and .threshold are live properties; threshold is the luminance above which things glow
composer.addPass(bloomPass);
composer.addPass(new OutputPass());
function animate() {
composer.render(); // not renderer.render()
}
renderer.setAnimationLoop(animate);
通道顺序
合成器的缓冲区存储线性颜色;色调映射和 sRGB 转换仅在 OutputPass 中执行,并使用渲染器的设置。按以下顺序构建处理链:RenderPass → 线性空间效果(泛光、景深、SSAO、轮廓等)→ SMAAPass,放在需要平滑边缘的效果之后 → OutputPass → 仅在使用 FXAA 时添加 FXAAPass(three/addons/postprocessing/FXAAPass.js),因为 FXAA 需要 sRGB 输入。不使用 FXAA 时,OutputPass 是最后一个通道。
SSAOPass 会使上一通道生成的图像变暗,因此必须在它之前添加 RenderPass。RenderPixelatedPass 会自行渲染场景,并取代 RenderPass。设置 pass.enabled = false 可跳过某个通道,无需重建处理链。
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";
import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
composer.addPass(new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85));
composer.addPass(new SMAAPass()); // no arguments; the composer sizes it
composer.addPass(new OutputPass());
合成器设置
- 合成器会在创建时复制渲染器的像素比。请在创建合成器之前调用
renderer.setPixelRatio(),或之后调用composer.setPixelRatio()。 - 合成器的缓冲区不支持多重采样;渲染器上的
antialias: true不会作用于这些缓冲区。若要对场景渲染和合成器缓冲区启用 MSAA,请传入带有samples(CSS 像素尺寸)的自定义缓冲区,并将合成器尺寸设置一次:
import * as THREE from "three";
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
const composer = new EffectComposer(
renderer,
new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
type: THREE.HalfFloatType,
samples: 4, // MSAA on the composer's two buffers
}),
);
composer.setSize(window.innerWidth, window.innerHeight); // scales the buffer by the pixel ratio
- 调整大小时,调用
camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); composer.setSize(w, h);——合成器会调整其缓冲区以及每个通道的尺寸,包括像素比。切勿手动调整通道尺寸。 composer.render()使用自身的内部计时器驱动基于时间的通道(FilmPass);仅在自行管理时间时才传入以秒为单位的时间增量。- 每一帧中,合成器都会将
renderToScreen设为最后一个已启用通道。只有在需要将结果保留在屏幕外时才设置composer.renderToScreen = false,如下方的选择性泛光示例所示。
选择性泛光
仅让标记了图层的网格产生泛光:将其他所有内容渲染为黑色,创建一个在屏幕外渲染的仅泛光合成器,然后将其纹理混合到一个渲染干净场景的最终合成器中。scene.background 不是网格:在调用 bloomComposer.render() 时将其设为 null,否则它会再次叠加到最终图像中;调整大小时,请对两个合成器都调用 setSize。
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";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
const BLOOM_LAYER = 1;
const bloomLayer = new THREE.Layers();
bloomLayer.set(BLOOM_LAYER); // test mask: matches any object enabled on this layer
mesh.layers.enable(BLOOM_LAYER); // add the layer; set() would remove the mesh from layer 0
// Paint non-bloomed meshes black, then restore them
const darkMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
const materials = new Map();
function darkenNonBloomed(object) {
if (object.isMesh && bloomLayer.test(object.layers) === false) {
materials.set(object, object.material);
object.material = darkMaterial;
}
}
function restoreMaterial(object) {
if (materials.has(object)) {
object.material = materials.get(object);
materials.delete(object);
}
}
// Bloom-only composer; with renderToScreen off, its result stays in renderTarget2
const bloomComposer = new EffectComposer(renderer);
bloomComposer.renderToScreen = false;
bloomComposer.addPass(new RenderPass(scene, camera));
bloomComposer.addPass(new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85));
// Final composer: the clean scene plus the bloom texture, mixed by a ShaderPass
const finalComposer = new EffectComposer(renderer);
finalComposer.addPass(new RenderPass(scene, camera));
finalComposer.addPass(
new ShaderPass(
new THREE.ShaderMaterial({
uniforms: {
baseTexture: { value: null }, // filled from the previous pass
bloomTexture: { value: bloomComposer.renderTarget2.texture },
},
vertexShader: /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: /* glsl */ `
uniform sampler2D baseTexture;
uniform sampler2D bloomTexture;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D(baseTexture, vUv) + texture2D(bloomTexture, vUv);
}
`,
}),
"baseTexture",
),
);
finalComposer.addPass(new OutputPass());
function animate() {
const background = scene.background;
scene.background = null; // not a mesh: it would bloom and be added twice
scene.traverse(darkenNonBloomed); // black scene: only marked meshes bloom
bloomComposer.render();
scene.traverse(restoreMaterial);
scene.background = background;
finalComposer.render(); // clean scene + bloom to the screen
}
renderer.setAnimationLoop(animate);
景深
import { BokehPass } from "three/addons/postprocessing/BokehPass.js";
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera)); // BokehPass reads this color image
const bokehPass = new BokehPass(scene, camera, {
focus: 10.0, // view-space depth that stays sharp, in world units
aperture: 0.025, // how far out of focus things blur
maxblur: 0.01, // cap on the blur radius
});
composer.addPass(bokehPass);
composer.addPass(new OutputPass());
// Refocus at runtime: distanceTo matches the view-space depth for objects near the view center
bokehPass.uniforms.focus.value = camera.position.distanceTo(mesh.position);
环境光遮蔽
import { SSAOPass } from "three/addons/postprocessing/SSAOPass.js";
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
const composer = new EffectComposer(renderer);
// SSAOPass darkens the previous image, so the scene render must come first
composer.addPass(new RenderPass(scene, camera));
const ssaoPass = new SSAOPass(scene, camera, window.innerWidth, window.innerHeight);
ssaoPass.kernelRadius = 16;
ssaoPass.minDistance = 0.005;
ssaoPass.maxDistance = 0.1;
composer.addPass(ssaoPass);
composer.addPass(new OutputPass());
// Debug views: ssaoPass.output = SSAOPass.OUTPUT.SSAO (or Blur, Depth, Normal); the default shows the shaded scene
轮廓
import * as THREE from "three";
import { OutlinePass } from "three/addons/postprocessing/OutlinePass.js";
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera);
outlinePass.selectedObjects = [mesh]; // only these are outlined
outlinePass.edgeStrength = 3;
outlinePass.visibleEdgeColor.set(0xffffff);
outlinePass.hiddenEdgeColor.set(0x190a05); // parts hidden behind other objects
composer.addPass(outlinePass);
composer.addPass(new OutputPass());
更多通道
胶片颗粒、故障、半色调和暗角效果可直接作为处理链中间的滤镜使用。ColorCorrectionShader 的用法类似暗角:使用 ShaderPass,并设置 powRGB / mulRGB / addRGB uniform 向量。其默认 powRGB 为 (2, 2, 2),会使图像变暗;若要保持中性,请从 (1, 1, 1) 开始。
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
import { FilmPass } from "three/addons/postprocessing/FilmPass.js";
import { GlitchPass } from "three/addons/postprocessing/GlitchPass.js";
import { HalftonePass } from "three/addons/postprocessing/HalftonePass.js";
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
import { VignetteShader } from "three/addons/shaders/VignetteShader.js";
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const filmPass = new FilmPass(0.35, false); // grain intensity, grayscale; animates from the composer's delta
composer.addPass(filmPass);
const glitchPass = new GlitchPass();
glitchPass.goWild = true; // glitch every frame; false (the default) glitches at random intervals
composer.addPass(glitchPass);
const halftonePass = new HalftonePass({ shape: 1, radius: 4, scatter: 0, greyscale: false }); // shape: 1 dot, 2 ellipse, 3 line, 4 square, 5 diamond
composer.addPass(halftonePass);
const vignettePass = new ShaderPass(VignetteShader); // uniforms: offset and darkness
vignettePass.uniforms.offset.value = 1.0;
vignettePass.uniforms.darkness.value = 1.0;
composer.addPass(vignettePass);
composer.addPass(new OutputPass());
RenderPixelatedPass 会自行渲染场景,因此它会取代 RenderPass,而不是接在其后:
import { RenderPixelatedPass } from "three/addons/postprocessing/RenderPixelatedPass.js";
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
const composer = new EffectComposer(renderer);
const pixelatedPass = new RenderPixelatedPass(6, scene, camera); // 6 = pixel size
pixelatedPass.normalEdgeStrength = 0.3; // edges where the surface normal changes (creases)
pixelatedPass.depthEdgeStrength = 0.4; // depth-discontinuity edges
composer.addPass(pixelatedPass);
composer.addPass(new OutputPass());
自定义通道
将任何类似 ShaderMaterial 的着色器对象包装在 ShaderPass 中。tDiffuse uniform 接收上一通道的输出。在自定义效果处理线性空间时,将其放在 OutputPass 之前;仅当效果需要 sRGB 输入时(例如 FXAA),才将其放在 OutputPass 之后。
import * as THREE from "three";
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
const WaveShader = {
uniforms: {
tDiffuse: { value: null },
time: { value: 0 },
intensity: { value: 1.0 },
},
vertexShader: /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: /* glsl */ `
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; // wave distortion
gl_FragColor = texture2D(tDiffuse, uv);
}
`,
};
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const wavePass = new ShaderPass(WaveShader);
composer.addPass(wavePass);
composer.addPass(new OutputPass());
// Drive time uniforms from a Timer
const timer = new THREE.Timer();
timer.connect(document); // keeps deltas sane in inactive tabs
function animate() {
timer.update();
wavePass.uniforms.time.value = timer.getElapsed();
composer.render();
}
renderer.setAnimationLoop(animate);
渲染器内置效果(无需合成器)
renderer.setEffects() 会在普通的 renderer.render() 之后,对内部 HDR 缓冲区运行常规通道——无需合成器,并会在效果之后自动应用色调映射和输出色彩空间。它要求设置 outputBufferType: THREE.HalfFloatType(或 FloatType),不接受 OutputPass 或 FXAAPass,通过 antialias: true 实现抗锯齿,并随 renderer.setSize() 调整大小。此处效果不会接收时间增量:FilmPass 的时间 uniform 会变成 NaN,胶片颗粒也会丢失,因此请通过 EffectComposer 运行依赖时间的通道。
import * as THREE from "three";
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
const renderer = new THREE.WebGLRenderer({ antialias: true, outputBufferType: THREE.HalfFloatType });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
renderer.setEffects([bloomPass]);
function animate() {
renderer.render(scene, camera); // bloom runs on the internal HDR buffers after the scene render
}
renderer.setAnimationLoop(animate);
WebGPU 和 TSL
在 WebGPURenderer 下,后期处理是在 RenderPipeline 上构建 TSL 节点图,而不是使用通道链:用 pass() 渲染场景,从 three/addons/tsl/display/*.js 构建效果,并将最终节点赋给 renderPipeline.outputNode。pass() 使用渲染器的 MSAA 样本数,因此这里可以使用 antialias: true,但 ao() 预通道除外,它必须使用 pass(scene, camera, { samples: 0 });traa 则需要一个未启用 antialias 的渲染器。pass() 也会跟随 renderer.setSize(),因此管线本身不需要单独调用调整尺寸的方法。
import * as THREE from "three/webgpu";
import { pass } from "three/tsl";
import { bloom } from "three/addons/tsl/display/BloomNode.js";
const renderer = new THREE.WebGPURenderer({ antialias: true });
await renderer.init(); // before any other use of the renderer
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111111); // the WebGPU canvas is transparent by default
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 5);
scene.add(new THREE.Mesh(new THREE.SphereGeometry(1, 32, 16), new THREE.MeshBasicNodeMaterial({ color: 0xffffff })));
const renderPipeline = new THREE.RenderPipeline(renderer);
const scenePass = pass(scene, camera);
const scenePassColor = scenePass.getTextureNode("output");
// bloom() returns only the glow, so add it to the scene color
renderPipeline.outputNode = scenePassColor.add(bloom(scenePassColor, 1.5, 0.4, 0.85));
function animate() {
renderPipeline.render(); // instead of renderer.render()
}
renderer.setAnimationLoop(animate);
滤镜会返回处理完成的图像并替换输出节点:dof、film、rgbShift、chromaticAberration、afterImage、dotScreen、lut3D 和 smaa(线性输入)接收场景颜色节点;pixelationPass(scene, camera, 6) 会自行渲染场景;traa 需要速度 MRT 和一个未启用 antialias 的渲染器。与 bloom 类似,outline(scene, camera, { selectedObjects }) 只返回边缘遮罩(.visibleEdge、.hiddenEdge),你需要为其着色并将其加到场景颜色上;ssao(depthNode, normalNode, camera) 只在 .r 中返回遮挡因子,该因子用于与场景颜色相乘。
import * as THREE from "three/webgpu";
import { pass, renderOutput } from "three/tsl";
import { dof } from "three/addons/tsl/display/DepthOfFieldNode.js";
import { fxaa } from "three/addons/tsl/display/FXAANode.js";
const renderPipeline = new THREE.RenderPipeline(renderer);
const scenePass = pass(scene, camera);
const scenePassColor = scenePass.getTextureNode("output");
// A filter replaces the image: node, viewZ, focus distance, focal length, bokeh scale
const dofNode = dof(scenePassColor, scenePass.getViewZNode(), 4.5, 1.2, 2.5);
// FXAA runs in sRGB: disable the automatic output transform, then convert the DOF result before FXAA
renderPipeline.outputColorTransform = false;
renderPipeline.outputNode = fxaa(renderOutput(dofNode));
// After the first frame, a changed outputNode or outputColorTransform only takes effect with:
renderPipeline.needsUpdate = true;
ao() 需要从单采样预通道获取深度和视图空间法线,其 AO 值位于红色通道中:
import * as THREE from "three/webgpu";
import { pass, mrt, normalView, screenUV, builtinAOContext } from "three/tsl";
import { ao } from "three/addons/tsl/display/GTAONode.js";
const renderPipeline = new THREE.RenderPipeline(renderer);
// Pre-pass with view-space normals in the output MRT attachment
const prePass = pass(scene, camera, { samples: 0 }); // ao() cannot read a multisampled depth texture
prePass.setMRT(mrt({ output: normalView }));
const aoPass = ao(prePass.getTextureNode("depth"), prePass.getTextureNode(), camera);
// Apply the AO to a normal scene pass
const scenePass = pass(scene, camera);
scenePass.contextNode = builtinAOContext(aoPass.getTextureNode().sample(screenUV).r);
renderPipeline.outputNode = scenePass;
自定义效果是直接对场景纹理进行处理的 TSL,而不是 GLSL ShaderPass;这是 Custom Passes 中的波浪效果,由 time 节点驱动动画:
import * as THREE from "three/webgpu";
import { pass, screenUV, sin, time, vec2 } from "three/tsl";
const renderPipeline = new THREE.RenderPipeline(renderer);
const scenePassColor = pass(scene, camera).getTextureNode("output");
const offset = vec2(sin(screenUV.y.mul(10).add(time)).mul(0.01), 0);
renderPipeline.outputNode = scenePassColor.sample(screenUV.add(offset));
这里的选择性泛光不需要图层技巧:将 emissive(来自 three/tsl)添加到通道 MRT 中,使用 scenePass.setMRT(mrt({ output, emissive })),并仅对该附件应用泛光:renderPipeline.outputNode = scenePassColor.add(bloom(scenePass.getTextureNode("emissive")))。
常见错误
three/examples/jsm/postprocessing/EffectComposer.js→three/addons/postprocessing/EffectComposer.js:examples/jsm 说明符无法通过导入映射解析。composer.addPass(new ShaderPass(GammaCorrectionShader))→composer.addPass(new OutputPass()):GammaCorrectionShader 仅应用 sRGB 传递函数,而 OutputPass 会应用渲染器的色调映射、曝光和输出色彩空间,并结束整个处理链;只有需要 sRGB 输入的通道(例如 FXAAPass)才应放在它之后。composer.addPass(bloomPass); composer.render();→composer.addPass(bloomPass); composer.addPass(new OutputPass()); composer.render();:composer 的缓冲区是线性的;没有 OutputPass 时,色调映射和 sRGB 转换会被跳过或应用不一致。composer.addPass(fxaaPass); composer.addPass(outputPass);→composer.addPass(outputPass); composer.addPass(fxaaPass);:FXAA 需要 sRGB 输入,因此应放在 OutputPass 之后;SMAA 在 线性空间中工作,必须放在它之前。new ShaderPass(FXAAShader)→new FXAAPass():FXAAPass 会根据 composer 的尺寸设置自己的分辨率 uniform(包括像素比);手动设置的值在 HiDPI 屏幕上不正确。new FilmPass(0.35, 0.5, 648, false)→new FilmPass(0.35, false):签名为(intensity, grayscale);扫描线参数并不存在。new HalftonePass(width, height, params)→new HalftonePass(params):r175 中移除了 width 和 height 参数;选项放在单个 params 对象中。new SMAAPass(width, height)→new SMAAPass():r175 中移除了尺寸参数;composer 会调整此通道的尺寸。new UnrealBloomPass(new THREE.Vector2(window.innerWidth / 2, window.innerHeight / 2), 1.5, 0.4, 0.85)→new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85):addPass 会立即将此通道调整为 composer 的尺寸,从而抵消半分辨率技巧。bloomPass.resolution.set(width, height)→composer.setSize(width, height):UnrealBloomPass 仅在构造函数中读取其分辨率;composer 会调整此通道及其 mip 的尺寸。bloomPass.renderToScreen = false→bloomComposer.renderToScreen = false:composer 会在每帧重新分配每个启用通道的 renderToScreen,因此只有 composer 级别的开关才能让结果不显示在屏幕上。requestAnimationFrame(animate)→renderer.setAnimationLoop(animate):两个渲染器的文档都建议使用它,而不是手动循环;启动 WebXR 会话时,它还会将帧交给该会话。clock.getElapsedTime()→timer.getElapsed():Clock 在 r183 中已弃用;创建 THREE.Timer,调用一次 timer.connect(document),并在每帧读取前调用 timer.update()。composer.render(); renderer.render(scene, camera);→bloomComposer.render(); finalComposer.render();:第二次 renderer.render 会清除画布并擦除 composer 的输出;选择性泛光会通过第二个 composer 混合泛光纹理。const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setEffects([bloomPass]);→const renderer = new THREE.WebGLRenderer({ antialias: true, outputBufferType: THREE.HalfFloatType }); renderer.setEffects([bloomPass]);:setEffects() 要求输出缓冲区类型为 HalfFloatType 或 FloatType;使用默认值时会记录错误且不应用任何效果。renderer.setEffects([bloomPass, new OutputPass(), new FXAAPass()])→renderer.setEffects([bloomPass]):内置路径会在效果处理完成后应用一次色调映射和色彩空间转换,因此列表中的 OutputPass 会导致重复应用(并发出警告),而没有它的 FXAAPass 会接收线性输入;应改用antialias: true进行抗锯齿。import { bloom } from "three/tsl"→import { bloom } from "three/addons/tsl/display/BloomNode.js":three/tsl 导出 pass、mrt 和 renderOutput;效果节点位于 addons 中。new THREE.PostProcessing(renderer)→new THREE.RenderPipeline(renderer):在 r183 中从 PostProcessing 更名而来;旧名称仅作为已弃用的别名保留,并会发出警告。renderPipeline.outputNode = bloom(scenePassColor)→renderPipeline.outputNode = scenePassColor.add(bloom(scenePassColor)):bloom() 仅返回辉光,因此单独使用时不会绘制场景。renderPipeline.outputNode = fxaa(scenePassColor)→renderPipeline.outputColorTransform = false; renderPipeline.outputNode = fxaa(renderOutput(scenePass));:FXAA 需要 sRGB 输入,但默认的 outputColorTransform 会在它之后进行色调映射和转换。renderPipeline.outputNode = scenePassColor.add(dof(scenePassColor, scenePass.getViewZNode()))→renderPipeline.outputNode = dof(scenePassColor, scenePass.getViewZNode()):dof 会返回处理完成的图像,因此再加上场景颜色会导致场景绘制两次。await renderPipeline.renderAsync()→renderPipeline.render():renderAsync 在 r181 中已弃用;在初始化阶段调用一次await renderer.init(),然后同步渲染。aoPass.distanceFallOff.value = 2→aoPass.radius.value = 0.2:distanceExponent 和 distanceFallOff 在 r186 中已弃用,并且不再影响 AO,因此应改为调整 radius 和 scale。new EffectComposer(webgpuRenderer)→new THREE.RenderPipeline(webgpuRenderer):EffectComposer 及其通道仅适用于 WebGLRenderer;WebGPURenderer 的效果是 RenderPipeline 上的 TSL 节点。
另请参阅
threejs-fundamentals— 创建渲染器、设置尺寸、配置色彩空间和动画循环。threejs-shaders— 编写 GLSL ShaderMaterial,以及使用 TSL 节点语言实现自定义效果。threejs-textures— 渲染目标,以及将后期处理后的图像用作纹理。