【Three.js后期处理】如何让你的场景拥有电影级调色

30 阅读6分钟

前言

同样是三个盒子,加了后期,就像好莱坞大片

去年我做了一个项目,给一个科技公司做产品展示。模型是人家设计师精雕细琢的,灯光我也调了半天,自认为效果还不错。结果拿给客户一看,对方皱了皱眉:

“嗯……怎么说呢,看着有点……素?”

我盯着屏幕看了半天,确实,模型是模型,场景是场景,但就是缺点什么。说不上来,就像一碗牛肉面没放盐,啥都有,但不香。

后来我看了一个国外大神的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);

这样能简单调色,但离“电影级”还差得远。

2. 曲线调色(更高级)

电影调色常用曲线工具:暗部加点蓝、亮部加点黄,形成冷暖对比。这需要自定义着色器,但网上有现成的 CurvePass 实现。我自己写了一个简单的:

// 自定义着色器实现曲线调色(伪代码,实际要写GLSL)
// 这里不贴完整代码,太长了,思路是通过纹理坐标采样原图,然后对RGB值做曲线映射

说实话,调曲线很麻烦,而且实时计算开销大。所以我更推荐另一个方案:LUT


四、LUT:电影调色的终极武器

LUT(颜色查找表)是电影工业的标准工具。简单说,你把一张图丢给调色师,他调好颜色,生成一个LUT文件(一张小图片),然后你在程序里把每个像素的颜色通过LUT映射成新颜色。

Three.js 里可以用 LUTPass 或者自己写 ShaderPass 加载LUT图。

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)

给画面加一点噪点,模仿胶片质感。

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,就多一次全屏渲染。移动端慎用,或者只在需要时开启。

优化技巧:

  • 降低内部渲染分辨率: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. 兼容性

某些旧设备不支持浮点纹理,可能导致泛光失效。可以降级处理。


八、让画面动起来

静态调色不过瘾,还可以随时间变化。比如进入地下场景时,把LUT强度调高,颜色更暗;战斗时加红色脉冲。

let time = 0;
function animate() {
  time += 0.01;
  lutPass.intensity = 0.8 + Math.sin(time) * 0.2; // 脉动
  composer.render();
}

这样画面就有了呼吸感。


写在最后

那次给科技公司展示完加了后期的版本,客户眼睛都亮了:“这才对嘛!有科技感!”

其实模型还是那个模型,灯光还是那个灯光,只是加了几道“滤镜”,整个气质就变了。

后期处理就像给素颜的照片化妆,化得好是锦上添花,化不好就……咳咳,大家多试试吧。

最后提醒一句:后期虽好,可不要贪杯哦。毕竟性能是第一位的,别为了好看把用户电脑卡崩溃了。


互动

你用过哪些酷炫的后期效果?有没有什么压箱底的Pass推荐?评论区分享出来,让我抄抄作业 😏

下篇预告:【Three.js与WebGPU】下一代3D技术到底强在哪?