颜色空间,线性和伽马颜色空间

参考:颜色空间 - Unity 手册 

 

为什么要有伽马空间,那是因为人眼对亮度的感知没暗部感知来的敏感,举个例子,黑暗的房间,灯源数量从1增加到2,人眼能明显感知到变化。但从100增加到101,人眼就很难察觉变化,虽然从物理上来说它们的增量是相同的。

如果把真实光照按照线性变化,如0-100亮度对应0-1的像素值(8bit 像素对应 0~255 的线性亮度),则会造成大量的浪费,大部分亮度信息我们感知不明显。所以有伽马空间,就是通过一种矫正,把0-100的亮度映射到合理的范围(举个例子,0-50对应0-0.7,51-100对应0.7-1这样,不太准确的举例,但大概意思差不多) 

把真实世界的线性光照进行非线性映射存储的过程叫伽马编码(sRGB 色彩空间),我们要看到照片:

  • 对于遵循 sRGB 标准的现代显示器(LCD/LED),其物理输出是线性的,因此需要先对 sRGB 图像做伽马解码(线性化),再输出;
  • 对于早期 CRT 显示器,其物理特效很凑巧的跟伽马空间对应上,不需要手动解码,因为显示器的物理特性本身就等效于伽马解码

 

Unity Editor 允许您使用传统的伽马颜色空间以及线性颜色空间。伽马颜色空间是历史悠久的标准格式,但线性颜色空间渲染可提供更精确的结果。

如需进一步阅读这方面的信息,请参阅以下相关文档:

线性和伽马颜色空间

人眼对光强的反应不呈线性。我们在观察光时会发现一些亮度比另一些亮度更容易看到,即从黑到白的线性渐变在我们人眼中不是线性渐变的。

左:线性渐变。右:人眼对该渐变的感知情况。注意每种情况下界限(正好是中灰色)与渐变相融合的位置左:线性渐变。右:人眼对该渐变的感知情况。注意每种情况下界限(正好是中灰色)与渐变相融合的位置

由于历史原因,监视器和显示器具有相同的特性。向监视器发送线性信号会导致看起来像上图右侧的渐变,人眼观察感觉是错误的。为了弥补这一点,需要发送经校正的信号来确保监视器能够呈现出看起来自然的图像。这种校正称为伽马校正。

伽马和线性颜色空间同时存在的原因是,光照计算应该在线性空间中进行,以便确保数学上的正确性,但结果应该在伽马空间中呈现以便让人眼看起来正确。

在帧缓冲格式限制为每通道 8 位的旧硬件上,计算光照时使用伽马曲线可在人类可感知的范围内提供更高的精度。在人眼最敏感的范围内,使用的位数最多。

即使当今的监视器是数字显示器,它们仍然采用伽马编码信号作为输入信号。图像文件和视频文件显式编码到伽马空间(这意味着它们带有伽马编码值,而不是线性强度)。这便是标准;一切数值都在伽马空间内。

伽马空间的公认标准称为 sRGB(请参阅 Wikipedia)。该标准定义了它与线性空间之间的一个映射,使得人眼能充分利用 8 位/通道的精度。

线性渲染指的是渲染场景的过程,此情况下的所有输入都是线性的,也就是说,没有经过伽马校正以适合人眼观察或输出到显示器。

 

Unity里的设置:

  1. 颜色纹理(贴图 / 照片) → 勾选 sRGB (Color Texture)
    Unity 会在采样后自动做伽马解码(线性化),让光照计算在线性空间中进行(光照计算必须线性才准确);最终输出到显示器前,再做一次伽马编码,匹配显示器的输出特性。
  2. 数据纹理(法线图 / 金属度图) → 不勾选 sRGB
    这类纹理存储的是 “数据” 而非 “亮度”,不需要伽马校正,直接以线性空间参与计算。

image

从左往右分别是(Linear空间下不勾sRGB,Linear空间下勾sRGB,Gamma空间(勾不勾sRGB都一样))

image image image

sRGB 纹理选项的本质:告诉 Unity 该纹理是否是 “伽马编码” 的(比如照片、UI 图),需要在采样时转换为线性空间;而非 sRGB 纹理(比如法线、高度图)是 “线性数据”,不需要转换。
但这个转换仅在线性(Linear)颜色空间生效,伽马空间下的逻辑完全不同:
    1. 线性空间(Linear):
      • 勾选 sRGB:Unity 采样时自动执行 线性值 = 伽马值^2.2(解伽马),用于光照计算;
      • 取消 sRGB:直接使用原始值(认为是线性数据)。
        👉 此时勾选 / 取消 sRGB 会有明显视觉差异。
    2. 伽马空间(Gamma):
      • Unity 默认所有颜色计算都在伽马空间完成,不会对 sRGB 纹理做解伽马转换;
      • 无论是否勾选 sRGB,纹理的原始像素值都会被直接采样使用(仅极少数特殊情况例外);
        👉 因此视觉上几乎无变化。

 

需要在Shder里手动进行颜色空间的转换吗?

一、核心逻辑:sRGB 对 Shader 的影响本质

sRGB 勾选的本质是给 Unity 一个 “纹理数据类型” 的标记:
  • ✅ 勾选 sRGB:告诉 Unity“这是伽马编码的颜色图(如贴图、UI),需要在采样时转线性空间”;
  • ❌ 取消 sRGB:告诉 Unity“这是线性数据(如法线、高度图),无需转换”。
这个标记的作用分为两种场景,直接决定 Shader 是否需要手动转换:

二、场景 1:Unity 全局颜色空间 = 线性(Linear)

这是 3D 项目的主流选择,sRGB 勾选会直接影响 Shader 的采样结果,且 Unity 会自动处理转换(无需手动写):

1. 勾选 sRGB 的纹理(颜色图)

  • Unity 在采样时自动执行:线性值 = pow(纹理采样值, 2.2)(解伽马);
  • Shader 中直接采样即可,无需手动转换:
    // 正确写法:直接用,Unity已自动转线性
    fixed4 col = tex2D(_MainTex, i.uv); 
    // ❌ 错误:手动再转一次会导致颜色过亮
    // fixed4 col = pow(tex2D(_MainTex, i.uv), 2.2);

2. 取消 sRGB 的纹理(数据图)

  • Unity 直接返回原始线性值,Shader 中无需任何转换:
    // 法线图:直接采样,无需转换
    fixed3 normal = UnpackNormal(tex2D(_NormalTex, i.uv)); 

关键例外:手动控制采样转换

如果需要强制覆盖 Unity 的自动转换(比如特殊效果),可通过 SamplerState 手动指定:
// 强制将sRGB纹理按线性采样(跳过自动转换)
SamplerState sampler_MainTex_Linear
{
    Filter = Bilinear;
    AddressU = Repeat;
    AddressV = Repeat;
    sRGB = false; // 关闭自动转换
};

fixed4 col = tex2D(sampler_MainTex_Linear, _MainTex, i.uv);
// 此时需要手动解伽马:
col = pow(col, 2.2);

三、场景 2:Unity 全局颜色空间 = 伽马(Gamma)

这是 2D/UI 项目的主流选择,sRGB 勾选对 Shader 采样结果无自动影响,是否手动转换看需求:

1. 勾选 / 取消 sRGB 的纹理

  • Unity 都会直接返回原始像素值(无自动转换),Shader 中采样结果完全一致;
  • 仅当你需要在 Shader 中做 “物理正确的光照计算”(比如 PBR)时,才需要手动转线性:
    // 伽马空间下,手动将sRGB颜色图转线性(用于光照计算)
    fixed4 col = tex2D(_MainTex, i.uv);
    col.rgb = pow(col.rgb, 2.2); // 手动解伽马
    
    // 光照计算(线性空间下更准确)
    fixed3 finalColor = col.rgb * _LightColor0.rgb;
    
    // 输出前转回伽马空间(可选,伽马空间下Unity会自动做,但手动更可控)
    finalColor = pow(finalColor, 1/2.2);

2. 数据图(法线 / 高度等)

  • 无论是否勾选 sRGB,都无需手动转换,和线性空间逻辑一致:
    fixed roughness = tex2D(_RoughnessTex, i.uv).r; // 直接用

四、关键总结:Shader 编写的通用原则

全局颜色空间纹理 sRGB 状态Shader 是否需要手动转换正确写法示例
线性 ✅ 勾选 ❌ 不需要 col = tex2D(_MainTex, i.uv);
线性 ❌ 取消 ❌ 不需要 normal = UnpackNormal(tex2D(_NormalTex, i.uv));
伽马 ✅ 勾选 ✅ 仅光照计算时需要 col.rgb = pow(tex2D(_MainTex, i.uv).rgb, 2.2);
伽马 ❌ 取消 ❌ 不需要 roughness = tex2D(_RoughnessTex, i.uv).r;

 

posted @ 2025-12-03 20:47  JeasonBoy  阅读(20)  评论(0)    收藏  举报