What Is OpenEXR?
OpenEXR is a high dynamic range (HDR) image file format developed by Industrial Light & Magic (ILM) — George Lucas's VFX company responsible for the original Star Wars effects — and released as open source in 2003. In 2021, the Academy Software Foundation (ASWF) adopted OpenEXR as an ISO/IEC standard (ISO/IEC 23082), cementing its status as the foundational image format for professional visual effects, 3D rendering, and color-graded cinematography.
The format's defining characteristic is its ability to store image data with linear floating-point precision across an effectively unlimited dynamic range — from the darkest shadow to a value representing the surface of the sun in the same image, without clipping or quantization artifacts. A standard JPEG or PNG maps the world into 256 brightness levels; an OpenEXR image can represent 65,504 distinct values per channel using 16-bit half-float, and over 3 billion with 32-bit float.
Why VFX Uses OpenEXR
The film VFX pipeline imposes requirements that consumer image formats cannot meet:
- Compositing requires pixel values to preserve physical energy relationships — adding light sources mathematically must produce correct results, not clipped white values
- Color space flexibility — OpenEXR files store scene-linear values; look-up tables (LUTs) and OpenColorIO (OCIO) configs are applied later at display time
- Multi-pass rendering — a single OpenEXR file can hold dozens of render passes (beauty, diffuse, specular, shadow, ambient occlusion, depth, motion vectors, cryptomatte IDs) as separate named channels
- Lossless round-trip — every encode/decode cycle must be numerically identical; JPEG's lossy compression is incompatible with compositing math
Every major film studio — Disney, Pixar, DreamWorks, Weta Digital, DNEG, ILM — mandates OpenEXR as the primary interchange format for VFX assets.
OpenEXR Data Types
Each channel in an OpenEXR file can independently use one of three data types:
| Type | Bits | Range | Use Case |
|---|---|---|---|
HALF |
16 | ±65,504; ~3 decimal digits | Color channels (R, G, B, A) — best balance of size and precision |
FLOAT |
32 | ±3.4×10³⁸; ~7 decimal digits | Depth, motion vectors, precise technical data |
UINT |
32 | 0–4,294,967,295 | Object IDs, sample counts, cryptomatte masks |
The HALF (16-bit IEEE 754 half-precision float) type is overwhelmingly used for RGB channels. It was invented specifically for OpenEXR — this format pre-dates widespread GPU support for fp16.
Channel Naming and Multi-Pass EXR
OpenEXR uses a dot-separated naming convention to organize multi-pass render data:
Standard channels:
R, G, B, A — beauty pass RGBA
Arbitrary render layers (part.channel format):
diffuse.R, diffuse.G, diffuse.B — diffuse shading
specular.R, specular.G, specular.B — specular highlights
shadow.R, shadow.G, shadow.B — shadow pass
AO.R, AO.G, AO.B — ambient occlusion
Z — depth (distance from camera, float32)
Pworld.X, Pworld.Y, Pworld.Z — 3D world-space position of each pixel
Nworld.X, Nworld.Y, Nworld.Z — surface normals in world space
motionvector.U, motionvector.V — 2D screen-space motion vectors
uv.U, uv.V — UV texture coordinates
cryptomatte00.R, cryptomatte00.G, — cryptomatte IDs for object/material masks
cryptomatte00.B, cryptomatte00.A
A single multi-pass EXR from a V-Ray or Arnold render might contain 30+ channels, all in one file.
OpenEXR Compression Modes
| Method | Type | Ratio | Use Case |
|---|---|---|---|
NONE |
Lossless | 1× | Maximum compatibility, archival |
RLE |
Lossless | ~1.5× | Run-length encoding; good for flat areas |
ZIPS |
Lossless | ~3× | ZIP compression per scanline |
ZIP |
Lossless | ~3–4× | ZIP over 16-scanline blocks |
PIZ |
Lossless | ~3–5× | Huffman variant tuned for visual data — most common for production |
PXR24 |
Lossy | ~4–6× | 24-bit log quantization; imperceptible loss |
B44 |
Lossy | fixed 4.5× | Half-float blocks; uniform compression |
B44A |
Lossy | variable | B44 with special case for flat regions |
DWAA |
Lossy | ~10–30× | DreamWorks wavelet; near-lossless at default quality |
DWAB |
Lossy | ~10–30× | DWAA with 256-scanline blocks (better parallelism) |
PIZ is the standard for VFX production — lossless, reasonably fast, excellent compression for photographic content. DWAA/DWAB is used when storage is at a premium and the minor perceptual difference is acceptable.
Reading OpenEXR with Python
OpenEXR Python bindings (official library):
import OpenEXR
import Imath
import numpy as np
# Open an EXR file
exr = OpenEXR.InputFile("render_beauty.exr")
header = exr.header()
print(f"Channels: {list(header['channels'].keys())}")
print(f"Data window: {header['dataWindow']}")
print(f"Compression: {header['compression']}")
# Read RGB channels (half-float type)
HALF = Imath.PixelType(Imath.PixelType.HALF)
dw = header["dataWindow"]
width = dw.max.x - dw.min.x + 1
height = dw.max.y - dw.min.y + 1
r_bytes = exr.channel("R", HALF)
g_bytes = exr.channel("G", HALF)
b_bytes = exr.channel("B", HALF)
R = np.frombuffer(r_bytes, dtype=np.float16).reshape((height, width))
G = np.frombuffer(g_bytes, dtype=np.float16).reshape((height, width))
B = np.frombuffer(b_bytes, dtype=np.float16).reshape((height, width))
rgb = np.stack([R, G, B], axis=-1) # shape (H, W, 3), float16
print(f"Image size: {width}×{height}")
print(f"R channel range: {R.min():.4f} – {R.max():.4f}")
# Read depth channel (float32)
FLOAT = Imath.PixelType(Imath.PixelType.FLOAT)
z_bytes = exr.channel("Z", FLOAT)
Z = np.frombuffer(z_bytes, dtype=np.float32).reshape((height, width))
imageio (simpler, for basic RGB):
import imageio
import numpy as np
# Read EXR as numpy array (requires imageio[exr] extra)
img = imageio.imread("render.exr", format="exr")
print(img.dtype) # float32
print(img.shape) # (H, W, 4) for RGBA
# Apply a simple exposure adjustment
img_exposed = img * 2.0 # +1 stop exposure
# Tone-map to 8-bit for display (simple Reinhard)
img_tm = img_exposed / (1.0 + img_exposed)
img_8bit = (np.clip(img_tm, 0, 1) * 255).astype(np.uint8)
imageio.imwrite("preview.png", img_8bit)
Multi-Part OpenEXR (OpenEXR 2.0+)
OpenEXR 2.0 introduced multi-part files — multiple independent image sets within a single .exr file, each with its own header, channels, and compression:
render_complete.exr
├── Part 0: "beauty" — RGBA, scanline, PIZ compression
├── Part 1: "depth" — Z float32, scanline, ZIP
├── Part 2: "normals" — XYZ float16, scanline, PIZ
└── Part 3: "cryptomatte" — RGBA uint32, scanline, NONE
This eliminates the need to manage dozens of separate EXR files per frame — a 4K film frame with 20 render passes fits in one EXR.
Deep Data: Volumetric Compositing
OpenEXR 2.0 also introduced deep compositing (DEEP_IMAGE type): instead of one RGBA value per pixel, deep EXR stores a variable-length list of samples per pixel, each with a depth range and color value. This enables:
- Compositing semi-transparent objects (rain, smoke, glass) without pre-multiplied alpha errors
- Correct depth-of-field compositing where a foreground object doesn't "bleed" onto a background object
- Volume rendering insertion into live-action scenes
Deep EXR files are much larger than regular EXR (10–50× per frame) and are only used in final-shot compositing, not intermediate renders.
OpenEXR in Rendering Software
Every major 3D renderer uses OpenEXR as its primary output format:
- Pixar RenderMan: native EXR output since 2002
- Arnold (Autodesk/Solid Angle): EXR with arbitrary AOVs
- V-Ray (Chaos): multi-layer EXR with separate render elements
- Blender Cycles / EEVEE: EXR via File Output node in compositor
- Unreal Engine: cinematic rendering outputs EXR sequences
- Redshift: DWAA-compressed EXR as default
- Octane: multi-pass EXR from render settings
The OpenEXR standard is maintained at https://openexr.com and the C++ library is part of VFX Platform, the industry-coordinated dependency specification used by all VFX studio pipelines.
Related conversions
Most teams that read this guide convert images in one of these directions: