先渲染到纹理,再加工
普通渲染直接把场景画到屏幕上。后期处理则先把场景画进一张离屏纹理(render target),然后用一系列全屏“滤镜”逐步加工,最后才输出到画布。辉光、景深、色彩校正、描边、抗锯齿都是这样实现的。
EffectComposer:一条通道链
EffectComposer 管理一串 Pass,每个 Pass 读取上一个的结果、写入下一张纹理(内部用两张纹理来回“乒乓”):
RenderPass(scene, camera):把场景正常渲染进纹理,是链条的起点。UnrealBloomPass(resolution, strength, radius, threshold):提取比threshold更亮的部分,多级模糊后叠加回去,产生辉光。ShaderPass(shader):运行你自己的片元着色器。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把 bloom 的 threshold 调到 0,让所有东西都发光,再慢慢调高找到合适的值。
- 2在 LensShader 里加一条扫描线:col *= 0.9 + 0.1 * sin(vUv.y * 800.0)。
- 3把 OutputPass 挪到 ShaderPass 前面,观察颗粒和暗角的颜色有什么不同,想想为什么。
- 4从 three/addons/postprocessing/ 再引入一个 Pass(例如 AfterimagePass),加到链条里。
还没有输出。console.log 的内容会显示在这里。
运行中