ORM & Mask Map Packer — Channel Pack Textures for Unity, Godot, glTF
Put AO, roughness, metallic and more into the channels your engine reads. Each preset states where its channel order comes from, and the PNG is written by the Lab itself, so bytes survive alpha 0.
Channel packing puts separate grey maps into the R, G, B and A of one PNG in the order your engine reads: Unity HDRP Mask Map = R metallic, G ambient occlusion, B detail mask, A smoothness; Unity URP channel-packed texture = R metallic, G occlusion, A smoothness; ORM for glTF 2.0 and Godot 4 = R occlusion, G roughness, B metallic. Drop up to four same-size grey maps, pick a preset or map each channel yourself, tick Invert where the engine wants smoothness instead of roughness, and download …-mask.png. The packed bytes were measured; import into an engine was not run.

- Channel PNGsMeasured· numpy
- Normal mapMeasured· numpy
- Packed mask / ORMMeasured· Pillow
- Engine importUNVERIFIED
How the packer fills each channel
Each input is read as a grey value per texel: its Rec. 709 luminance, 0.2126 R + 0.7152 G + 0.0722 B, rounded. For a real grey map (R = G = B) that is exactly the stored value, so a grey byte goes into its channel unchanged. A coloured image would be mixed down to luminance, which is rarely what you want for a data map.
Presets only choose where inputs go. Inputs fill the meaningful channels in the order you dropped them; a channel the layout ignores is written as 0 and an unused alpha as 255, so the file stays opaque. Nothing is inverted automatically: Unity's smoothness is 1 − roughness, so if you hold a roughness map you tick Invert on the smoothness channel (255 − value).
Alpha is data here. In an HDRP or URP texture, smoothness 0 means alpha 0. The packer writes the PNG with its own encoder (32 rows at a time), not through a canvas, so the metallic and AO bytes under those texels survive. Its inputs are read by the browser's image decoder, so feed it plain grey PNGs rather than files with transparency.
Import it as data. Unity's HDRP page asks you to disable sRGB (Color Texture) and use Texture Type Default for the mask map, and URP's page asks for sRGB off on its channel-packed texture; glTF 2.0 requires metallic and roughness values to be linear.
- Mask map
- Unity HDRP's packed texture: R metallic, G AO, B detail mask, A smoothness.
- ORM
- Occlusion, roughness, metallic in R, G, B — glTF 2.0's order, also Godot's ORMMaterial3D.
- Constant channel
- A channel written as 0 or 255 everywhere because the layout needs no map there.
- Invert
- 255 − value, the 8-bit form of 1 − x; turns roughness into smoothness and back.
Example: the same three maps in three layouts
Three grey maps of one texel: AO 10, roughness 80, metallic 220. The mapping line is how the packer shows it (number = dropped file, ⁻ = inverted):
ORM (glTF 2.0, Godot 4) files: ao, roughness, metallic R←1 · G←2 · B←3 · A←255 → (10, 80, 220, 255)
Unity HDRP mask map files: metallic, ao, roughness R←1 · G←2 · B←0 · A←3⁻ → (220, 10, 0, 175)
Unity URP packed files: metallic, ao, roughness R←1 · G←2 · B←0 · A←3⁻ → (220, 10, 0, 175)
smoothness = 255 − 80 = 175 (1 − 0.314 = 0.686)
Measured in the Lab: ORM preset (10, 80, 220, 255); custom mapping (220, 10, 80, 0); R inverted (245, 80, 220, 255)The HDRP and URP rows are identical here because there is no detail mask; HDRP would read B as detail mask, URP ignores it. With the HDRP preset and only three files dropped, the third file lands in B, so set B to 0 and A to file 3 with Invert, as shown.
Where each grey map goes
| Grey map you have | Unity HDRP Mask Map | Unity URP channel-packed | glTF 2.0 / Godot ORM |
|---|---|---|---|
| Metallic | R | R | B |
| Ambient occlusion | G | G (read when the texture is also assigned to Occlusion) | R |
| Roughness | A, inverted (smoothness) | A, inverted (smoothness) | G |
| Detail mask | B | — | — |
| No map for a channel | Constant 0 (e.g. B without detail) | B = 0 | A = 255 (unused) |
Nerulio's "Unity metallic + smoothness" preset follows the Built-in Standard shader page (G and B unused) and writes G = 0. For URP with occlusion, choose Custom and map the AO file to G.
What it does
- Presets: Unity HDRP mask (M, AO, detail, smoothness), URP metallic (M, –, –, smoothness), ORM (glTF 2.0, Godot 4).
- Custom mapping per channel, constant 0/255, and per-channel inversion (roughness → smoothness).
- Written as PNG by the Lab (not a canvas), so RGB under alpha 0 is kept.
- Each preset links the documentation or engine source its order is taken from.
How to do it
- Drop the grey maps.
- Choose a preset or map the channels by hand.
- Invert channels where the engine expects the opposite.
- Download the packed PNG.
Files you get
| File | What it is for |
|---|---|
rock_metallic-mask.png | The packed texture, named after the first file you dropped: 8-bit RGBA PNG, RGB kept where alpha is 0. |
rock_metallic-mask-channels.zip | Optional check: open the packed file in the channel unpacker and save its four planes to compare with your inputs. |
Use the packed texture in the engine
Steps from the engines' documentation; Nerulio did not run these imports.
- Unity HDRP: import the PNG, disable
sRGB (Color Texture), set Texture Type toDefault, and assign it to Mask Map under Surface Inputs of the HDRP Lit material. Metallic, Smoothness and AO Remapping sliders appear once a mask map is assigned. - Unity URP: import with
sRGB (Color Texture)off, set the Lit material's Workflow Mode toMetallic, assign the texture to Metallic Map and to Occlusion Map, and keep Smoothness › Source atMetallic Alpha(the default). - Godot 4: create an
ORMMaterial3Dand assign the ORM PNG as its ORM texture. AStandardMaterial3Dcan read the same file by settingao_texture_channelto Red,roughness_texture_channelto Green andmetallic_texture_channelto Blue. - glTF 2.0: reference the ORM image as the material's metallicRoughness texture and, if it carries occlusion, as its occlusion texture too; base colour stays a separate sRGB image.
Check the result
- Open the packed PNG in the channel unpacker with the same layout: each plane should match the grey map you put there (the capability check measured byte-identical channels on ambientCG grey maps), and inverted channels read 255 − value.
- A constant channel shows min = max (0 or 255) in the channel stats.
- In the engine, a rough area should look dull and a polished one sharp; if it is the other way round, the smoothness channel was not inverted.
Troubleshooting
| Symptom | Likely cause | How to check | Fix |
|---|---|---|---|
| Rough areas look polished in Unity | Roughness went into alpha without Invert | Unpack A: it equals the roughness map instead of 255 − roughness | Tick Invert on A and pack again |
| A map landed in the wrong channel | Inputs fill channels in the order they were dropped | The mapping line (e.g. R←1 · G←2 · B←3 · A←255) under the preview | Pick the right file in each channel's drop-down |
| "All channel inputs must have the same dimensions" | One grey map has another size | The file list shows each size | Export every map at the same size first |
| URP material goes dark in ambient light | The texture is assigned to Occlusion, and the Unity preset wrote G = 0 | Unpack G: min = max = 0 | Map the AO file to G with Custom, or leave Occlusion Map empty |
| Everything too glossy or too matte after import | The packed texture was imported with sRGB on | Unity: the texture's sRGB (Color Texture) checkbox | Turn sRGB off; the values are data |
| A channel has unexpected values | A coloured input was reduced to its luminance | The input is not grey (R, G, B differ) | Unpack the coloured source to its real channel first, then pack that plane |
Other ways to do it
| Option | When it is the better choice |
|---|---|
| Godot's channel selectors, no packing | Godot only: StandardMaterial3D reads each map from a chosen channel of one texture, so an existing packed file in another order needs no repacking. |
| Your texturing application's export preset | The maps come from a painting tool that can export in the engine's layout directly; Godot's docs name Substance Painter and ArmorPaint (Unreal Engine preset) for ORM. |
| Shader Graph (Unity) | Your textures use a layout no preset covers; Unity's URP page points to Shader Graph for channels packed differently. |
Outputs and how each was checked
"Measured" means the downloaded file was opened again outside the page (Pillow, numpy or an independent parser) and the property was measured. Loading these files in a game engine was not tested.
| Target | Files | Check |
|---|---|---|
| Channel PNGs | <name>-r/g/b/a.png (8-bit grey) | Measured numpyByte-identical per channel, including RGB under fully transparent texels. |
| Normal map | PNG · OpenGL (+Y) or DirectX (−Y) | Measured numpyDecoded vectors are unit length (worst deviation 0.0002); OpenGL and DirectX differ in green only. |
| Packed mask / ORM | Unity HDRP mask · URP metallic · ORM (glTF, Godot) | Measured PillowPacked bytes checked: ORM preset over AO 10 / roughness 80 / metallic 220 → (10, 80, 220, 255). |
| Engine import | Unity · Unreal · Godot | UNVERIFIEDNot imported in any engine. |
Evidence
Channel unpack byte-identical on all four channels (max difference 0), 2048 transparent texels keep their RGB; edge bleed changed 448 texels, all with alpha 0, and no alpha byte; OpenGL ↔ DirectX differ exactly in green (255 − g); converted normals decode to unit vectors.
Limits
- Exact channel bytes need PNG; JPEG/WebP are decoded by the browser and marked "not byte-exact".
- Interlaced PNG and 16-bit output are not supported; up to 33.5 million pixels on the exact path.
- A normal map's convention cannot be detected from its pixels, only converted.
- Nothing was imported into Unity, Unreal or Godot: channel orders rest on their documentation and engine source.
Versions and sources
Measured on the Texture Lab (docs/TEXTURE-LAB.md): the ORM preset over AO 10 / roughness 80 / metallic 220 wrote (10, 80, 220, 255) from inside the Lab and from this page; a custom mapping gave (220, 10, 80, 0) and an inverted R (245, 80, 220, 255). The browser capability check packs three ambientCG grey maps and finds each channel byte-identical to its input. The layouts come from the documentation below (Godot's ORM order from its engine source); no engine import was run.
Official documentation used
Questions
Is the Unreal ORM order official?
Epic does not document an R=AO, G=roughness, B=metallic order; the ORM preset follows glTF 2.0 and is labelled that way.
Was it imported into the engines?
No. The packed bytes were checked, e.g. (10, 80, 220, 255) for the ORM preset; import into Unity, Unreal or Godot is not tested.
Is it free, and do I need an account?
Everything on these pages is free and works without an account. There is no watermark and no export that is held back.
Are my sprites uploaded?
No. The Studio decodes, packs and exports in this browser tab. Projects are autosaved in the browser (IndexedDB) and can be saved as a .nerulio file.
Does it need Aseprite or an engine plugin installed?
No. .aseprite files are read and written in the browser, and each engine bundle carries what that engine needs (for Godot, the import settings and scene; for Unity and the Godot TileSet, a small editor script).