Every material in a modern PBR pipeline requires multiple textures: Base Color, Normal, Roughness, Metallic, Ambient Occlusion, and often more. A single material can easily use 5-6 texture samples per pixel. In a scene with dozens of materials, that's hundreds of texture fetches per frame — and each one costs bandwidth, VRAM, and GPU cycles.
ORM texture packing is one of the simplest optimizations you can make, and it's standard practice at every professional studio. If you're not doing it, you're leaving significant performance on the table.
What Is ORM Packing?
ORM stands for Occlusion, Roughness, Metallic. It's a technique where you pack three grayscale textures into the R, G, and B channels of a single RGB texture:
| Channel | Data |
|---|---|
| Red | Ambient Occlusion |
| Green | Roughness |
| Blue | Metallic |
Since AO, Roughness, and Metallic are all grayscale (single-channel) data, they each only need one color channel. Storing them in separate files wastes two-thirds of each file's capacity. Packing them into one RGB texture uses the same total data but requires one texture sample instead of three.
Why It Matters for Performance
The performance impact is straightforward:
Fewer texture samples. A standard PBR material without packing needs: Base Color (1 sample), Normal (1 sample), AO (1 sample), Roughness (1 sample), Metallic (1 sample) — that's 5 samples. With ORM packing: Base Color (1 sample), Normal (1 sample), ORM (1 sample) — that's 3 samples. You've reduced texture fetches by 40%.
Less VRAM. Three separate 2K textures at 8 bits per channel: 3 files x 2048x2048 x 3 channels = approximately 36 MB uncompressed. One packed 2K ORM texture: 1 file x 2048x2048 x 3 channels = approximately 12 MB uncompressed. With GPU texture compression (BC7/ASTC), the savings carry through proportionally.
Better cache performance. GPUs read textures in blocks. When you sample one channel from an RGB texture, the adjacent channels are already in the cache. Sampling three separate textures means three separate cache fetches from different memory locations.
Fewer draw calls. Some rendering pipelines batch objects by material, and materials with fewer texture bindings are faster to set up.
In real-world scenarios, studios report 15-25% reduction in material rendering cost from channel packing alone — no visual quality loss, no shader changes, just reorganized data.
How to Create ORM Textures
Method 1: Manual Packing in Photoshop/GIMP
The conceptual process:
- Open your AO, Roughness, and Metallic textures
- Create a new RGB image at the same resolution
- Copy the AO grayscale data into the Red channel
- Copy the Roughness grayscale data into the Green channel
- Copy the Metallic grayscale data into the Blue channel
- Save as a non-sRGB format (PNG, TGA, or EXR)
In Photoshop:
1. Open all three source textures
2. Create new document (same dimensions)
3. Window > Channels
4. Select Red channel → Paste AO texture
5. Select Green channel → Paste Roughness texture
6. Select Blue channel → Paste Metallic texture
7. Save As → TGA/PNG (no color profile embedded)
In GIMP:
1. Open all three source textures as layers
2. Colors > Components > Compose
3. Select "RGB" compose type
4. Assign: Red = AO layer, Green = Roughness layer, Blue = Metallic layer
5. Export as TGA/PNG
Method 2: Command-Line with ImageMagick
For batch processing, ImageMagick handles this in a single command:
magick convert ao.png roughness.png metallic.png -combine orm.png
For an entire folder:
for dir in */; do
magick convert "${dir}ao.png" "${dir}roughness.png" "${dir}metallic.png" \
-combine "${dir}orm.png"
done
Method 3: Blender Compositor
If your textures are baked in Blender, you can pack them directly in the Compositor:
AO Image → Separate RGB → R ─┐
├→ Combine RGB → Output
Roughness Image → Separate RGB → R ─┤
│
Metallic Image → Separate RGB → R ─┘
Connect each grayscale source to the appropriate channel of a Combine RGB node, then render/save the result. This avoids the round-trip to an external image editor.
Method 4: Automated with One-Click PBR Bake
If you're working in Blender and want to skip the manual baking and packing steps entirely, One-Click PBR Bake handles the full pipeline — baking individual PBR maps from your shader setup and packing them into ORM textures ready for import into your target engine. It eliminates the multi-step process of baking each map separately, opening an image editor, and manually assembling channels.
Engine-Specific Requirements
Unreal Engine 5
UE5 natively expects ORM packing in its default material setup. When you import an ORM texture:
- Set the texture's Compression Settings to Masks (no sRGB) — this is critical. ORM is linear data, not color data.
- In your material, break the ORM texture into channels using a BreakOutFloat3Components node or channel masks.
- Connect: R to Ambient Occlusion, G to Roughness, B to Metallic.
UE5's default material template already has inputs designed for this. If you use Quixel assets, they ship with ORM textures by default.
The material setup in UE5:
ORM Texture Sample
├─ R → Ambient Occlusion pin
├─ G → Roughness pin
└─ B → Metallic pin
Important: Never set an ORM texture to sRGB in Unreal. sRGB applies a gamma curve that corrupts the linear data. Your roughness will look wrong, metallic transitions will be inaccurate, and AO will be too dark. Set it to Linear/Masks.
Unity (URP/HDRP)
Unity's approach differs by render pipeline:
HDRP uses a Mask Map with a different channel layout:
| Channel | Data |
|---|---|
| Red | Metallic |
| Green | Ambient Occlusion |
| Blue | Detail Mask |
| Alpha | Smoothness (inverted Roughness) |
Note that HDRP uses Smoothness (1 minus Roughness), not Roughness directly. You'll need to invert the Roughness channel before packing.
URP doesn't have a single packed input by default. You'll need to either use a custom shader or pack into the Metallic Alpha channel (Smoothness) plus separate AO.
# Invert roughness to smoothness for Unity HDRP (ImageMagick)
magick convert roughness.png -negate smoothness.png
# Pack HDRP Mask Map: R=Metallic, G=AO, B=DetailMask(white), A=Smoothness
magick convert metallic.png ao.png \
-size 2048x2048 xc:white smoothness.png \
-combine hdrp_mask.png
Godot 4
Godot's ORM material uses a different arrangement:
| Channel | Data |
|---|---|
| Red | Ambient Occlusion |
| Green | Roughness |
| Blue | Metallic |
This matches the standard ORM layout. In Godot's material editor, enable the ORM texture slot and assign your packed texture. The engine handles the channel splitting automatically.
Common Mistakes
Applying sRGB to packed textures. This is the number one error. Packed data textures must be imported as Linear/Non-Color in every engine and every DCC tool. sRGB gamma correction will corrupt your material values.
Mismatched resolutions. All three source textures must be the same resolution before packing. If your AO is 2K and your Roughness is 1K, resize the Roughness to 2K first (or preferably, bake all maps at the same resolution from the start).
Packing color data. Only pack grayscale data into channels. Base Color and Emissive are color data and must remain as separate RGB textures. Attempting to "pack" a color texture into a single channel destroys two-thirds of its information.
Forgetting to update materials after packing. If you pack textures but your material still samples the three separate files, you've gained nothing and added a texture. Remove the individual texture samples and replace them with channel-split references to the packed texture.
Over-compressing packed textures. When you apply texture compression (BC7, ASTC), the compressor treats the texture as a single image. Since ORM channels contain very different data, the compressor may introduce artifacts where one channel's sharp edges conflict with another channel's smooth gradients. Test your compressed ORM textures in-engine to verify quality.
Beyond ORM: Other Packing Strategies
ORM is the most common packing scheme, but the concept extends to any combination of grayscale data:
- RMA (Roughness, Metallic, AO) — Same data, different channel order. Some studios prefer this layout.
- MRAO (Metallic, Roughness, AO, Opacity) — Adds opacity in the alpha channel for masked materials.
- Normal + AO — Some pipelines pack AO into the Blue channel of a two-channel normal map (since tangent-space normals only use RG in BC5 compression).
The principle is always the same: if you have grayscale data consuming a full RGB texture, it can be packed into a channel. Just document your packing convention clearly so everyone on the team knows which channel holds which data.
Practical Checklist
Before shipping your packed textures, verify:
- All source textures are the same resolution
- Packed texture is saved as Linear (no sRGB/gamma)
- Channel assignment matches your engine's expected layout
- Material in-engine samples the packed texture with correct channel splits
- Compressed texture quality is acceptable (check in-engine, not just in the editor)
- Old individual texture files are removed from the project (don't ship unused assets)
ORM packing is one of those optimizations that costs nothing in visual quality while delivering measurable performance gains. Once you integrate it into your asset pipeline — or automate it with tools like One-Click PBR Bake — it becomes invisible. You just produce better-performing assets by default.