跳到主要内容
3D Playground
Three.js 核心第 10 / 13 节

后期处理

高级WebGL

先渲染到纹理,再加工

普通渲染直接把场景画到屏幕上。后期处理则先把场景画进一张离屏纹理(render target),然后用一系列全屏“滤镜”逐步加工,最后才输出到画布。辉光、景深、色彩校正、描边、抗锯齿都是这样实现的。

EffectComposer:一条通道链

EffectComposer 管理一串 Pass,每个 Pass 读取上一个的结果、写入下一张纹理(内部用两张纹理来回“乒乓”):

  1. RenderPass(scene, camera):把场景正常渲染进纹理,是链条的起点。
  2. UnrealBloomPass(resolution, strength, radius, threshold):提取比 threshold 更亮的部分,多级模糊后叠加回去,产生辉光。
  3. ShaderPass(shader):运行你自己的片元着色器。
  4. OutputPass():做色调映射(tone mapping)和 sRGB 色彩空间转换,应当放在最后。

使用 composer 后,循环里用 composer.render() 代替 renderer.render(scene, camera)。尺寸变化时也要调用 composer.setSize() 和 composer.setPixelRatio(),因为它持有自己的渲染目标。

让 bloom 好看的关键

Bloom 只挑“足够亮”的像素。场景中的霓虹环用 emissive + 较高的 emissiveIntensity,萤火虫的颜色乘以 4,都让它们的亮度超过 1.0(HDR)。而深色的金属核心不会发光。调整 threshold 就能决定谁发光。

写一个自定义 ShaderPass

一个 shader 对象包含 uniforms、vertexShader 和 fragmentShader。tDiffuse 是约定俗成的名字,composer 会自动把上一个 Pass 的结果填进去:

glsl
vec2 dir = vUv - 0.5;
float r = texture2D(tDiffuse, vUv + dir * uAberration * 2.0).r;
float b = texture2D(tDiffuse, vUv - dir * uAberration * 2.0).b;
col *= 1.0 - uVignette * dot(dir, dir) * 1.6;

示例的 LensShader 做了三件事:色差(R、B 通道错位采样)、暗角(按到中心的距离压暗)和胶片颗粒(随时间变化的哈希噪声)。uniform 可以直接绑定到 GUI 上实时调整。

代价

每个 Pass 都是一次全屏绘制,分辨率越高越贵。Bloom 内部有多级模糊,是比较重的效果。移动端要谨慎叠加。

关闭 enable composer,对比原始画面和后期处理后的画面。

核心 API

EffectComposerRenderPassUnrealBloomPassShaderPassOutputPasscomposer.setSizerenderer.toneMappingmaterial.emissiveIntensity

动手试试

  1. 1把 bloom 的 threshold 调到 0,让所有东西都发光,再慢慢调高找到合适的值。
  2. 2在 LensShader 里加一条扫描线:col *= 0.9 + 0.1 * sin(vUv.y * 800.0)。
  3. 3把 OutputPass 挪到 ShaderPass 前面,观察颗粒和暗角的颜色有什么不同,想想为什么。
  4. 4从 three/addons/postprocessing/ 再引入一个 Pass(例如 AfterimagePass),加到链条里。
运行中