Texture Channel Splitter — Unpack RGBA / ORM into Grey Maps
A packed texture read through a browser canvas loses every byte under alpha 0. The Texture Lab decodes the PNG itself, so each channel comes out exactly as it was stored.
A packed texture keeps up to four unrelated grey maps in its R, G, B and A channels — an ORM texture holds occlusion, roughness and metallic in R, G and B; a Unity HDRP mask map holds metallic, AO, detail mask and smoothness. Splitting it means writing each channel to its own single-channel image without changing a byte. The Channel Unpacker reads the PNG itself, labels each channel for the layout you pick (HDRP, URP, glTF, Godot ORM, Unreal ORM), can invert a channel (smoothness ↔ roughness) and saves 8-bit grey PNGs that are byte-identical to the source, including under fully transparent texels.

- Channel PNGsMeasured· numpy
- Normal mapMeasured· numpy
- Packed mask / ORMMeasured· Pillow
- Engine importUNVERIFIED
What is inside a packed texture, and why the order matters
Each channel of a packed texture is an independent 0–255 number per texel, not part of a colour. Viewed as a picture it shows odd colours; only the layout tells you that G means roughness (glTF, Godot ORM), ambient occlusion (Unity HDRP) or occlusion (Unity URP). The file name rarely tells the order: _ORM, _ARM, _MaskMap and _packed all just say "packed", so the Lab classifies them all as a packed map and lets you choose the layout.
The alpha channel is where tools lose data. In a Unity mask map, a fully rough texel has smoothness 0, which is alpha 0. A browser canvas stores pixels premultiplied, so that texel's metallic, AO and detail bytes come back as 0. The Unpacker decodes PNG files with its own reader (all non-interlaced colour types, bit depths 1–16, tRNS) and writes each channel as a colour-type-0 PNG, one byte per texel, with no colour conversion.
Inverting is exact: the saved plane is 255 − value, and inverting twice returns the original bytes. That is how a Unity smoothness channel becomes the roughness another engine wants.
- Channel plane
- The w × h bytes of one channel, saved as a greyscale PNG.
- Premultiplied alpha
- Colour stored already multiplied by alpha; at alpha 0 every colour becomes 0, so packed bytes there are lost.
- Mask map
- Unity HDRP's packed texture: R metallic, G ambient occlusion, B detail mask, A smoothness.
- 16-bit input
- Accepted; the Lab keeps the high byte of each sample. Output is always 8-bit.
Example: one texel of an HDRP mask map
A fully rough metal texel with AO 200 and no detail mask. Its smoothness is 0, so its alpha is 0:
Source texel (crate_MaskMap.png) R 255 G 200 B 0 A 0
Read through a browser canvas R 0 G 0 B 0 A 0 (premultiplied: colour × 0)
Read by the Lab's PNG decoder R 255 G 200 B 0 A 0
Saved with the Unity HDRP layout, A inverted:
crate_MaskMap-r-metallic.png 255
crate_MaskMap-g-ao.png 200
crate_MaskMap-b-detail.png 0
crate_MaskMap-a-inverted-smoothness.png 255 (255 − 0: roughness)The inverted file keeps the channel's layout name (smoothness) with inverted- in front, so its content is roughness. Rename it if another tool picks maps by file name.
Which channel holds what, per layout
| Channel | Unity HDRP Mask Map | glTF 2.0 / Godot ORM | Unity URP channel-packed |
|---|---|---|---|
| R | Metallic | Occlusion | Metallic |
| G | Ambient occlusion | Roughness | Occlusion |
| B | Detail mask (HDRP only) | Metallic | Not used |
| A | Smoothness — invert for roughness | Not used | Smoothness — invert for roughness |
The Built-in Standard shader's metallic map reads only R (metallic) and A (smoothness). Nerulio's "Unity URP / Built-in" preset follows that page and labels G "ignored"; Unity's URP page reads occlusion from G when the same texture is also assigned to Occlusion.
What it does
- One 8-bit grey PNG per channel (R, G, B, A), byte-identical to the source.
- RGB under fully transparent texels is kept (checked on 2048 texels).
- Each channel previewed with its meaning in the chosen engine layout (Unity HDRP, URP, ORM).
- Invert a channel (e.g. roughness ↔ smoothness) and save the result.
How to do it
- Drop the packed PNG.
- Pick the engine layout to see what each channel means.
- Invert a channel if needed.
- Download the channel PNGs.
Files you get
| File | What it is for |
|---|---|
crate_MaskMap-r-metallic.png … -a-smoothness.png | One 8-bit grey PNG per channel (colour type 0), named file-channel-role; inverted- is added before the role when you inverted it. |
crate_MaskMap-channels.zip | All four channel PNGs in one archive (Save all four channels). |
Move a packed texture to another engine's layout
Example: a Unity HDRP mask map going to Godot 4 or glTF. The engine settings follow their documentation; Nerulio did not run the imports.
- Open the packed PNG and set Read channels as to the layout it came from (here Unity HDRP Mask Map). Check the channel stats: a channel with min = max is a constant filler.
- Click Invert on A so smoothness becomes roughness, then Save all four channels (ZIP).
- Godot 4 with separate maps: in a
StandardMaterial3Dassign the roughness, metallic and AO PNGs to their texture slots (enable Ambient Occlusion first). Each slot's*_texture_channeldefaults to Red, which a grey PNG satisfies. - Godot
ORMMaterial3Dor glTF: repack AO, roughness and metallic with the ORM preset in the mask packer (R occlusion, G roughness, B metallic). - Unity: import every grey PNG or repacked texture with
sRGB (Color Texture)off. URP and HDRP have no separate roughness slot, so pack metallic and smoothness again rather than using the planes one by one.
Check the result
- Repack the planes with the layout you unpacked them with and unpack the result: every channel should read the same min, max, mean and unique-value count as before (the Lab measured max difference 0).
- Open a saved PNG in any image tool: it is a single-channel 8-bit grey image at full resolution, not an RGB copy.
- For an inverted plane, a texel that read v before reads 255 − v after (the stage shows the in → out pair side by side).
Troubleshooting
| Symptom | Likely cause | How to check | Fix |
|---|---|---|---|
| A channel is black wherever the texture is transparent (in another tool) | That tool read the file through premultiplied alpha | Open the same PNG in the Channels stage: the bytes are there | Unpack in the Lab, and keep packed data out of tools that use a canvas |
| The file is marked "decoded by the browser (not byte-exact)" | JPEG or WebP input; JPEG compression also mixes channels | The file list and the report (exactChannels: false) | Unpack the PNG the texture was made from |
| The unpacked "AO" looks like roughness, or metallic looks noisy | The wrong layout is selected (G is AO in HDRP, roughness in glTF) | Switch Read channels as and compare the stats and previews | Pick the layout of the program that exported the texture |
| Roughness looks inverted in the target engine | The source channel was smoothness (Unity) and was not inverted | Its file name has no inverted- | Click Invert on that channel and save again |
| A 16-bit texture loses precision | Output is 8-bit; the high byte of each sample is kept | Bit depth in the report | Use a 16-bit capable editor when you need 16-bit planes |
Other ways to do it
| Option | When it is the better choice |
|---|---|
| Keep the texture packed | Godot's StandardMaterial3D can read each map from a chosen channel (roughness_texture_channel, metallic_texture_channel, ao_texture_channel), so no split is needed when the order is simply different. |
| An image editor's channel split | Opaque textures with no alpha-0 texels, when you are already in that editor; check the alpha area before trusting it with a mask map. |
| Roughness to smoothness | You only need one inverted grey map for Unity. |
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): a 64×64 ORM of random bytes unpacked with max difference 0 on R, G, B and A; all 2048 alpha-0 texels kept their RGB (an explicit (1, 2, 3, 0) corner came back with R = 1); the files re-opened in Pillow as mode L, 64×64; the inverted roughness plane equalled 255 − roughness exactly. The layouts are the engines' documented ones listed below (Godot's ORM order from its engine source); nothing was imported into an engine.
Official documentation used
Questions
Why not just open it in an image editor?
Many editors and every browser canvas premultiply alpha, so metallic/roughness bytes under transparent texels come back as 0. This path reads the PNG bytes directly.
Does it work with JPEG?
Yes, but JPEG is decoded by the browser and marked "not byte-exact".
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).