3、颜色校正滤镜:亮度/对比度调节、色相/饱和度调整、颜色矩阵(ColorMatrix)实现、Gamma校正

好,咱们进入第三节课。说实话,颜色校正这块儿,是后处理特效里最实用、也最容易出效果的部分。你想想看,一个场景渲染出来,有时候就是差那么一点点味道——要么太暗,要么颜色太艳,要么整体发灰。这时候,颜色校正滤镜就是你的救星。

我个人习惯把颜色校正分成四个维度来聊:亮度/对比度、色相/饱和度、颜色矩阵,还有Gamma校正。咱们一个一个来。

亮度与对比度调节

先说最简单的。亮度和对比度,说白了就是让画面更亮或更暗,以及让明暗差异更明显。

在Three.js里,我们可以用 EffectComposer 配合自定义着色器来实现。但更省事的方式,是用现成的 ShaderPass 加上一个简单的片元着色器。

核心思路其实就一行公式:

// 亮度对比度着色器核心代码
gl_FragColor = vec4((vUv - 0.5) * contrast + 0.5 + brightness, 1.0);

嗯,这里要注意:brightness 的范围我一般控制在 -0.5 到 0.5 之间,contrast 控制在 0.0 到 2.0 之间。超过这个范围,画面就容易出现裁切——亮部死白,暗部死黑。

我的小技巧: 调节对比度时,记得把亮度稍微拉高一点。因为增加对比度会让暗部更暗,整体画面会变暗。我一般习惯对比度加 0.2,亮度加 0.1,这样画面看起来更通透。

色相与饱和度调整

色相调整,说白了就是让颜色在色环上转一圈。饱和度呢,就是让颜色更鲜艳或者更灰。

这里我推荐用 HSL 模型来处理。为什么?因为 RGB 直接调色相太反直觉了。你想想看,把红色调成绿色,在 RGB 空间里你得同时改三个通道,但在 HSL 空间里,你只需要转一下色相值。

核心代码长这样:

// 色相饱和度着色器
vec3 hsl = rgb2hsl(color.rgb);
hsl.x += hueShift;      // 色相偏移,范围 0.0 ~ 1.0
hsl.y *= saturation;    // 饱和度缩放,1.0 为原样
vec3 result = hsl2rgb(hsl);

我在项目中遇到过一个问题:直接调色相时,皮肤颜色很容易变得诡异。后来我发现,色相偏移量控制在 ±0.05 以内,对肤色的影响就比较自然。超过这个范围,人就变成阿凡达了。

避坑指南: 我曾经在做一个美颜滤镜时,把饱和度调到了 1.5,结果画面里的红色直接溢出,变成了色块。后来我加了一个 clamp 操作,把 RGB 值限制在 0~1 之间,才解决了这个问题。记住:饱和度调整一定要做数值裁切

颜色矩阵(ColorMatrix)实现

颜色矩阵,这才是颜色校正的终极武器。说白了,就是用 4x4 的矩阵去变换 RGBA 四个通道。

它的威力在于:你可以用矩阵乘法实现亮度、对比度、色相、饱和度、甚至色彩混合的任意组合。一个矩阵搞定所有。

标准形式是这样的:

// 颜色矩阵着色器
vec4 color = texture2D(tDiffuse, vUv);
mat4 colorMatrix = mat4(
    r, 0, 0, 0,
    0, g, 0, 0,
    0, 0, b, 0,
    0, 0, 0, a
);
gl_FragColor = colorMatrix * color;

但说实话,手动写矩阵太痛苦了。我一般用 Three.js 的 ColorMatrix 类,或者直接传一个数组进去。

举个例子,实现一个复古滤镜(Sepia),矩阵是这样的:

const sepiaMatrix = [
    0.393, 0.769, 0.189, 0,
    0.349, 0.686, 0.168, 0,
    0.272, 0.534, 0.131, 0,
    0,     0,     0,     1
];

你看,这个矩阵把 RGB 三个通道混合在一起,产生了一种泛黄的老照片效果。这就是颜色矩阵的魅力——你可以任意组合通道。

核心要点: 颜色矩阵的第四行第四列控制 Alpha 通道。如果你不做透明度变化,保持为 1 就行。前三行的最后一列是偏移量,可以用来调整亮度。

Gamma校正

最后聊 Gamma 校正。这是个容易让人头疼的概念,我尽量说简单点。

人眼对亮度的感知不是线性的。你想想看,从 0 到 0.1 的亮度变化,人眼觉得很明显;但从 0.9 到 1.0 的变化,几乎看不出来。Gamma 校正就是为了补偿这个非线性。

在 Three.js 里,Gamma 校正通常分两步:

  1. 编码:在输出到屏幕前,对颜色做 Gamma 变换(一般是 1/2.2 次方)
  2. 解码:在纹理采样时,对输入颜色做逆 Gamma 变换(2.2 次方)

代码实现很简单:

// Gamma 校正着色器
float gamma = 2.2;
vec3 color = pow(originalColor, vec3(1.0 / gamma));  // 编码
// 或者
vec3 color = pow(originalColor, vec3(gamma));        // 解码

我记得有一次做 HDR 渲染,忘了做 Gamma 校正,结果画面亮部全部过曝,暗部又死黑。折腾了两天才发现是 Gamma 的问题。从那以后,我养成了一个习惯:只要涉及颜色输出,先检查 Gamma 是否做了

实用建议: Three.js 从 r130 版本开始,默认启用了线性工作流。你只需要在渲染器设置里打开 outputColorSpace 属性即可。但如果你用自定义着色器,记得手动处理 Gamma。

总结一下

颜色校正这块,我的建议是:

  • 简单场景:用亮度/对比度 + 饱和度,三个参数搞定
  • 中等场景:加一个色相偏移,做风格化调色
  • 复杂场景:上颜色矩阵,实现 LUT 级别的效果
  • 永远别忘了:Gamma 校正,这是颜色准确性的基石

下一节课,咱们聊聊更高级的滤镜——模糊、辉光和景深。这些才是真正让画面「哇塞」起来的东西。