【Three.js后期处理】如何让你的场景拥有电影级调色
同样是三个盒子,加了后期,就像好莱坞大片
前言
去年我做了一个项目,给一家科技公司做产品展示。模型是人家设计师精雕细琢的,灯光我也调了半天,自认为效果还不错。结果拿给客户一看,对方皱了皱眉:
“嗯……怎么说呢,看着有点……素?”
我盯着屏幕看了半天,确实,模型是模型,场景是场景,但就是缺点什么。说不上来,就像一碗牛肉面没放盐,啥都有,但不香。
这是当时我提交的“素颜”版本(示例):
平平无奇,对吧?模型该有的都有,灯光该打的都打了,但就是没有“电影感”。
后来我看了一个国外大神的Three.js demo,同样的模型,同样的材质,但人家的画面就是有质感、有氛围,像电影截图一样。
这是加了后期处理之后的效果:
同一個模型,同一个角度,但整个气质都不一样了。我研究了半天,发现奥秘就两个字:后期。
从那以后,我就在后期处理的坑里越陷越深。今天就把我折腾出来的经验分享给大伙儿,让你也能给你的Three.js场景加上电影级调色。
一、后期处理是啥?
后期处理(Post-processing)就是在渲染完3D场景之后,在屏幕上显示之前,再对渲染好的图像做一通“加工”。就像拍完照片之后用Photoshop调色、加滤镜。
Three.js里后期处理的核心是 EffectComposer。它就像一个流水线,你可以往上面挂各种处理工序(Pass),画面会按顺序经过这些工序,最后输出到屏幕。
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 在动画循环里用 composer.render() 替换原来的 renderer.render()
就这么简单,现在画面会先经过 renderPass(就是把场景正常渲染一遍),然后再输出。当然,只加 RenderPass 没任何效果,我们还得加别的Pass。
二、第一个魔法:泛光(Bloom)
泛光就是让画面中亮的部分“溢”出来,有种发光的感觉。电影里经常用,尤其是阳光透过窗户、霓虹灯、发光物体,加了泛光立刻有氛围。
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
import { Vector2 } from 'three';
const bloomPass = new UnrealBloomPass(new Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
// 参数:分辨率、强度、半径、阈值
bloomPass.threshold = 0.1; // 亮度超过多少的开始泛光
bloomPass.strength = 1.2; // 泛光强度
bloomPass.radius = 0.5; // 泛光半径
composer.addPass(bloomPass);
我第一次加上泛光的时候,整个场景瞬间“活了”。
泛光效果对比:
前
后
那个科技产品的边缘微微发光,像电影里的全息投影。我激动地发给同事看,同事说:“哟,开美颜了?”
三、颜色校正:让画面有“电影感”
泛光只是开胃菜,真正的调色大餐是颜色校正。Three.js 里可以用 ShaderPass 加上自定义着色器,也可以用现成的 ColorCorrectionPass。
1. 基础颜色校正
调整亮度、对比度、饱和度、色相。
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { ColorCorrectionShader } from 'three/examples/jsm/shaders/ColorCorrectionShader.js';
const colorCorrectionPass = new ShaderPass(ColorCorrectionShader);
colorCorrectionPass.uniforms['powRGB'].value.set(1.1, 1.0, 0.9); // RGB通道的幂次调整
colorCorrectionPass.uniforms['mulRGB'].value.set(1.0, 1.0, 1.0); // RGB乘数
composer.addPass(colorCorrectionPass);
这样能简单调色,但离“电影级”还差得远。
基础调色前后对比:
前
后
四、其他氛围Pass
除了调色,再加几个小点缀,氛围感直接拉满:
1. 胶片颗粒(Film Grain)
给画面加一点噪点,模仿胶片质感。
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass.js';
const filmPass = new FilmPass(0.35, 0.5, 2048, false);
filmPass.renderToScreen = true; // 确保它是最后一个Pass
composer.addPass(filmPass);
胶片颗粒效果:
无颗粒
有胶片颗粒
2. 渐晕(Vignette)
画面四周变暗,聚焦中心。
import { VignetteShader } from 'three/examples/jsm/shaders/VignetteShader.js';
const vignettePass = new ShaderPass(VignetteShader);
composer.addPass(vignettePass);
3. 景深(DOF)
模拟大光圈镜头,背景虚化。这个比较耗性能,慎用。
import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js';
const bokehPass = new BokehPass(scene, camera, {
focus: 10, // 焦点距离
aperture: 0.025, // 光圈大小
maxblur: 1.0
});
composer.addPass(bokehPass);
景深效果对比:
无景深
有景深-背景虚化
五、完整流程示例
来一个完整的例子,把上面这些串起来:
// 初始化 composer
const composer = new EffectComposer(renderer);
// 1. 基础渲染
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 2. 泛光
const bloomPass = new UnrealBloomPass(new Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
bloomPass.threshold = 0.2;
bloomPass.strength = 1.0;
bloomPass.radius = 0.5;
composer.addPass(bloomPass);
// 3. LUT调色
const lutPass = new LUTPass({
lut: textureLoader.load('cine-lut.png'),
intensity: 0.9
});
composer.addPass(lutPass);
// 4. 胶片颗粒
const filmPass = new FilmPass(0.25, 0.5, 2048, false);
composer.addPass(filmPass);
// 5. 渐晕
const vignettePass = new ShaderPass(VignetteShader);
composer.addPass(vignettePass);
// 动画循环
function animate() {
requestAnimationFrame(animate);
composer.render();
}
最终效果对比:
原始画面-无任何后期
最终效果-泛光+颗粒+渐晕
注意Pass的顺序很重要:先泛光,再调色,最后加颗粒和渐晕。顺序不同效果也不同,可以自己试试。
七、坑与优化
1. 性能开销
后期处理很耗性能。每加一个Pass,就多一次全屏渲染。移动端慎用,或者只在需要时开启。
性能对比:
| 后期效果 | 帧率(移动端) | 帧率(桌面端) |
|---|---|---|
| 无后期 | 60 | 60 |
| 单个Bloom | 45 | 60 |
| Bloom + LUT | 40 | 55 |
| Bloom + LUT + 颗粒 + 渐晕 | 30 | 50 |
优化技巧:
- 降低内部渲染分辨率:
composer.setSize(width/2, height/2)然后全屏拉伸,画质略降但性能翻倍。 - 对不需要每帧更新的Pass,可以用
composer.renderToScreen = true跳过后续Pass。
2. 最后一个Pass要 renderToScreen
只有最后一个Pass需要设置 renderToScreen = true,否则画面可能不显示。或者你也可以在 composer.render() 里指定。
3. LUT图路径
LUT图最好用LinearFilter,不要生成mipmap,避免颜色偏移。
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
4. 兼容性
某些旧设备不支持浮点纹理,可能导致泛光失效。可以降级处理。
写在最后
那次给科技公司展示完加了后期的版本,客户眼睛都亮了:“这才对嘛!有科技感!”
其实模型还是那个模型,灯光还是那个灯光,只是加了几道“滤镜”,整个气质就变了。
后期处理就像给素颜的照片化妆,化得好是锦上添花,化不好就……咳咳,大家多试试吧。
最后提醒一句:后期虽好,可不要贪杯哦。毕竟性能是第一位的,别为了好看把用户电脑卡崩溃了。
互动
你用过哪些酷炫的后期效果?有没有什么压箱底的Pass推荐?评论区分享出来,让我抄抄作业 😏
下篇预告:【Three.js与WebGPU】下一代3D技术到底强在哪?