前言
同样是三个盒子,加了后期,就像好莱坞大片
去年我做了一个项目,给一个科技公司做产品展示。模型是人家设计师精雕细琢的,灯光我也调了半天,自认为效果还不错。结果拿给客户一看,对方皱了皱眉:
"嗯......怎么说呢,看着有点......素?"
我盯着屏幕看了半天,确实,模型是模型,场景是场景,但就是缺点什么。说不上来,就像一碗牛肉面没放盐,啥都有,但不香。
后来我看了一个国外大神的Three.js demo,同样的模型,同样的材质,但人家的画面就是有质感、有氛围,像电影截图一样。我研究了半天,发现奥秘就两个字:后期。
从那以后,我就在后期处理的坑里越陷越深。今天就把我折腾出来的经验分享给大伙儿,让你也能给你的Three.js场景加上电影级调色。
一、后期处理是啥?
后期处理(Post-processing)就是在渲染完3D场景之后,在屏幕上显示之前,再对渲染好的图像做一通"加工"。就像拍完照片之后用Photoshop调色、加滤镜。
Three.js里后期处理的核心是 EffectComposer。它就像一个流水线,你可以往上面挂各种处理工序(Pass),画面会按顺序经过这些工序,最后输出到屏幕。
javascript
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)
泛光就是让画面中亮的部分"溢"出来,有种发光的感觉。电影里经常用,尤其是阳光透过窗户、霓虹灯、发光物体,加了泛光立刻有氛围。
javascript
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. 基础颜色校正
调整亮度、对比度、饱和度、色相。
javascript
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);
这样能简单调色,但离"电影级"还差得远。
2. 曲线调色(更高级)
电影调色常用曲线工具:暗部加点蓝、亮部加点黄,形成冷暖对比。这需要自定义着色器,但网上有现成的 CurvePass 实现。我自己写了一个简单的:
javascript
// 自定义着色器实现曲线调色(伪代码,实际要写GLSL)
// 这里不贴完整代码,太长了,思路是通过纹理坐标采样原图,然后对RGB值做曲线映射
说实话,调曲线很麻烦,而且实时计算开销大。所以我更推荐另一个方案:LUT。
四、LUT:电影调色的终极武器
LUT(颜色查找表)是电影工业的标准工具。简单说,你把一张图丢给调色师,他调好颜色,生成一个LUT文件(一张小图片),然后你在程序里把每个像素的颜色通过LUT映射成新颜色。
Three.js 里可以用 LUTPass 或者自己写 ShaderPass 加载LUT图。
javascript
import { LUTPass } from 'three/examples/jsm/postprocessing/LUTPass.js';
const lutPass = new LUTPass({
lut: textureLoader.load('path/to/your-lut.png'), // 一张512x512的LUT图
intensity: 1.0
});
composer.addPass(lutPass);
哪里找LUT?网上有很多免费的电影风格LUT,也可以自己用Photoshop生成。我下载了一套"柯达胶片"风格的LUT,加载进去之后,整个画面瞬间有了电影质感,暗部泛青,亮部偏暖,像《银翼杀手》的调调。
注意:LUT图必须是特定的格式(通常是512x512,横向16x16网格),网上有现成的。
五、其他氛围Pass
除了调色,再加几个小点缀,氛围感直接拉满:
1. 胶片颗粒(Film Grain)
给画面加一点噪点,模仿胶片质感。
javascript
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)
画面四周变暗,聚焦中心。
javascript
import { VignetteShader } from 'three/examples/jsm/shaders/VignetteShader.js';
const vignettePass = new ShaderPass(VignetteShader);
composer.addPass(vignettePass);
3. 景深(DOF)
模拟大光圈镜头,背景虚化。这个比较耗性能,慎用。
javascript
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);
六、完整流程示例
来一个完整的例子,把上面这些串起来:
javascript
// 初始化 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,就多一次全屏渲染。移动端慎用,或者只在需要时开启。
优化技巧:
- 降低内部渲染分辨率:
composer.setSize(width/2, height/2)然后全屏拉伸,画质略降但性能翻倍。 - 对不需要每帧更新的Pass,可以用
composer.renderToScreen = true跳过后续Pass。
2. 最后一个Pass要 renderToScreen
只有最后一个Pass需要设置 renderToScreen = true,否则画面可能不显示。或者你也可以在 composer.render() 里指定。
3. LUT图路径
LUT图最好用LinearFilter,不要生成mipmap,避免颜色偏移。
javascript
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
4. 兼容性
某些旧设备不支持浮点纹理,可能导致泛光失效。可以降级处理。
八、让画面动起来
静态调色不过瘾,还可以随时间变化。比如进入地下场景时,把LUT强度调高,颜色更暗;战斗时加红色脉冲。
javascript
let time = 0;
function animate() {
time += 0.01;
lutPass.intensity = 0.8 + Math.sin(time) * 0.2; // 脉动
composer.render();
}
这样画面就有了呼吸感。
写在最后
那次给科技公司展示完加了后期的版本,客户眼睛都亮了:"这才对嘛!有科技感!"
其实模型还是那个模型,灯光还是那个灯光,只是加了几道"滤镜",整个气质就变了。
后期处理就像给素颜的照片化妆,化得好是锦上添花,化不好就......咳咳,大家多试试吧。
最后提醒一句:后期虽好,可不要贪杯哦。毕竟性能是第一位的,别为了好看把用户电脑卡崩溃了。
互动
你用过哪些酷炫的后期效果?有没有什么压箱底的Pass推荐?评论区分享出来,让我抄抄作业 😏
下篇预告:【Three.js与WebGPU】下一代3D技术到底强在哪?