GPU Gems 1 · Chapter 22. Color Controls

用 Levels、Curves、一维查找纹理、灰度点积与三乘三矩阵,把艺术家可控的色彩校正稳定地带进实时 pixel shader。

学习目标

  • 能把 color correction 分解为逐通道 remap、查表与通道混合,并说明每一步的输入输出
  • 能实现 Levels 与 Curves 的 shader 版本,选择合适的一维或三维资源保存艺术家控制
  • 能用加权点积和三乘三矩阵完成灰度与色彩空间转换,并验证结果没有意外压暗或溢出
  • 能在实时预算内调试 lookup texture、gamma、saturation 和矩阵顺序,保留可复现的视觉控制

色彩校正不是渲染完成后的静态修图,而是把像素从一个可见的颜色关系映射到另一个关系。它可以把不同资产的色彩统一到同一场景,也可以制造褪色胶片、交叉冲洗、红外和游戏 UI 的风格。关键是把艺术家熟悉的控制,转换成 pixel shader 能稳定执行的资源。

的共同接口是逐像素输入和逐像素输出。输入可以来自 2D 纹理、3D 场景的 back buffer 或中间 render target;输出通常在 pixel shader 中完成,因此可以实时作用于完整画面。

color correction:pixel → mapping → channel mixinputscene pixelper-channelLevels / Curves1D LUTR, G, B mapsmixdot / 3×3outputgraded pixelartist-authored resource + pixel shader = repeatable real-time look
先区分逐通道 remap 与通道混合:前者适合 Levels/Curves,后者适合灰度、色相与色彩空间转换。

1. 逐通道控制:Levels 先建立可解释的基线

用少量参数描述每个通道的输入输出范围。将输入归一化后,可以先把 inBlackinWhite 映射到零到一,再施加 gamma,最后恢复到目标输出范围:

float LevelChannel(float value, float inBlack, float inWhite, float gamma,
                   float outBlack, float outWhite) {
  float normalized = saturate((value - inBlack) / max(inWhite - inBlack, 0.0001));
  float shaped = pow(normalized, gamma);
  return lerp(outBlack, outWhite, shaped);
}

这里把颜色先放到零到一的 shader 表示;若工具以零到二百五十五的整数工作,就在 CPU 端预先归一化参数,避免每个像素重复做无意义的缩放。 不同于黑白点:若三个通道的 gamma 不同,就不能把所有幂运算合并成一个共享标量,必须保留三条通道路径。

Levels 与 Curves:同一条输入轴上的不同控制力LevelsinBlackinWhite / gammaCurves → 1D LUTinput intensityoutput intensityeach channel keeps a one-to-one input → output lookup
Levels 是参数少、易于直接写进 shader 的 remap;Curves 更自由,通常先由艺术家制作映射,再烘焙为一维纹理。

Levels 适合做曝光、对比度和动态范围的基础调整。它也适合作为资产校验的第一层:给输入 ramp,检查黑点是否真的变黑、白点是否真的到达目标、gamma 是否只改变曲线而没有偷偷改变端点。

2. Curves 与一维查找纹理:把艺术操作变成数据

是 Curves 的运行时表示。Curves 工具可以让艺术家绘制任意的输入到输出关系;shader 不必重现 spline,而是读取这条已经烘焙好的 ramp。通常准备一张一乘二百五十六的 RGB 图:黑到白的初始 ramp 经过 Curves 调整后,R、G、B 三个分量分别成为三个通道的映射。

1D lookup texture:把艺术工具的曲线带进 shaderartist rampblack → whiteapply .acv / Curves1×256 RGB mapR / G / B rampspixel shadertex1D per channel→ graded RGBthe lookup resource preserves artist intent without reproducing spline math
1×256 的 RGB ramp 把复杂的曲线编辑变成稳定的运行时查表;shader 只需要三次一维采样。
float3 inputColor = tex2D(inSampler, IN.UV).xyz;
float3 outputColor;
outputColor.r = tex1D(colorCorrMap, inputColor.r).r;
outputColor.g = tex1D(colorCorrMap, inputColor.g).g;
outputColor.b = tex1D(colorCorrMap, inputColor.b).b;
return float4(outputColor, 1.0);

查表的工程契约需要明确纹理格式、寻址模式、采样过滤和颜色空间。对于连续的颜色映射,线性过滤能在相邻 texel 之间插值;对于需要精确分段的风格化 map,则应验证过滤不会制造不想要的中间色。工具导出的 ramp 还要和运行时读取的通道顺序一致。

3. 通道混合:灰度、矩阵与三维映射

当输出通道依赖多个输入通道时,逐通道一维 map 不再够用。 可以用每个输出通道一个 dot product 表示;把三个 dot product 放在一起,就是一个三乘三矩阵乘法。

灰度是最小的例子。按照官方章节给出的 Rec 709 权重,可以写成:

float grayscale = dot(float3(0.222, 0.707, 0.071), inputColor);
float3 grayColor = float3(grayscale, grayscale, grayscale);

权重总和为一,通常能保持整体能量尺度,但它也意味着纯蓝可能被压到很暗的灰度。游戏风格不一定要服从这个默认结果,因此应把权重作为显式艺术参数,而不是把它藏在 shader 常量里。

能表达最一般的 RGB 到 RGB 映射:

float3 outputColor = tex3D(colorSpaceSampler, inputColor);

代价是三维纹理的内存、滤波和制作管线。若转换关系是线性的,优先把它写成三乘三矩阵:

float3x3 conversionMatrix;
float3 outputColor = mul(conversionMatrix, inputColor);

矩阵不仅可以表达 RGB 到其他工业色彩空间,也可以表达饱和度、亮度和色轮旋转等控制。将多个线性操作预先在 CPU 端串接,可以减少 shader 中的中间值和纹理访问;但要记录矩阵乘法顺序,因为矩阵通常不满足交换律。

multichannel correction:dot product、3×3 matrix 或 3D mapinput RGBone pixel, three valuesgrayscaledot(0.222, 0.707, 0.071)Rec 709 weightscolor spacenew = M × RGB3×3 matrixoutput3D map is generalbut costly to authorchoose the smallest representation that preserves the required channel relationship
通道混合不能只靠三个独立的一维 map:灰度用加权点积,色彩空间转换通常用三乘三矩阵,完整映射才考虑三维查找表。

4. 运行时控制与验证策略

艺术家需要看到参数改变后的完整画面,程序员则需要知道每个参数改变的是哪条通道。建议把控制分成三层:先用 Levels 处理输入范围,再用一维 map 处理风格化的 per-channel remap,最后用 dot product 或矩阵执行需要通道混合的操作。若要做 grayscale,就在混合阶段明确写出权重;若要做 3D map,就将其视为独立资源并记录版本。

Color Controls Lab

先预测:gamma 与 saturation 改变的是同一件事吗?

可交互
per-pixel Levels + channel mixcontrast 0.41R0.71G0.48B0.30input samplemapped outputchannel mix = luminance + saturation × offset

观察:Levels 先压缩输入动态范围,gamma 改变曲线形状;saturation 只改变通道偏离 luminance 的幅度,grayscale 则把三个输出合成同一亮度。

实验中的示意像素不是完整色彩管理系统,但它能暴露三个高频错误:黑白点反转造成所有输出饱和,gamma 方向写反造成中间调过亮或过暗,以及 saturation 与 grayscale 顺序不清造成预览和 shader 不一致。发布前至少用黑、白、灰、纯红、纯绿、纯蓝和一条灰阶 ramp 验证,并在目标 framebuffer 的颜色空间中重复检查。

小结:把艺术控制编码成可验证的像素映射

  • color correction 先区分逐通道 remap 与通道混合,再选择 shader 公式或纹理资源。
  • Levels 用黑白点和 gamma 提供可解释的动态范围控制;Curves 更自由,适合烘焙到一维 lookup texture。
  • 灰度可用加权 dot product;线性的色彩空间转换可用三乘三矩阵;任意 RGB 到 RGB 关系才值得考虑 3D color map。
  • 运行时必须记录颜色空间、采样过滤、通道顺序和操作顺序,并用极端颜色与灰阶 ramp 回归。

练习

问题 1|改 Demo 代码 给定 inBlack=0.1inWhite=0.9gamma=1.0,输入通道值为 0.5。按 Levels 的归一化步骤计算输出的中间值;如果把 gamma 改为 2.0,中间调趋势如何变化?

问题 2|资源选择题 艺术家要复现三条任意 Curves 曲线,运行时每个像素只需要独立改变 R、G、B;应选择一维 lookup texture 还是三维 color map?说明制作和采样代价。

问题 3|独立实现题 为一个需要灰度预览、复古曲线和可调饱和度的实时画面设计 shader 与验证计划。请写出阶段顺序,并列出至少四个回归样本。

名词解释

本章出现的专业名词,用大白话再讲一遍。

color correction
将像素颜色从一种通道关系、色彩空间或艺术风格映射到另一种结果的过程。
Levels
使用黑白点、输出范围和 gamma 调整单个颜色通道动态范围的参数化校正。
1D lookup texture
由输入通道值索引输出通道值的一维纹理资源,常用于保存 Curves 映射。
color-space conversion
将 RGB 或其他通道值通过标准或自定义关系映射到另一种颜色表示的过程。
3D color map
用三维输入坐标索引输出颜色的查找资源,可表达任意 RGB 到 RGB 关系。
gamma
作用于归一化颜色值的幂函数参数,用于改变中间调的亮暗曲线。

资料与写作方式声明

本章以GPU Gems 系列权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…