44858cba99a438495cd64d5bd4308829
Metal【14】—— Gamma & sRGB

之前有不少朋友问,为什么我渲染出来的画面比较暗?

这篇文章针对这个问题,从定位到解决,最后聊聊所涉及的一些图形学知识。

PS:

订阅后的朋友,可以加我微信:wxidlongze,拉你进群。交流,扯淡,学习资源分享~

最后,源码在文末~

那么,开始吧~


1. 问题描述、定位

问题很简单,为什么用 UIImageView 显示的图像,和 MetalView 显示的,存在差异?Metal 渲染出来的会偏暗,效果如下:

Metal 操作上,通过 MTKTextureLoader 创建纹理,然后直接渲染到 CAMetalLayer,很常规的操作。

let textureLoader = MTKTextureLoader(device: device)
let texture = try! textureLoader.newTexture(cgImage: cgImage, options: nil)

那么,问题出在哪里?

当遇到类似这样的效果问题时,建议的方式就是先开启 Capture GPU Frame看看每步操作下来,产生的 Texture 到底是什么样,从而定位在哪里出现的问题。

这里,我们同样这么来试试:

因为 Demo 比较简单,我们直接点开 All Resources,聚焦到我们关心的 Texture 部分。

这两个 Texture,简单来说分别代表 input 和 output,

  • 第一个 size 512 x 512 的,就是我们通过 CGImage 创建出来的 Texture,相当于 input。
  • 第二个 size 300 x 300 的,则是和 layer 大小一致的 drawable.texture,相当于 output。

可以看到,这里不管是 input 和 output,本身都已经偏暗了,所以输入源本身就存在问题了。

同时,细心的朋友可能发现了,Details 描述里,两个 texture 对应的 color space 不一致。

一个是 BGRA8Unorm_sRGB,而另外一个是 BGRA8Unorm,而这,就是问题所在。

注意,这里的 color space,均是默认选项,代码中未显式的设置。所以导致不少朋友疑惑,为什么简简单单的代码,出来的效果确不一致。

那么回到 color space 本身,假如我们设置成一样,是否就正常了?

添加如下代码:

metalLayer.pixelFormat = .bgra8Unorm_srgb

再次运行,效果如下:

这下舒服了,UIImageView 和 MetalView 的效果看起来是一致的,

点开 Capture GPU Frame,我们发现 color space 都已经变成了 BGRA8Unorm_sRGB,但是 Texture 本身,还是偏暗的。

到目前为止,我们看似解决了这个显示问题,但它是因为,在相同 color space 下,系统知道如何去渲染你的 texture,按照同样的规则,所以效果正常。

但本质上,这里的 input 已经变暗了,和原图不符,所以经由 shader 处理的时候,会出现其他意想不到的误差,因为这个变暗是非线形的变化。

为什么变暗,就是接下去我们要讲的 Gamma & sRGB。再次之前,我们再谈一个比较常见的问题,UIKit 颜色显示不一致的问题。

不知道是否遇到过,同样的 RGBA,设置后出来的效果,和 UI 的设计图不一致?

多半也是 color space 不一致引起的。

举个例子,同一个色值,#BF0A30 (191,10,48),通过下面两种方式,出来的效果是不一致的。

第一种,纯代码设置:

self.view.backgroundColor = UIColor(red: 191.0 / 255.0, green: 10.0 / 255.0, blue: 48.0 / 255.0, alpha: 1.0)

第二种,Storyboard 设置:

效果如下:

可以看到,Storyboard 设置的效果,会偏浅。

这同样是因为 color space 不一致导致的,而这同样是系统默认的配置不一样。

public init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)
// 默认是 sRGB

而 Storyboard,默认则是 Generic RGB。

将 Storyboard 的改成 sRGB,再重新设置下色值(注意修改 color space 后,数值是会改变的),这次效果就会一致了。

所以,颜色、显示效果,切记要把 color space 对应上!


2. Gamma

结合上面的两个现象,我们不难发现,当 color space 为 sRGB 时,显示的效果都会偏暗。

如果我们把 texture 的 color space 改成其他的,是否就正常了?

将 texture 的创建方式改成这样,强制标记非 sRGB:

let textureLoader = MTKTextureLoader(device: device)
let texture = try! textureLoader.newTexture(cgImage: cgImage, options: [.SRGB : false])

再次运行,效果正常,同时查看输入输出的 texture,也都正常。

top Created with Sketch.