【HDR学习】苹果EDR技术洞察(二)

 

  • 综述

苹果用 EDR 这个词是为了跟 HDR 区分开,因为 HDR 在不同的场景可能对应着不同的理解:

  1. HDR 显示:更生动的显示亮色和暗色
  2. HDR 格式:HDR10、Dolby Vision
  3. HDR 转换函数:PQ、HLG
  4. 色调映射(Tone Mapping):HDR → SDR

而 EDR(Extended Dynamic Range)是苹果推出的一套渲染管线技术,以支持在不同的屏幕上同时正确显示 SDR 和 HDR 内容。当显示 HDR 的内容时,EDR 并不会直接将 HDR 区域变得更亮,而是识别到 HDR 内容后提高整体屏幕亮度的同时,降低非 HDR 区域的白点值,使得其看起来没有那么亮。

  • EDR 的技术方案

SDR 的像素浮点数表示范围为 [0.0, 1.0],其中 0.0 表示黑色,1.0 表示白色。在 EDR 的像素浮点数表示中,SDR 的部分映射到 [0.0, 1.0],而大于 1.0 的部分就是比 SDR 更亮的 HDR 部分。

不像其他的 HDR 格式那样,EDR 不会做 Tone Mapping 将像素值都映射到 [0.0, 1.0] 的范围。这就意味着在渲染时,它有一套新的机制。当渲染时,像素浮点值范围为 [0.0, 1.0] 的 SDR 内容是始终会正常渲染的。(1.0, EDR headroom] 范围的 HDR 内容也是可以渲染的。但是,超过了 EDR headroom 的部分就会被丢掉。

EDR headroom 的存在支持了亮度更高的 HDR 内容,但是它具体是多少呢?其实,EDR headroom 是动态的,它的值受到多种因素的影响,比如:设备的显示技术、当前的显示亮度等等。

我们通常可以使用下面这个公式粗略估计 EDR headroom:

Headroom ≈ Display Peak / SDR

Pro Display XDR显示器可手动/自动调节的最大亮度等级是500nits(用于光线好的环境),所以把它作为1.0EDR预设值,这个范围就是SDR的范围,EDR值=1600nits/500nits=3.2;如果昏暗环境,手动/自动把显示器亮度调到4nits, EDR值就是1600nits/4nits=400。

  • 代码讲解

1. 以首选的EDR框架CAMetalLayer为示范。首先看以下4个步骤:选择使用EDR-》设置扩展范围色域-》将metaLayer的像素格式调整为浮点格式,如RGBA16Float-》实际生成EDR像素

前三个步骤代码示例如下:

有关第四个步骤,我们用ImageIO导入HDR静态图像内容,并将其渲染成EDR纹理,这个过程可以概括为: 先通过HDR图片建立CGImage-》绘制浮点bitmap-》创建浮点纹理-》将EDR位图导入到texture中-》将texture渲染成EDR可用的metal管线

1)读取HDR原图片,保存成CGImageRef格式。

CGImageRef ,这个结构用来创建像素位图,可以通过操作存储的像素位来编辑图片。其参数解释如下:

sizt_t是定义的一个可移植性的单位,在64位机器中为8字节,32位位4字节。
width:图片宽度像素
height:图片高度像素
bitsPerComponent:每个颜色的比特数,例如在rgba-32模式下为8
bitsPerPixel:每个像素的总比特数
bytesPerRow:每一行占用的字节数,注意这里的单位是字节
space:颜色空间模式,例如const CFStringRef kCGColorSpaceGenericRGB 这个函数可以返回一个颜色空间对象。
bitmapInfo:位图像素布局,这是个枚举
provider:数据源提供者
decode[]:解码渲染数组
shouldInterpolate:是否抗锯齿
intent:图片相关参数

2)绘制浮点位图

先读取像素位图的宽度、高度,再根据位图组成信息和色彩空间(之前设置过的Display-P3),调用CGBitmapContextCreate创建绘图上下文(相当于一个画布),然后调用CGContextDrawImage方法在当前上下文画图。

CGContextRef (Quartz 2D绘图的核心API是CGContextRef,该API专门用于绘制各种图形。)

CGContextRef CGBitmapContextCreate (
void *data,
size_t width,
size_t height,
size_t bitsPerComponent,
size_t bytesPerRow,
CGColorSpaceRef colorspace,
CGBitmapInfo bitmapInfo
);
/**
参数:
data                    指向要渲染的绘制内存的地址。这个内存块的大小至少是(bytesPerRow*height)个字节。使用时可填NULL或unsigned char类型的指针。
width                   bitmap的宽度,单位为像素
height                  bitmap的高度,单位为像素
bitsPerComponent        内存中像素的每个组件的位数.例如,对于32位像素格式和RGB 颜色空间,你应该将这个值设为8。
bytesPerRow             bitmap的每一行在内存所占的比特数,一个像素一个byte。
colorspace              bitmap上下文使用的颜色空间。
bitmapInfo              指定bitmap是否包含alpha通道,像素中alpha通道的相对位置,像素组件是整形还是浮点型等信息的字符串。
*/
  • CGBitmapInfo讲解

CGBitmapInfo由两部分取或运算组成,一部分是指定 cpu使用的大小端模式,另一部分指定的是颜色空间中每个 bule green red alpha 的排列顺序。

typedef CF_ENUM(uint32_t, CGImageByteOrderInfo) {
    kCGImageByteOrderMask     = 0x7000,
    kCGImageByteOrderDefault  = (0 << 12),
    kCGImageByteOrder16Little = (1 << 12),
    kCGImageByteOrder32Little = (2 << 12),
    kCGImageByteOrder16Big    = (3 << 12),
    kCGImageByteOrder32Big    = (4 << 12)
} CG_AVAILABLE_STARTING(10.0, 2.0);

typedef CF_ENUM(uint32_t, CGImageAlphaInfo) {
    kCGImageAlphaNone,               /* For example, RGB. */
    kCGImageAlphaPremultipliedLast,  /* For example, premultiplied RGBA */
    kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */
    kCGImageAlphaLast,               /* For example, non-premultiplied RGBA */
    kCGImageAlphaFirst,              /* For example, non-premultiplied ARGB */
    kCGImageAlphaNoneSkipLast,       /* For example, RBGX. */
    kCGImageAlphaNoneSkipFirst,      /* For example, XRGB. */
    kCGImageAlphaOnly                /* No color data, alpha data only */
};

typedef CF_OPTIONS(uint32_t, CGBitmapInfo) {
    kCGBitmapAlphaInfoMask = 0x1F,

    kCGBitmapFloatInfoMask = 0xF00, 
    kCGBitmapFloatComponents = (1 << 8), // 浮点型表示

    kCGBitmapByteOrderMask     = kCGImageByteOrderMask,
    kCGBitmapByteOrderDefault  = kCGImageByteOrderDefault,  // 默认
    kCGBitmapByteOrder16Little = kCGImageByteOrder16Little, // 16 位小端
    kCGBitmapByteOrder32Little = kCGImageByteOrder32Little, // 32 位小端
    kCGBitmapByteOrder16Big    = kCGImageByteOrder16Big, // 16 位大端
    kCGBitmapByteOrder32Big    = kCGImageByteOrder32Big // 32 位大端
} CG_AVAILABLE_STARTING(10.0, 2.0);
// Big、Little 大端和小端分别
// 大端表示低字节放在高地址,高字节放在低地址
// 小端表示高字节放在高地址,低字节放在低地址

颜色空间的格式 RGB肯定要连续排序,唯一可能的变化是A的存放位置,A存放位置有两种可能:

情况一:A放在RGB之后RGBA 对应iOS的CGImageAlphaInfo为AlphaLast.

(对于32位图像,4个字节表示一个像素,每8位表示一个颜色。)

情况二:A放在RGB前面ARGB 对应iOS的CGImageAlphaInfo为AlphaFirst

 再联系上大小端,那么

对于情况一:A放在RGB之后RGBA 

对于大端对齐的cpu其像素存储格式是 0xRGBA 

对于小端对齐的cpu其像素存储格式是 0xABGR

对于情况二:A放在RGB前面ARGB

对于大端对齐的cpu其像素存储格式是 0xARGB

对于小端对齐的cpu其像素存储格式是 0xBGRA

另外:Alpha通道的作用

 IOS是小端序,所以kCGBitmapByteOrder16Host就是 kCGBitmapByteOrder16Little。

3)创建RGBA16Float类型的纹理对象(MTLTexture)

通过newTextureWithDescriptor方法使用一块新的用于存放texture image data的内存来创建 MTLTexture纹理对象,该api中通过 MTLTextureDescriptor 来描述texture的属性

创建 MTLTexture的时候 MTLTextureDescriptor 被用于定义属性,包括图像尺寸(宽、高、深度)、pixel format、arrangement(array、cubemap)以及mipmap的数量。MTLTextureDescriptor 值在创建 MTLTexture 的时候有用,当创建完毕后, 改变 MTLTextureDescriptor 的属性将对已经创建的 texture 没有任何影响。也就是说,当纹理对象创建完成后,它的大多数属性,比如大小,新类型,像素格式都是不能改变的,但是纹理的像素数据是可以改变。

  • 创建一个包含 texture 属性的 MTLTextureDescriptor :
    • textureType 表示 texture的dimensionality 和 arrangement(array or cube)
    • width、height、depth用于表明 texture base level mipmap中每一个dimension的pixel size
    • pixelFormat表明 texture中的像素存储方式
    • arrayLength 表明 MTLTextureType1DArray or MTLTextureType2DArray 类型 texture的数组元素的数量
    • mipmapLevelCount 表明texture mipmap的数量
    • sampleCount 表明每个pixel的对应的sample数量
    • resourceOptions 表明内存分配的方式
  • 通过 MTLDevice 的 newTextureWithDescriptor: 方法根据 MTLTextureDescriptor 创建一个texture。创建完毕后,如果要复制内存的像素数据到纹理中,调用 replaceRegion:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage: 方法来加载texture image data

 4)将EDR的像素数据加到纹理中。

利用CGBitmapContextGetData获取EDR图像数据,并用replaceRegion设置EDR纹理。

5)使用metal管线渲染EDR

 【相关链接】

bilibili讲解的文字版描述: WWDC 2022 音视频相关 Session 概览(EDR 相关)丨音视频工程示例 - 掘金

bilibili讲解:【中文字幕】Apple:EDR是如何工作的?HDR渲染之道 | Explore HDR rendering with EDR_哔哩哔哩_bilibili

w3讲解EDR:http://3ms.huawei.com/hi/group/1004055/wiki_6221842.html

Apple’s “EDR” Brings High Dynamic Range to Non-HDR Displays — Prolost

微信公众号讲解: https://mp.weixin.qq.com/s/EgJkGimBs5AF1n3O4KqYog

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值