mcpField
T
threejs-postprocessing

threejs-postprocessing

github.com/cloudai-x创意与媒体3.8k14.4k 次安装1 周前更新
代码仓库

关于 threejs-postprocessing

面向 three.js r186 开发者的后期处理参考技能,介绍 WebGL 的 EffectComposer 通道链和 WebGPU 的 TSL 渲染管线,并提供常见图像效果示例。

主要功能

  1. 01EffectComposer 通道链配置
  2. 02Bloom、景深与 SSAO
  3. 03轮廓、颗粒、故障与像素化效果
  4. 04自定义 ShaderPass 效果
  5. 05WebGPU TSL 渲染管线
  6. 06尺寸调整与输出色彩处理

适用场景

  1. 01为渲染场景添加 Bloom
  2. 02应用景深或环境光遮蔽
  3. 03构建自定义图像效果
  4. 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 — 渲染目标,以及将后期处理后的图像用作纹理。

评论

支持 Markdown