3、HDR存储格式:OpenEXR格式详解、Radiance HDR格式、HDR元数据、色彩空间与色域
说到HDR图像,很多人第一反应是「拍出来的照片更亮」。其实没那么简单。真正决定HDR图像质量的,除了拍摄和合成算法,还有一个关键环节——存储格式。选错了格式,你辛辛苦苦合成的HDR数据,可能直接废掉一半。
我个人习惯把HDR存储格式比作「容器」。容器够大、结构合理,高动态范围的信息才能完整保留。今天我们就来拆解两个最主流的容器:OpenEXR和Radiance HDR,顺便聊聊元数据和色彩空间那些坑。
核心观点:HDR存储格式不仅仅是文件后缀名,它决定了你的亮度范围、色彩精度、元数据完整性,以及后续流程的兼容性。选对格式,事半功倍。
3.1 OpenEXR格式详解
OpenEXR是工业界的事实标准。ILM(工业光魔)在1999年开发,2003年开源。你看到的《阿凡达》《复仇者联盟》这些大片,中间帧几乎全是EXR。
为什么它这么强?说白了,它解决了三个核心问题:
- 高动态范围:支持16位浮点(半精度)、32位浮点(单精度),甚至32位整数。亮度范围远超普通8位/16位整数格式。
- 无损/有损压缩:PIZ、ZIP、RLE、DWAA/DWAB等多种压缩方式。我一般在最终交付时用PIZ无损压缩,中间调色用DWAA(有损但速度快)。
- 多层存储:一个文件可以存多个通道(R、G、B、A、Z深度、法线、运动向量……)。这在后期合成里太方便了。
来看一个典型的OpenEXR文件结构:
OpenEXR File Structure:
┌─────────────────────────────┐
│ Magic Number │ (4字节: 0x76, 0x2f, 0x31, 0x01)
├─────────────────────────────┤
│ Version Field │ (4字节: 版本号 + 标志位)
├─────────────────────────────┤
│ Header │
│ ┌───────────────────────┐ │
│ │ Display Window │ │ (显示区域)
│ │ Data Window │ │ (数据区域)
│ │ Channels │ │ (通道定义: 名称, 类型, 采样)
│ │ Compression │ │ (压缩方式)
│ │ LineOrder │ │ (扫描线顺序)
│ │ ScreenWindowCenter │ │ (屏幕窗口中心)
│ │ ScreenWindowWidth │ │ (屏幕窗口宽度)
│ │ PixelAspectRatio │ │ (像素宽高比)
│ │ ... (自定义属性) │ │ (元数据: 白平衡, 镜头信息等)
│ └───────────────────────┘ │
├─────────────────────────────┤
│ Pixel Data │
│ ┌───────────────────────┐ │
│ │ Scan Line 0 │ │
│ │ Scan Line 1 │ │
│ │ ... │ │
│ │ Scan Line N │ │
│ └───────────────────────┘ │
├─────────────────────────────┤
│ Offset Table │ (用于随机访问扫描线)
└─────────────────────────────┘
嗯,这里要注意:Header里的Display Window和Data Window是两个容易搞混的概念。Display Window定义了你「想看到」的区域,Data Window定义了你「实际存储」的区域。如果做裁剪或拼接,Data Window可能比Display Window大或小。我曾经在项目里遇到过一个EXR文件,显示正常但读像素数据时总报错,查了半天发现是Data Window设置错了。
我的建议:在Python里用OpenEXR库读写时,一定要检查header中的dataWindow和displayWindow。用exr.header()['dataWindow']和exr.header()['displayWindow']分别获取,确保它们一致,否则后续处理容易出bug。
3.2 Radiance HDR格式
Radiance HDR(.hdr或.pic)是另一个老牌格式,诞生于上世纪80年代,由Greg Ward开发。它比OpenEXR更轻量,但功能也相对简单。
它的核心编码方式是RGBE:每个像素用4个8位通道存储——R、G、B各8位,再加一个共享的指数(Exponent)通道。实际亮度值 = (R, G, B) × 2^(E - 128 - 8)。
你想想看,这种编码方式的好处是什么?
- 存储效率高:每个像素只占4字节,比OpenEXR的16位浮点(6字节/像素)更省空间。
- 动态范围够用:理论上支持10^76的亮度范围,实际应用中完全够用。
- 解析简单:文件结构就是文本头 + 二进制像素数据,解析起来非常快。
但缺点也很明显:
- 精度有限:RGBE只有8位尾数,在暗部区域容易出现条带(banding)。
- 不支持多层:一个文件只能存一层RGBE,没法像EXR那样存Z深度、法线等辅助通道。
- 色彩空间固定:Radiance HDR默认使用线性sRGB(其实更接近Rec.709原色),不能灵活指定色彩空间。
我记得有一次做环境光照采集,用单反拍了多张包围曝光照片,合成后存成.hdr格式。结果在渲染器里一用,发现暗部有明显的色阶断层。后来换成OpenEXR 16位浮点存储,问题就解决了。所以我的经验是:如果只是做简单的光照探针或背景图,.hdr够用;但如果要做精细的后期合成或调色,还是用EXR更稳妥。
3.3 HDR元数据
元数据是什么?就是「关于数据的数据」。在HDR图像里,元数据决定了你怎么正确解读像素值。
常见的HDR元数据包括:
| 元数据项 | 说明 | 典型值/范围 |
|---|---|---|
| 色彩空间(Color Space) | 定义RGB原色和白点 | sRGB, Rec.709, Rec.2020, ACEScg, DCI-P3 |
| 传递函数(Transfer Function) | 定义编码值与亮度的映射关系 | Linear, sRGB, PQ (ST 2084), HLG |
| 亮度范围(Luminance Range) | 最小/最大亮度值 | 0.0 ~ 65504.0 (FP16) |
| 白平衡(White Balance) | 色温或RGB乘数 | D65 (6500K), D55 (5500K) |
| 曝光信息(Exposure) | 拍摄时的曝光设置 | 光圈、快门、ISO |
| 自定义属性(Custom Attributes) | 用户自定义的键值对 | 如:cameraMake, lensModel, artist |
这里有个坑:很多软件在读取HDR图像时,会忽略或错误解析元数据。比如,你用一个线性色彩空间的EXR文件,但软件默认把它当成sRGB来显示,结果就是颜色完全不对。我曾经在项目交付时遇到过这个问题——渲染器读EXR时把线性数据当sRGB处理,导致整个镜头颜色偏灰。后来我们在EXR header里强制写入了chromaticities和whiteLuminance属性,才解决了问题。
避坑指南:在OpenEXR中写入自定义元数据时,建议使用标准属性名(如chromaticities、adoptedNeutral、whiteLuminance),而不是随便起名。非标准属性名可能被下游软件忽略。我曾经见过有人把白平衡信息写在myWhiteBalance里,结果Nuke和Photoshop都读不到。
3.4 色彩空间与色域
色彩空间这个话题,说大不大,说小不小。但在HDR流程里,它是个绕不开的坎。
简单来说:色彩空间 = 色域(Gamut)+ 传递函数(Transfer Function)+ 白点(White Point)。
常见的HDR色彩空间有:
- ACEScg:AMPAS(美国电影艺术与科学学院)制定的标准,色域覆盖整个可见光谱的绝大部分。我个人的主力工作空间。
- Rec.2020:ITU为4K/8K超高清电视制定的标准,色域比sRGB大很多。
- DCI-P3:数字影院标准,色域介于sRGB和Rec.2020之间。
- sRGB / Rec.709:老标准,色域小,但兼容性最好。
为什么HDR要关注色域?因为高动态范围 + 宽色域 = 更真实的视觉体验。你想想看,如果亮度范围上去了,但颜色还是sRGB那点可怜的范围,很多鲜艳的颜色(比如霓虹灯、夕阳)根本显示不出来。
来看一张我常用的色彩空间对比图:
从图上可以清楚看到:ACEScg > Rec.2020 > DCI-P3 > sRGB。色域越大,能表示的颜色越丰富。
但这里有个实际问题:你的显示设备能覆盖多少色域? 我见过很多团队在ACEScg空间里做渲染,但最终输出到sRGB显示器上,颜色直接「缩水」。所以我的建议是:在制作流程中保持宽色域(ACEScg或Rec.2020),在最终输出时根据目标设备做色域映射(Gamut Mapping)。
实用技巧:在OpenEXR中存储ACEScg数据时,建议在header中写入chromaticities属性,值为ACEScg的原色坐标(R: 0.713, 0.293; G: 0.165, 0.830; B: 0.128, 0.044; W: 0.32168, 0.33767)。这样下游软件就能正确识别色彩空间,避免颜色偏差。
3.5 格式选择与最佳实践
说了这么多,到底该用哪个格式?我整理了一个对比表:
| 特性 | OpenEXR | Radiance HDR |
|---|---|---|
| 位深 | 16位浮点 / 32位浮点 / 32位整数 | 32位(RGBE 8+8+8+8) |
| 动态范围 | 极高(FP16: 10^4.5, FP32: 10^76) | 高(10^76) |
| 精度 | 高(浮点,无条带) | 中(8位尾数,暗部有条带风险) |
| 多层支持 | 是(任意通道数) | 否(仅RGBE) |
| 压缩 | PIZ, ZIP, RLE, DWAA/DWAB | RLE(仅亮度编码) |
| 元数据 | 丰富(标准+自定义) | 有限(仅文本头) |
| 色彩空间 | 灵活(可指定任意色彩空间) | 固定(线性sRGB/Rec.709) |
| 文件大小 | 较大(但压缩后可控) | 较小(4字节/像素) |
| 兼容性 | 极好(VFX/动画/游戏行业标准) | 好(老软件/光照探针常用) |
我的个人建议是:
- VFX/动画/后期合成:用OpenEXR,16位浮点,PIZ压缩,ACEScg色彩空间。
- 环境光照/IBL探针:用Radiance HDR,简单快速,兼容性好。
- 实时渲染/游戏:考虑压缩后的EXR或专用格式(如BC6H)。
- 存档/交付:用OpenEXR,32位浮点,无损压缩,保留所有元数据。
一句话总结:OpenEXR是「瑞士军刀」,功能全面但稍重;Radiance HDR是「小刀」,轻便但功能有限。根据场景选,别盲目。
好了,HDR存储格式这部分就聊到这儿。记住:格式只是工具,真正重要的是你如何管理好里面的数据——亮度范围、色彩精度、元数据完整性,一个都不能少。