Gamma Correction/Gamma校正/灰度校正/亮度校正 - 部分 DCC 中的线性工作流配置


纠正

之前自己写的乱七八糟,都是似懂非懂
直到有一天,要给项目中 linear 效果,要在 gamma space 下还原
后来发现以前自己写的这边文章,全写了一堆错误的理解,现在自己重新修改了很多出写错的地方


我们一般说的 gamma correction 简称 gamma,而 gamme correction 就是对 gamma2.2或是gamma0.45,也就是 pow(inputVal, 1.0/gamma),也就是 pow(inputVal, 1.0/2.2) 或是 pow(inputVal, 1.0/0.45)

注意,DCC软件中的 gamma 值的设置,一般都是这么使用,比如 3Ds max 中的 gamma 值:

  • outputVal=gammaCorrect(inputVal, gamma)
  • gammaCorrect(inputVal, gamma)=pow(inputVal, 1.0/gamma)
  • outputVal=pow(inputVal, 1.0/gamma)

下文有提到


OK,到这里,我们都不知道为何要有这个东西


因为经过前人验证过

如果现实世界中,我们看到的线性的颜色亮度,人眼看起来感觉不是线性的

如下图,看图感受:
在这里插入图片描述

  • 问题1:你觉得左边是线性值,还是右边是线性值?
  • 问题2:你觉得左边的还是右边的图中的黑和白的颜色过渡比较均匀?

那么下面回答:

  • 问题1 回答:左边是:非线性,右边才是:线性的值(这是前人使用过专业的光子平均面积亮度测量仪测出来的结果)
  • 问题2 回答:正常人都会觉得,左边的比较均匀,感觉中间就是刚刚好的黑与白中间的中灰

下面是右边的图的 中灰 位置
在这里插入图片描述

下面是左边的图的 中灰 位置
在这里插入图片描述


上图再次总结:

  • 人眼看到的亮度线性信息,大脑感知的是非线性的
  • 这个非线性呈:上拱曲线型,大概是:pow(inputVal, 1.0/2.2) 的输入输出曲线 (x轴:input,y轴:output)
  • 从上图的右图可得知,人眼看到的是暗部偏少(也就是说,暗部的值很快就往更亮的值靠近,所以才呈现:上拱形状)

使用 GGB 可以看到 pow(InputVal, 1.0/2.2) 是怎么个拱法:
在这里插入图片描述

为什么变亮了,如:输入线性值为 0.5,但是被上拱过之后,输出就变成了在 0.73 左右

所以原本 0.5 变成了 0.73 那肯定就更亮了
在这里插入图片描述

倒回这两个黑白颜色过渡图
在这里插入图片描述

如果我们想要将:右边的线性值,在人眼看到后在大脑呈现上拱(被提亮)过的信号,能像 左边那样看着比较舒服的信号

那么我们就需要之前开头说的:这里相对 humanEyeSeeLinear 之后的数据,做 类似 pow(inputVal, 2.2) - 压暗颜色 来处理 上拱过的数据 (压回限制感知强度)

注意,所谓的 gamma 校正 是 上拱的 pow(val, 1.0/2.2) 变成到 sRGB 空间下的颜色,因为以前的CRT显示器会 pow(val, 2.2) 的压暗处理

所谓的 degamma 是 下压的,就是将 sRGB 颜色还原到 Linear 颜色空间的过程,即:pow(val, 2.2)

还是使用 GGB 来查看效果
在这里插入图片描述

可以看到 humanEyeSeeLinear (没错就是 Cnglish,一般可以写为:linearAfterEyeSee 会更好一些)是人眼看到的线性感知后的上拱型,而我们需要的 gamma 是下压型来将 被拱 过的 压回 线性的

可以看到 经过 linear(x)=fixed(humanEyeSeeLinear(x)) 就是线性的结果(因此,现在各大渲染引擎,影视,都是按这套流程来渲染前处理颜色数据)


下面引用韩世鳞老师的一张图来说明(此图中的显示器都是 CRT为例),

DCC软件中编辑好的照片,在 CRT 显示器 中,感觉被 gamma=0.45454545; output=pow(input, 1.0/gamma); output=pow(input, 1.0/0.45454545);,导致亮度被压暗了,

因此需要提前 gamma=2.2; output=pow(input, 1.0/gamma); output=pow(input, 1.0/2.2);,也就是转到 sRGB 颜色空间的 sRGB 贴图,这样此图片在 CRT 上看着正常了

然后此贴图通过 internet 等传送方式到各个CRT设备上的显示时(这些图片都是经过:gamma=2.2; output=pow(input, 1.0/0.45454545); 处理过提亮了),

所以在 CRT 自带的 gamma=0.45454545; output=pow(input, 1.0/0.45454545) 的特性抵消好,刚好就是 linear 的结果

在这里插入图片描述


因为我们人眼在看到真实世界中的亮度值时

在眼球编码生物神经电信号给大脑中的灰度感知并非线性的(人眼会将 outputVal=pow(inputVal, 1.0/2.2) 处理(近似这样处理)

会将原来的线性颜色表现出来的亮度整体提升,这种视觉效果感觉很差,暗部色阶跳跃过大(暗部往亮提了,导致暗部灰阶细节降低,在 8bits 的表示 255 中,色阶跳跃过大,因此暗部细节表现就会丢失)

所以我们为了提高视觉效果更好,才会在颜色输出给人眼看到之前,先将压暗暗颜色,也就是outputVal=pow(inputVal, 2.2)

(注意:但是相对 CRT 显示器(会压暗,比如,输入电压0.5,输出亮度才 0.2+),因此我们需要给CRT显示器输入的颜色是要提亮的,也就是输入 sRGB 空间下的颜色,而不是压暗,下面的内容有提到)

这样将压暗过 pow(intputVal, 2.2) 的结果,这一升一降,刚刚好变成线性的

如果这时时候人眼看到线性颜色,肯定又是整体提亮的,所以需要再次 pow(inputVal, 2.2) 这时在人脑中的灰度信号是线性的,那么视觉上自然就会舒服很多,但是实际上真正的物理亮度是被压暗过的

CRT显示器是会降低亮度,近似:outputColor = pow(inputColor, 2.2) 这样的结果(如下图,参考:CRT显示器的亮度响应曲线图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


也可以简单参考 SIGGRAPH 2010 的 PBS 的 Gamma-Correct

s2010_physically_based_shading_hoffman_b.pdf

下面是原理,和需要处理的部分:

In Theory, Just Need To (原理,需要处理的):
• Convert shader inputs to linear before shading (在 shader 的 shading 前,先将输入值转为 linear)
• Convert shader output to gamma at end (在 shader 输出前,将 linear to gamma,其实应该是 fbo 输出前而不是 shader 输出前,这里写的不够精准)
• “Free” (pre-convert constants & vertex colors, HW converts from textures / to frame buffer) (“免费” (预处理 一些常量值 和 顶点颜色,硬件处理 纹理采样转换,和 输出到 frame buffer 的转换))

下面是一些引起的其他问题,多是一些早期的硬件没考虑 颜色空间的问题导致硬件程序的处理不当导致的:

Complications
• Some HW does gamma blending incorrectly (某些硬件处理 gamma 混合不正确)
– Bad for multipass / deferred shading, transparencies (多pass,延迟着色,透明 等,渲染都是糟糕的)
• Some HW filters gamma textures incorrectly (某些硬件处理 texture filter 不正确)
– But you can at least generate MIP maps the right way (但是你至少可以在生成mipmap时是对的)
• Actual nonlinear space supported by HW varies (实际上,非线性空间的支持 是各不相同的)
– Especially bad for consoles (尤其是糟糕的主机设备)

下面是导致需要在工作流中做对应调整的部分:

Unintended Consequences (其他确定的结果)
• Changes light distance falloff, Lambert falloff, soft shadow edges, vertex interpolation, etc. (改变灯光衰减,兰伯特衰减,软阴影边缘,顶点插值,等)
– May require artist adjustment / retraining (可能需要美术调整 或是 重新培训)
– In some cases (like vertex interpolation) it might make sense to fix in the shader (在某些情况(比如顶点插值)是可以在 shader 中修复的)


Unity 中的 gamma、linear 工作流

比如:“A纹理是在 Photoshop Gamma 空间下生产的”

意思说:在 Photoshop 中默认(默认的颜色空间配置,默认是sRGB的)对 A 纹理制作生产过程中,这类颜色空间下的颜色都是 pow(inputVal, 1.0/2.2) 的,想要返回Linear 空间需要 压暗亮度pow(inputVal, 2.2)

参考 :

unity 纹理 sRGB 勾选项的作用:

参考:TextureImporter.sRGBTexture

  • gamma 空间上
    勾不勾 都无所谓,都当作 没有勾选 纹理格式一样是使用:GL_COMPRESSED_RGB8_ETC2,虽然 unity 引擎不会申请 sRGB 格式,硬件也不会有 sRGB To Linear 处理,但是贴图图像的颜色值是否经过 sRGB 编码的决定于DCC软件产出的贴图是否sRGB颜色空间
  • linear 空间上
    • 勾上,那么unity 引擎会申请 sRGB 纹理格式,硬件 在 sample 后,做 sRGB to Linear (硬件将此纹理提亮;纹理格式为:GL_COMPRESSED_SRGB8_ETC2 (压缩格式取决于 unity 引擎中对此纹理的格式设置,这一小段的内容,我们整理出了下面的表格,并以 ETC2 格式纹理为例))
    • 不勾,就是说,硬件把你的纹理直接当做 linear 数据,硬件不会 在 sample 后,不做 sRGB to Linear (不压暗亮度) ,直接给到 shader 着色计算,纹理格式为:GL_COMPRESSED_RGB8_ETC2

简单总结为:

  • linear space 下
    • 纹理的 sRGB 勾上,说明此说明是 sRGB颜色空间的贴图,此贴图为的是看着更舒服的线性颜色,实际是提亮过的,请 unity 帮我在显示这个贴图的时候 处理压暗 pow(color, 2.2),并且申请sRGB贴图格式,进行 sRGB 编码提亮,在为的是shader调用sample时,硬件返回颜色前经过 压暗 pow(color, 2.2) 处理的,再返回线性值,用于后续shading的正确性;因此我们看到 inspector 中,一般勾上 sRGB 的话,inspector 中显示贴图会就变暗 (sRGB编码的硬件自动处理过程)
    • 纹理的 sRGB 没勾,说明此贴图没有经过 sRGB 编码过, shader调用sample后不会有任何处理就返回

所以你现在应该知道 unity sRGB 的作用了

因此设置unity颜色空间的话,其实除了会影响纹理的 sRGB 的格式外,还会影响 FrameBuffer(下面简称 FB)的格式 (简单总结区别就是: Linear Color Space 下,够了 sRGB 的贴图格式就是 sRGB 的,FBO 的 color attachment 的 纹理格式也是 sRGB 的)

下面以 OpenGL Graphics API 在 RenderDoc 中抓帧分析的情况,使用一张表格来表示

Tabel EditPlatformColor SpacesRGB CheckedHDRTextureDefaultFormatFrameBufferFormatComments
jave.linOpenGLGammaFalseFalseGL_COMPRESSED_RGB8_ETC2GL_RGBA8初始化…
jave.linOpenGLGammaTrueFalseGL_COMPRESSED_RGB8_ETC2GL_RGBA8在 Gamma 空间下,将 sRGB=True,发现 Texture, FB 的格式都没有变化
jave.linOpenGLGammaTrueTrueGL_COMPRESSED_RGB8_ETC2GL_R11F_G11F_B10F将 HDR=True,发现 FB 格式 RGBA8 变成了 R11FB11FG10F,A通道精度完全丢失,都放到RGB通道来来做High Range
jave.linOpenGLLinearTrueTrueGL_COMPRESSED_SRGB8_ETC2GL_SRGB8_ALPHA8Color Space调整为Linear后 Texture 和 FB 的格式都有变化
jave.linOpenGLLinearFalseTrueGL_COMPRESSED_RGB8_ETC2GL_SRGB8_ALPHA8sRGB=False,Texture格式变为:GL_COMPRESSED_RGB8_ETC2
jave.linOpenGLLinearFalseFalseGL_COMPRESSED_RGB8_ETC2GL_R11F_G11F_B10F将 HDR=False,发现 FB 格式 RGBA8 变成了 R11FB11FG10F,A通道精度完全丢失,都放到RGB通道来来做High Range

(以下内容在 2024/01/04 补充,一直忘记补充)
我们去测试了一个特效面片绘制的时候,在 gamma 和 linear 下的绘制差异是不同的

下面是 gamma 下的-贴图格式: GL_COMPRESSED_RGBA_ASTC_5x5没有unity贴图后处理pow(color, 2.2),也没有sRGB编码,也就是硬件的没有pow(color, 1.0/2.2)编码,也没有shader调用sample后的pow(color, 2.2))
在这里插入图片描述
下面是 linear 下的-贴图格式: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5unity贴图后处理pow(color, 2.2),也sRGB编码,也就是硬件的没有pow(color, 1.0/2.2)编码,也shader调用sample后的pow(color, 2.2))
在这里插入图片描述

所以贴图格式是不同的,除此之外,FB的Color Attachment 的 texture internal format 也是不同的

下面是 gamma 下的 FB color attachment texture internal format : GL_RGBA8没有sRGB,即FBO color attachment 的 texture 格式不是 sRGB 的,会直接用于最终的颜色输入到屏幕设备)
在这里插入图片描述

下面是 linear 下的 FB color attachment texture internal format : GL_SRGB8_ALPHA8sRGB,即FBO color attachment 的 texture 格式 sRGB 的,用于最终的颜色输入到屏幕设备,硬件会处理 pow(color, 1.0/2.2) 压暗后,再输入到屏幕设备)
在这里插入图片描述

然后,如果比较细心的同学可能会发现 format 为何没有 compressed 之类的字样

因为如下图,fb buffer 的 texture format 使用 compressed 是不适合的,是不可工作的,如下图:
在这里插入图片描述

在 RDC 中,可以看到,gamma 下的贴图 RGB 通道的颜色值是比较的,因为没有经过unity sRGB 贴图后处理的压暗处理,如下图:
在这里插入图片描述

而 linear 下的贴图 RGB 通道的颜色值是比较的,因为经过unity sRGB 贴图后处理的压暗处理,如下图:在这里插入图片描述

另外我对 GL_SRGB8GL_SRGB8_ALPHA8 特别需要注意的点,都翻译说明了一下下,如下图:
wiki : Image Format
在这里插入图片描述
另一个 wiki 也有讲到 : sRGB
opengl 3.0 (比如 nVidia GeForce 8 代)有将在 filter 或是 差值前,先将 color 从 srgb 转换到 linear
并且几乎没有性能的代价
在这里插入图片描述


那么继续往下看

在 Unity 中,颜色空间的有:

  • Edit/Project Settings…/Player/Other Settings/Color Space: Gamma 或是 Linear
  • 每个纹理资源只要是颜色类型的纹理(不是 AO, Normal, Height 贴图),那么 sRGB 的勾选需要在对应的 Color Space 下选择是否勾选(简单理解就是,用于给用户颜色直接输出的颜色值的贴图,要是用户给眼睛看的贴图,都是要 sRGB 的):
    • 在 Color Space 为 Gamma 时,勾上(Unity 默认情况下时会勾上的)不勾上选都无所谓,因为在 Color Space 为 Gamma 时,纹理是否 sRGB 空间的颜色值,取决于 DCC软件导出是否 sRGB 的
    • 在 Color Space 为 Linear 时
      • 如果我们美术输出的纹理颜色资源都是在被 压暗过的数据,或者说是在 Gamma 空间下生产的资源,那么 unity 就要勾上 sRGB,让 unity 引擎给这个纹理格式申请一个 sRGB 格式,纹理采样返回前硬件就会先(也就是 pow(val, 2.2)),然后再返回值
      • 如果我们美术输出的纹理颜色资源都是 线性的数据(没有提亮,也就是 dcc软件没有导出 sRGB),那么 unity 中,此贴图就不要勾选 sRGB,就是 shader 中采样这个纹理的时候,因为贴图没有申请为 sRGB 格式,硬件就不会处理 sRGB to linear的过程

(但是由于现在很多 DCC 软件输出的颜色纹理默认都是 被提亮过的数据,归根到底就是想要颜色的灰阶让人眼看起来舒服的,也就是 sRGB编码过的,所以一般我们在 unity Linear color spacce 下的颜色纹理默认都会勾上 sRGB,来让 unity 引擎申请此贴图为 sRGB 格式,并采样后,来一遍 sRGB To Linear (pow(val, 2.2)) 后,这压回线性空间:具体顺序:unity sRGB 贴图后处理pow(color,2.2),申请 sRGB格式硬件编码 pow(color, 1.0/2.2),shader sample后返回前 pow(color, 2.2))

unity 想要保留 gamma 的处理,因为Linear 空间是有硬件兼容性问题的 (早期没人在渲染管线上考虑到 sRGB 格式的纹理和FBO,因此会有旧版本兼容性的问题)

Linear 的颜色空间的话,如果在 Android 平台,那么需要在 Android 4.3 或是 OpenGL 2.0 以上(不含 2.0)


再次详细说一下 DCC 中的 sRGB 的作用 和 unity 贴图中 sRGB 的作用,和 sRGB 的 framebuffer 的作用

DCC软件产出的 albedo, emission, 等,与颜色相关的(美术直观调整的颜色值,不是数据值),默认产出的是 sRGB 的,sRGB 意思是经过 pow(val, 2.2) 压暗过的处理,sRGB中的 s 可以理解为 standard 的意思,如果你要了解什么是 sRGB 可以参考:

其实和色域有关,可以参考:

linear space 下,unity中贴图的 sRGB 属性的理解,如果勾选的话,意思让unity认为,你之前DCC导出的是 sRGB 下的颜色值(经过 pow(val,1.0/2.2)来提亮过的值),因此shader sample 后需要将其 pow(val,2.2)压暗再返回;

那为何 数据类型的值 不需要 sRGB 呢,比如,法线,rgb通道 (或是 dxt 的只使用到 rg 存 normal.xy 的) 存的是 向量的分量,
因为这类数据一般dcc软件默认是帮你生成的是没有经过 sRGB 处理的,因为不是颜色(因为不需要直接查看颜色),
是数据贴图,所以你在unity linear color space下,这类贴图不需对其够上 sRGB (也就是不需要硬件处理 sRGB 2Linear,也就是 pow(val, 2.2))。

不然经过pow(val,/2.2)后,贴图存的法线方向就不对了,
又比如,一些贴图的通道,混合存放的是 R: 金属度,G: 光滑度,B: AO之类的,
因为这些数值都只是表示数值强弱,和而不是颜色,在shader中计算都不能变,否则就强弱值不对了。

在unity linear coloe space下,framebuffer是 sRGB 格式的,

framebuffer sRGB的作用:意思要将最终颜色输出到屏幕前会经过硬件层处理 pow(val, 1.0/2.2),

来抵消 显示器 本身的 gamma0.454545, pow(color, 1.0/gamma),即:pow(color, 2.2) (也就是压暗处理),

以前CRT显示器也是下压的,frame buffer sRGB 是不需要的,但是渲染流程上,无论是影视还是游戏的渲染,

以前也没有对 sRGB 的 pow(color, 2.2) 处理,导致 PBR 渲染的质量是一直都有问题的,

但是LCD显示器,默认集成保留gamma0.454545 (pow(val, 1.0/gamma),为的是兼容以前的工作流(LCD的电压输入和亮度输出可以精准控制,达到 输入和输出比是线性的,但是为了以前的工作流,所以集成保留了gamma0.454545 (pow(val, 1.0/gamma)),

其实在没有硬件格式 sRGB 贴图,和 FBO sRGB 的时候,我们也可以在 Gamma Color Space 下还原linear效果,做法如下:

【采样贴图后degamma颜色】
纹理采样后的颜色,在值着色前,我们自己处理 pow(tex_color, 2.2)

【后处理所有的像素灰阶gamma correct颜色】
在frame buffer output 前,来一波后处理 pow(pixel, 1.0/2.2)
就可以模拟现在的linear color space 的效果,只是这种在 shader 中,自己处理 sample 后的 pow(tex_color, 2.2),还有后厨里的 pow(pixel, 1.0/2.2) 并没有硬件驱动集成的那么高效

(在 linear color space 下,够了 sRGB 的纹理,才会申请 sRGB,具体可以查看前面的 render doc抓帧分析的过程)


Unity BRP 管线中的 shaderlab 提供的 gamma2linear, linear2gamma

下面是在:BRP 管线中的:UnityCG.cginc 下相关与 gamma2linear, linear2gamma 的代码

可以看出来:

  • half3 GammaToLinearSpace (half3 sRGB) 就是提供了对输入的 half3 sRGB 进行下压(变暗)曲线变换,等价于:outputVal=pow(inputVal, 2.2)half3 sRGB 也就是 被 dcc 导出的 sRGB 资源,都是被提亮过的
  • half3 LinearToGammaSpace (half3 linRGB) 就是提供了对输入的 half3 linRGB 尽心上拱(提亮)曲线变换,等价于:outputVal=pow(inputVal, 1.0/2.2)outputVal=pow(inputVal, 0.4545454545)
// Legacy for compatibility with existing shaders
inline bool IsGammaSpace()
{
    #ifdef UNITY_COLORSPACE_GAMMA
        return true;
    #else
        return false;
    #endif
}

inline float GammaToLinearSpaceExact (float value)
{
    if (value <= 0.04045F)
        return value / 12.92F;
    else if (value < 1.0F)
        return pow((value + 0.055F)/1.055F, 2.4F);
    else
        return pow(value, 2.2F);
}

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));
}

inline float LinearToGammaSpaceExact (float value)
{
    if (value <= 0.0F)
        return 0.0F;
    else if (value <= 0.0031308F)
        return 12.92F * value;
    else if (value < 1.0F)
        return 1.055F * pow(value, 0.4166667F) - 0.055F;
    else
        return pow(value, 0.45454545F);
}

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));
}

至于 IsGammaSpace() 其实就算判断,ProjectSettings…/OtherSettings/RenderSettings/Color Space : Gamma 与否:

下面是测试 shader代码

// jave.lin : 测试是否在 gamma color space 下的判断
Shader "TestInCammaOrLinear"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                if (IsGammaSpace())
                    return half4(1, 0, 0, 1);
                else
                    return half4(0, 1, 0, 1);
            }
            ENDCG
        }
    }
}

GIF 效果:
请添加图片描述


Unity中线性空间配置

  1. 确保 unity 项目在 线性空间 (Editor/Project Setting/Player/Other Setting/Color space : 选择Linear)
  2. 确保 MASK贴图的 inspector 面板属性中,不勾选:SRGB
  3. 确保 GraphicsSetting.asset(文件所在:<ProjectName>/ProjectSettings/GraphicsSettings.asset) 中的配置是否使用了 线性空间的 灯光强度

  1. 使用线性颜色空间 : Linear
    在这里插入图片描述

  2. 数据纹理SRGB不要勾
    在这里插入图片描述

  3. 用记事本打开:<ProjectName>/ProjectSettings/GraphicsSettings.asset 文件,修改最后的内容:m_LightsUseLinearIntensity : 1
    在这里插入图片描述


DCC 中的 gamma 值是怎么计算的?

在往下看 各种 DCC 软件 的 gamma 值设置

DCC 软件中是这么用的:(参考:Gamma and LUT Preferences

DCC 中提到的 gamma 值,都是这么用的:

  • outputVal=gammaCorrect(inputVal, gamma)
  • gammaCorrect(inputVal, gamma)=pow(inputVal, 1.0/gamma)
  • outputVal=pow(inputVal, 1.0/gamma)

gamma”的计算是 output_intensity = input_intensity (1/gamma)。也就是说,结果是原始值的 gamma 值的倒数次幂。该计算的一个结果是 1.0 的 Gamma 值根本不会调整图像。
在这里插入图片描述
Gamma = 1.0:无校正

I 代表强度。

伽玛计算的另一个结果是黑色不受调整的影响,白色也不受调整(或任何完全饱和的颜色)。Gamma 调整仅影响图像的中间色调。

在这里插入图片描述
左:伽马 = 1.8
右:gamma = 2.2,标准
水平轴代表输入(原始值),垂直轴代表输出(伽马校正值)。

标准伽马值为 2.2。这是sRGB 标准指定的理论上正确的值。现代显示器已根据此标准进行校准。Gamma 2.2 使存储在位图中并由渲染器使用的线性色彩空间在屏幕上看起来是线性的。

然而,由于摄影胶片的响应也不是线性的,一些用户发现这个理论上正确的值看起来太亮并且褪色了。一种折衷方案是渲染到 1.7 或 1.8 的 gamma,使事物看起来更具照片感;也就是说,就好像图像是在胶卷上拍摄然后显影的。更好的方法是使用mr 摄影曝光控制,它可以模拟胶片的响应。另一种方法是在后期制作中应用类似电影的响应曲线。

建议您始终打开伽玛校正。

如果您需要为特定显示器设置伽玛,请找到一个值,使您自己的显示器上的中间灰色与真正的中间 (50%) 灰色相匹配。

注意:如果使用您的渲染的唯一其他应用程序也是 Autodesk 媒体和娱乐产品,您可能希望使用 LUT 校正而不是 Gamma 校正来进行显示。


Photoshop 的线性工作流

在这里插入图片描述

(下面以 Photoshop 2022 cc,23.0.0 版本为例)


方式1

先将 图像/调整/32位每通道

然后在进行:缩放混合、或是高斯模糊,等等(涉及到 像素之间的颜色值运算的都需要先这么处理)
处理好折后,CTRL + SHIFT + E,将所有可将图层合同一个新的图层
在将这个新的图层复制打开在另一个 *.psd 工程
然后在对这个新的 *.psd 的颜色在此调整:图像/调整/8位每通道

这个方法在官方 Adobe 论坛有找到类似得说法:Creating Linear Color space

在这里插入图片描述

但是这种工作流的方式导致美术那边工作很麻烦

特别是多选图层的导出更加麻烦,每次导出都要 多选需要导出的、合并、再新建 PSD 图像/模式/8位通道,然后才能导出非 HDR 的 LDR 数据图片,比如:JPG, PNG 等

所以我们强烈推荐另一种方式2,往下看


方式2

另一种是:调整 编辑/颜色设置…
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

如果想要同步 PS 与 Unity 中的字体的透明度混合,需要设置下面的选项:
在这里插入图片描述
该方法可以参考我之后的一篇:Unity & PS Linear Workflow - Unity 和 PS 的线性工作流实践 - 简单配置示例

后续美术同学在新建 PSD 文档时,就可以设置:“颜色配置文件” 的配置信息了
在这里插入图片描述

还有 首选项->导出->去掉 颜色空间的 sRGB 复选框,这个参考:PhotoShop如何导出线性空间下的图片

(jave.lin : 首选项->导出->颜色空间的 sRGB 复选框 在部分低版本的 PS 中是没有的,我也没找到低版本在哪设置,或是压根就没得设置)

在这里插入图片描述

总结为一句话:编辑->颜色设置…->工作空间->RGB->自定义 RGB->Gamma: 1.0->保存,然后每次新建 PSD 都直接选这个 RGB 设置即可

这种方式在后续的贴图的制作中,设计到颜色的混合、模糊、各种计算,都是在 Linear 下的,保存导出的图像也是 线性空间下的,比之前 方式1 的有好很多


验证 Photoshop 中是否在线性工作流

检查自己的 PS 里面是否正确配置线性空间工作流,可以这么试验:
先在 同一图层 画两个 红、绿 圆形,让两个圆形 的像素有交集半个圆左右的面积
在这里插入图片描述

然后使用 高斯模糊 6.0 半径

如果是模糊混合出来的颜色是带有 黑色 边缘的,说明你的 PS 不是线性工作流
在这里插入图片描述

正确的 红、绿 模糊时 混出来的是 黄色,如下图:
在这里插入图片描述


PS与 Unity 下的 Linear Workflow 实践案例

直接参考我之后写的一篇:Unity & PS Linear Workflow - Unity 和 PS 的线性工作流实践 - 简单配置示例


AE 中的 Linear 工作流

在这里插入图片描述


C4D 中的 Octane 渲染器 的 Linear 工作流

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


3Ds Max VRay 中的线性工作流

在这里插入图片描述

Gamma Correction changes and Linear Workflow in 3ds Max - 官方 auto desk 3ds max 的 linear workflow
在这里插入图片描述


在一些 DCC 软件中,如果没有 Gamma 与 线性工作流的,需要自己去转换 gamma 与 线性
如下:

Linear to sRGB : sRGB(non-linear) = pow(linear pixel, 1/2.2)
sRGB to linear = pow(sRGB pixel, 2.2)

线性空间工作流程图

请添加图片描述


Docs


URP下的 UI alpha blend 的 gamma to linear

可以参考:


BRP下的 UI alpha blend 的 gamma to linear

(后续自己增加实例测试)


总结 unity 中 linear 下 tex的后处理以及srgb编码 和 shader sample srgb to linear 和 fbo output linear to srgb 的处理细节

通过上面的 RenderDoc 抓帧分析 : gamma 和 linear 的区别
我们可以得知

linear color space 下

【tex的后处理以及srgb编码】
tex如果勾上了 srgb 选项,那么此贴图将有 srgb格式,
有这个 srgb 格式意味着,此tex就当作DCC在导出贴图时候 srgb = pow(rgb, 1.0/2.2) 过的 (提亮过的),
这类贴图,引擎要处理pow(color,2.2)贴图资源后处理,
贴图格式申请sRGB,进行sRGB编码pow(color,1.0/2.2),
因此我们看到 linear color space 下,unity的贴图在 inspector 面板中,如果我们勾上 srgb 就会比没有勾上 srgb 的要压暗一些的原因
【tex的 shader sample srgb to linear】
在 shader 中我们对这些贴图采样的话,硬件会将 srgb 的值经过 rgb = pow(srgb, 2.2) 再返回 (压暗过的),
那么经过 提亮 和 压暗,shader 中对这些 srgb 采样的结果其实就是 linear 下的了
【fbo的output linear to srgb】
用于抵消显示器(CRT/LCD)的pow(color,2.2),
linear 的颜色值需要先转回sRGB,
也就是 srgb = pow(fbo_linear_rgb, 1.0 / 2.2) 转到 srgb 的 gamma 空间来保存

gamma color space 下

就没有上面的 【tex的后处理以及srgb编码】【tex的 shader sample srgb to linear】【fbo的output linear to srgb】都没有

如果颜色贴图的话,是经过 sRGB 编码提亮过的,人眼看着灰度是很舒服的
如果数据贴图的话,也经过 sRGB 编码提亮的话,那么RGB通道的数据值都会偏大,就会导致 DCC软件里面的线性空间渲染效果和游戏引擎里面的差异很大


总结 sRGB 空间,sRGB shader中采样, gamma correct, degamma,CRT,LCD,早期需要 gamma correct 等,相对理解

  • sRGB 颜色空间
    是 色域范围定义的一种,原始线性空间颜色要转换到这个颜色空间,需要 pow(color, 1.0/2.2) 的,sRGB 颜色空间转到 线性空间,就是反过来 pow(color, 2.2)
  • sRGB 格式贴图
    图像颜色是在 sRGB 颜色空间 DCC的工具产出了,带有 压暗处理 pow(color, 2.2),需要一般 shading 前需要 pow(color, 1.0/2.2) 转到 Linear 后使用
  • 对于在 人眼 看颜色偏亮的情况 (人眼天然的近似 pow(color, 1.0/2.2) 后处理)
    • sRGB 颜色空间 就是为了近似于人眼的感知,所以将颜色 pow(color, 1.0/2.2)
    • gamma correct 是 压暗,这样颜色在脑子里,呈现的灰度强度是线性的,因此 sRGB 颜色想要看着其是线性的,需要 gamma correct, pow(color, 2.2)
    • degamma 是上拱,就是解决 gamma 的颜色校正,会倒回:人眼天然的近似 pow(color, 1.0/2.2),一般是为了抵消 CRT 的自带的 pow(color, 2.2),和现在流行的 LCD 集成的 pow(color, 2.2)
    • 我们要对颜色处理成 sRGB 的,也就是 pow(color, 2.2),人眼 看着才是线性灰阶
  • 渲染相关
    • 对于在 渲染前,颜色贴图的使用情况 (光照时候,我使用到的颜色,需要确保是线性的)
      • 需要 pow(color, 2.2) 这样将原来 sRGB 压过的颜色,提亮回线性的 (这部分可以硬件加速,即:申请为 sRGB 格式的贴图)
    • 对于在 渲染完,颜色最终要输出到显示器 (因为光照算法完毕了,已经成像了的线性颜色像素,我们需要转回 sRGB 输出到显示器,这样 和 人眼天然 degamma 抵消)
      • gamma correct 是 压暗,将原来计算完光照算法后得出的 linear 颜色值,压暗到 sRGB 在输出到显示器 (这部分可以硬件加速,即:申请为 sRGB 格式的FBO color attachment 的贴图)
  • 显示器
    • CRT 显示器输入电压于输出亮度【非】线性比,因此输入CRT之前如果是 sRGB 空间的颜色,则不需要处理,否则需要 pow(1.0/2.2) 提亮
    • LCD 显示器输入电压于输出亮度【是】线性比,为兼容以前工作流,集成了保留 pow(val, 2.2)
  • 早期gamma为了解决什么
    • 早期需要 gamma correct 都是为了让暗部、亮部精度均衡,暗部细节会好很多;
      另外,如果想要保留更多的暗部细节,是需要提升 float 的 bit 位数,早期基本都是 R8G8B8A8,每个通道8bits,RGBA共32bits;
      现在有 R16G16B16A16 每个通道 16bits,甚至是 R32G32B32A32,所以这些不精度存储是够的,就不需要gamma2.2即pow(color,1.0/2.2)编码了

另外,我罗列了一些我咨询GPT的结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


Project

如下图,我目前对比了 linear 和 gamma 下的渲染区别
并且在 gamma space 下,尽可能的还原了 linear space 的效果
其中人物的衣服渲染算是还原了
这里头还有很多需要还原的:

  • skybox (cubemap ,这类 cube 还不能通过 SetPixels 设置值,会有报错)
  • 皮肤
  • 后处理的所有颜色

请添加图片描述

下面是又是后续处理了皮肤
还有头发之后的 (头发没有处理完整,因为使用 ASE 连连看练出来的,使用 surface shader,虽然可以生成一下 vert, frag 的方式在修改,但是我懒得去修改了,这样就是 PBR 的 BRDF 里面的部分曲线是不一样的,所以可以看到头发有一些差异)

(剩下一些: cubemap 的贴图部分没有没有还原,这部分后续再想想方案)
请添加图片描述


References

  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值