Unity的颜色空间管理与转换

  1. 纹理取消选中 sRGB (Color Texture), 则可以绕过sRGB 采样
  2. Unity光照计算都是在线性空间中
  3. 老版本的gui系统 贴图导入时不会移除其伽马校正。
  4. 可以在线性空间shader中手动进行gamma矫正。尽量使用内置函数,因为Unity处理了图片,参数之类的需要一致性

两种颜色空间的介绍

Unity引擎支持两种颜色空间: 伽马(Gamma) 和 线性(Liner) 伽马颜色空间是历史悠久的标准格式, 但线性颜色空间渲染可提供更精确的结果

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

Unity伽马和线性颜色空间同时存在的原因是,光照计算应该在线性空间中进行,以便确保数学上的正确性,但结果应该在伽马空间中呈现以便让人眼看起来正确。 在帧缓冲格式限制为每通道 8 位的旧硬件上,计算光照时使用伽马曲线可在人类可感知的范围内提供更高的精度。在人眼最敏感的范围内,使用的位数最多。 即使当今的监视器是数字显示器,它们仍然采用伽马编码信号作为输入信号。图像文件和视频文件显式编码到伽马空间(这意味着它们带有伽马编码值,而不是线性强度)。这便是标准;一切数值都在伽马空间内。 伽马空间的公认标准称为 sRGB(请参阅 Wikipedia)。该标准定义了它与线性空间之间的一个映射,使得人眼能充分利用 8 位/通道的精度。下面是此映射的图表。 线性渲染指的是渲染场景的过程,此情况下的所有输入都是线性的,也就是说,没有经过伽马校正以适合人眼观察或输出到显示器。

Unity线性和伽马工作流程

纹理倾向于保存在伽马颜色空间中,而着色器期望使用线性颜色空间。因此,在着色器中对纹理进行采样时,基于伽马的值会导致不准确的结果。为了解决此问题,可将 Unity 设置为使用 RGB 采样器从伽马采样跨越到线性采样。这样确保了线性工作流程中的着色器的所有输入和输出都在正确的颜色空间中,从而产生正确的结果。

切换Gamma 和Liner 选项模式

要指定伽马或线性工作流程,请选择Edit > Project Settings,再选择 Player 类别,导航到 Other Settings,打开 Rendering 部分,然后根据偏好将 Color Space 更改为 LinearGamma

需要注意:Android平台需要注意的是, Liner 模式不支持 OpenGLES2, 自然也不支持 Auto Graphic API选项

需要注意的项

Gamma空间模式

即使这些值在伽马空间中,Unity Editor 的所有着色器计算仍然按照在线性空间中的方式处理它们的输入。为了确保获得可接受的最终结果,Editor 在将着色器输出写入帧缓冲区时进行调整以处理不匹配的格式,并且不对最终结果应用伽马校正。

Liner空间模式

如果纹理是在线性或伽马颜色空间中创建的,则可使用线性颜色空间。向线性颜色空间着色器程序提供的伽马颜色空间纹理将在移除伽马校正后输入给着色器。

Unity线性和伽马空间的纹理

Unity 在默认情况下使用 GPU 的 sRGB 采样器从伽马颜色空间跨越到线性颜色空间。如果纹理是在线性颜色空间内创建的,则需要绕过 sRGB 采样

采用线性渲染的 Gamma 纹理

如果纹理位于线性颜色空间内,需要禁用 sRGB 采样。

线性渲染为渲染的场景提供不同的外观。如果创作的项目在伽马空间中渲染时看起来很好,那么更改为线性渲染时,视觉效果不太会仍然保持良好。因此,如果从伽马渲染更改为线性渲染,您可能需要一些时间来调整项目,使其看起来像以前一样好。不过,这种转变最终可实现更加一致和逼真的渲染。您可能需要调整纹理、材质和光照。

光照贴图

光照贴图中的光照计算始终在线性空间中完成。 光照贴图始存储在伽马空间中。这意味着,无论是在伽马还是线性颜色空间中,光照贴图纹理都是相同的。 位于线性颜色空间时,纹理样本在纹理采样时会从伽马空间转换为线性空间。位于伽马颜色空间时则不需要转换。因此,在更改颜色空间设置后,必须重新烘焙光照贴图:当 Unity 的光照设置为自动烘焙(默认设置)时,此过程会自动激活。

Unity 创建的光照贴图 EXR 文件中的数据位于线性空间内。导入过程中它将被转换到伽马空间。从外部光照贴图中引入光照贴图时,请将光照贴图的 Texture Type 设置为 Lightmap。此设置可确保在导入时绕过 sRGB 采样。

线性渲染支持的平台

  • Windows、Mac OS X 和 Linux(独立平台)
  • Xbox One
  • PlayStation 4
  • Android
  • iOS
  • WebGL

当设备不支持线性渲染时,不会回退到伽马空间。在这种情况下,播放器将退出。通过在脚本中查找 QualitySettings.activeColorSpace 即可核实有效的颜色空间。 如果平台或硬件不支持给定的颜色空间, 则最终实际使用的颜色空间可能会有所不同 * QualitySettings.desiredColorSpace : 用户指定的颜色空间rending * QualitySettings.activeColorSpace : 实际使用的颜色空间rending

namespace UnityEngine
{
    //
    // 摘要:
    //     Color space for player settings.
    public enum ColorSpace
    {
        //
        // 摘要:
        //     Uninitialized color space.
        Uninitialized = -1,
        //
        // 摘要:
        //     Gamma color space.
        Gamma = 0,
        //
        // 摘要:
        //     Linear color space.
        Linear = 1
    }
}

在 Android 上,线性渲染至少需要 OpenGL ES 3.0 图形 API 和 Android 4.3。 在 iOS 上,线性渲染需要 Metal 图形 API。 在 WebGL 上,线性渲染至少需要 WebGL 2.0 图形 API。

线性颜色空间和 HDR

使用 HDR 时,渲染在线性空间中执行到浮点缓冲区。这些缓冲区具有足够的精度,无论何时访问缓冲区,都不需要转换到伽马空间或从伽马空间转换。这意味着,在线性模式下渲染时,您使用的帧缓冲区会将颜色存储在线性空间中。因此,所有混合效果和后处理效果均在线性空间中隐式执行。当写入最终后备缓冲区时,将应用伽马校正。

线性颜色空间和非 HDR

启用线性颜色空间但未启用 HDR 时,将使用一种特殊的帧缓冲类型,它支持 sRGB 读取和 sRGB 写入(读取时从伽马转换为线性,而写入时从线性转换为伽马)。当此帧缓冲区用于混合,或将其绑定为纹理时,值在使用前将转换为线性空间。写入这些缓冲区时,所写入的值将从线性空间转换为伽马空间。如果在线性模式和非 HDR 模式下进行渲染,则所有后处理效果都会创建其源缓冲区和目标缓冲区并启用 sRGB 读写权限,以便在线性空间中进行后处理和后处理混合。

使用线性纹理

当纹理处于伽马颜色空间时,sRGB 采样允许 Unity Editor 在线性颜色空间中渲染着色器。当您选择使用线性颜色空间时,Editor 默认使用 sRGB 采样。如果纹理处于线性颜色空间,则需要使用线性颜色空间并为每个纹理禁用 sRGB 采样

  • 旧版本 gui不会移除 sRGB 对旧版 GUI 系统元素的渲染始终在伽马空间中完成。Editor GUI and Legacy GUI 的纹理在导入时不会移除其伽马校正。

线性创作的纹理

同样重要的是,如果查找纹理、遮罩和其他纹理存在有特殊意义且未应用伽马校正的 RGB 值,则必须绕过 sRGB 采样。这样可以防止被采样纹理的值在用于着色器之前删除不存在的伽马校正,确保使用磁盘上存储的原始值进行计算。Unity 假设 GUI 纹理和法线贴图纹理都是在线性空间中创作的。

Unity Shder 中进行手动矫正

使用内置函数

  • 包含 UnityCG.cginc
    • 内置的 liner转换 gamma函数
        inline half3 LinearToGammaSpace (half3 linRGB)
        {
            linRGB = max(linRGB, half3(0.h, 0.h, 0.h));
            // An almost-perfect approximation from http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1
            return max(1.055h * pow(linRGB, 0.416666667h) - 0.055h, 0.h);
            // Exact version, useful for debugging.
            //return half3(LinearToGammaSpaceExact(linRGB.r), LinearToGammaSpaceExact(linRGB.g), LinearToGammaSpaceExact(linRGB.b));
        }
    • 内置的 gamma 转换liner函数
    inline half3 GammaToLinearSpace (half3 sRGB)
    {
        // Approximate version from http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1
        return sRGB * (sRGB * (sRGB * 0.305306011h + 0.682171111h) + 0.012522878h);
        // Precise version, useful for debugging.
        //return half3(GammaToLinearSpaceExact(sRGB.r), GammaToLinearSpaceExact(sRGB.g), GammaToLinearSpaceExact(sRGB.b));
    }
  • 对最终的颜色进行运算
    // Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)

Shader "Name"
{
    Properties
    {
        //...
    }

    SubShader
    {
        // ...
        Pass
        {
           //...
        CGPROGRAM
            // ...
            #pragma fragment frag
            // ...
            #include "UnityUI.cginc"

            // ...
            fixed4 frag(v2f IN) : SV_Target
            {
                half4 color = // ...;


                //将线性运算的结果进行 gamma矫正
                color.rgb = LinearToGammaSpace(color.rgb);
                // ...
                return color;
            }
        ENDCG
        }
    }
}

文章作者: Yonggang Long
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yonggang Long !
 上一篇
2022-08-10 Yonggang Long
下一篇 
2022-08-10 Yonggang Long
  目录