把黑色与白色各取一半,直接平均8位码值得到128;在线性光域平均后再编码,却得到约188。同样写着“平均”,结果差了60个码值。原因不是加法写错,而是参与加法的数字代表了不同的量。

第03篇为了观察采样,直接把教学信号映成灰度码值。本篇开始固定后续渲染器的颜色约定:光照累加与过滤使用线性浮点值,sRGB编码留到显示输出;高动态范围压缩另行处理,并保留压缩前数据。

RGB三个分量还没有说明全部含义

一个三元组只有在原色、白点和传递函数确定后,才具有明确的颜色含义。相同的RGB数字放在不同颜色空间里,不保证对应相同颜色。这里选择sRGB原色与D65白点,先区分它的编码值与线性光值,不扩展广色域转换。

设c为归一化的sRGB编码分量,L为对应线性分量,当前接口限定二者输入在[0,1]内。c不是与光能成正比的量。将c翻倍,通常不会让对应的L恰好翻倍。

这也不表示L直接以坎德拉或辐射亮度单位计量。当前L是相对于约定白色的线性数值,能够在同一工作空间中做线性组合;要得到绝对物理单位,还需曝光、光源与显示标定。后续讲物理光照时会再次明确这些单位。

sRGB不是一个简单的2.2次方

本实验依据W3C CSS Color 4的sRGB定义,使用分段解码:

L=D(c)={c/12.92,c0.04045,((c+0.055)/1.055)2.4,c>0.04045.L=D(c)=\begin{cases} c/12.92,&c\le0.04045,\\ ((c+0.055)/1.055)^{2.4},&c>0.04045. \end{cases}

对应输出编码为:

c=E(L)={12.92L,L0.0031308,1.055L1/2.40.055,L>0.0031308.c=E(L)=\begin{cases} 12.92L,&L\le0.0031308,\\ 1.055L^{1/2.4}-0.055,&L>0.0031308. \end{cases}

低端是线性段,高端才用幂函数。pow(x,1/2.2)可以作为某些粗略近似,却不能在声称实现标准sRGB传递时替代上述分段式。输入数据已经编码还是仍为线性,也不能靠观察数值大小判断。

W3C还定义了处理负值的扩展形式。当前教学接口选择有限且位于[0,1]的子集,超界、NaN与无穷均报错,不能称为完整CSS颜色实现。后续HDR数据可以大于1,但必须先经过明确的显示策略,不能偷偷塞入这个受限编码接口。

标准中的分段常数经过取整,不能对临界点附近的任意浮点输入要求编码解码严格比特互逆。本次检查独立核对两个分段阈值,并对全部256个8位输入码值做浮点往返,而不是把任意舍入误差都当作实现错误。

黑白平均的两条路径

对黑色c=0和白色c=1,错误地把编码值当线性光量,会先算:

cdirect=(0+1)/2=0.5.c_{direct}=(0+1)/2=0.5.

转换成8位整数是 round(255×0.5)=128。但这个编码值解码后的线性值只有:

D(0.5)0.2140411405.D(0.5)\approx0.2140411405.

如果需要的是两份线性贡献的等权平均,应该先解码、平均,再编码:

Lmean=(D(0)+D(1))/2=0.5,E(Lmean)0.7353569831.L_{mean}=(D(0)+D(1))/2=0.5,\qquad E(L_{mean})\approx0.7353569831.

对应8位整数为188。对这组输入,直接平均编码值保留的线性量显著小于0.5,因此在相同输出条件下更暗。严格说128/255并不恰为0.5,实际解码约为0.21586;这点量化差异不改变两种路径的结论。

下面是实际程序输出。左半为码值128,右半为码值188;两块各32×32,整张原图64×32,放大仅用于观察。它不是显示器亮度测量,数值证据来自程序与原始文件。

左半直接平均编码值128,右半线性平均后编码188

黑白是便于手算的特殊例子。对一般sRGB纹理,过滤路径仍应是先解码到相同的线性工作空间,按权重组合,再编码。若纹理存的是法线、深度、粗糙度等数据,它就不是需要按sRGB解释的颜色贴图;不能对每张三通道图片都套用颜色解码。

只在输出时编码一次

累计代码加入 LinearRgb {r,g,b},与第00篇的整数Rgb8区分。display_pixel接收[0,1]内的线性RGB,逐分量编码并量化;Image仍保存最终整数输出。后续渲染阶段应在调用它之前完成光照计算,不能把整数帧缓冲当作线性累加器。

这个结构名表达约定,没有自动赋予数值物理正确性。调用方若把已经编码的0.735再当L输入,就会重复编码并使结果偏亮;若把线性0.5直接写成码值128,则少编码一次。两种错误可以生成合法PNG,图片解析器不会替渲染器识别。

同样,8位量化是有损步骤。即使函数实现正确,编码、取整、解码后也不能恢复任意原始浮点值。原始CSV因此是验收的一部分,而不是从PNG倒推出原来的计算值。

HDR压缩与输出编码不是同一件事

光照相加可能得到大于1的线性值,例如L=4。它表示相对于当前参考白的更大线性量,不能因为8位显示通道只有0到255就提前把它改成1。

本实验比较两个明确的显示策略。第一条将L截断到[0,1];第二条使用简单压缩函数:

T(L)=L1+L,L0.T(L)=\frac{L}{1+L},\qquad L\ge0.

然后两条路径都执行相同的sRGB编码。对L=4,截断结果为1,压缩结果为0.8,压缩后编码约为0.9063317533。0.8与0.9063分别位于线性显示域和编码域,不能写成同一个“亮度值”。

这个函数只是用于观察高光压缩的教学选择,不代表完整的摄影或HDR显示流程。对RGB逐通道使用它还可能改变色彩关系;当前图仅使用灰阶,不能据此宣称彩色场景的色相已被良好保持。

下面从左至右取128个线性样本,范围0到4。上半先截断再编码,下半先压缩再编码。图像由实际C++输出,原图128×32。

上半截断高光,下半L除以1加L压缩,之后均sRGB编码

上半在达到1后变成相同白色,原输入1、2、4的差异丢失。下半仍保留高光变化,却也改变了包括中间调在内的数值。它不是“把真实亮度完整装进8位”,而是按指定曲线重新分配显示范围。

linear.csv保留每个位置的原L、截断后L、压缩后L及两种编码值,共128行,末行原L仍为4。即使将来更换显示策略,也可以从这份未截断数据重新计算。若只保存上半的白色像素,就无法判断它原来来自1还是4。

运行检查覆盖了什么

在仓库根目录执行:

1
2
make -C examples/computer-graphics check
examples/computer-graphics/build/color_check examples/computer-graphics/build

输出目录须预先存在。检查器复跑已有实验,并得到:

1
2
3
encoded_mean_code=128 linear_mean_code=188 encode(0.5)=0.735356983052449 decode(0.5)=0.214041140482233
HDR input=4 clipped=1 tone=0.8 tone_srgb=0.906331753344059
PASS 256 code roundtrips, thresholds, 4 rejected inputs; saved 128 linear samples

阈值检查使用独立常数:E(0.0031308)=0.040449936E(0.0031308)=0.040449936D(0.04045)0.00313080495356D(0.04045)\approx0.00313080495356。256个8位归一化码值经过解码再编码,与原值之差均不超过 101210^{-12}。这一步没有插入新的8位量化,所以它验证的是传递函数往返,不是证明8位存储无损。

实际被拒绝的四个编码输入是−1、2、NaN和正无穷。前两个超出当前[0,1]契约,后两个不是有限数。PPM头部与通道数另行检查,PNG转换成功。图像文件用于浏览器观察,CSV用于线性数值核对;没有随机采样或性能结论。

练习与自检

练习一:两份相同灰色。 两个输入编码值都为0.5,等权平均的正确输出仍为0.5吗?这是否说明直接平均编码值总是安全?

答案:先解码后,两份都为约0.21404,平均不变,再编码回约0.5。相同值的平均当然不变,但黑白反例已经说明不同值不满足同样结论。用两个相同颜色测试过滤,无法暴露错误工作空间。

练习二:高光还能恢复吗? 输入L=2和L=4,若先截断到1再保存PNG,能从文件区分二者吗?若保存原始线性CSV,再换一条压缩曲线,是否需要重跑光照计算?

答案:截断后两者相同,无法唯一恢复。若CSV保留完整原线性结果且其工作空间、曝光等约定不变,更换显示压缩与编码可直接重新处理,不必重算产生这些值的光照。它不保证任意场景参数改变都能后处理解决。

系列导航与资料

前篇:03:为什么细线会断裂、棋盘格会闪烁系列入口。下一篇:05《哪些样本属于三角形》,尚未完成。

W3C CSS Color 4,2026-09-13候选推荐草案,§10.2给出sRGB定义与解码,§10.3区分线性sRGB,§19给出非规范性的颜色转换示例代码。实际查阅于2026-09-19;本文不把该草案称为最终推荐标准,也未实现CSS完整的扩展色域行为。

源码为examples/computer-graphics/color.hppcolor_check.cpp,原始运行记录为writing-plans/computer-graphics/evidence/04-cpu.txt。PPM延续系列的显示码值变体,用于中间输出;不把它冒称严格BT.709标定图像。浏览器观察属于相同页面下的相对对照,不是显示器校色报告。