Channel Pack
Cram four grayscales into one RGB(A) texture
- red_image
- green_image
- blue_image
- alpha_image
- red_mask
- green_mask
- blue_mask
- alpha_mask
- image
- packing_info
Textures are expensive, so real engines pack multiple grayscale maps into one RGB(A) image and let the shader unpack them - that's how ORM (occlusion/roughness/metalness) and RMA (roughness/metalness/AO) textures work. x1ChannelPack is the node that builds those packed textures in your graph: it takes up to four grayscale sources and packs them into a single RGB or RGBA output. From MKRShift Nodes' Surface/Tech Art branch, it's the direct counterpart to x1ChannelBreakout - the docs even say so: break out what you need, pack what you want.
The pack is young (no impressions, no community footprint I found), but this node is as simple and well-scoped as the branch gets.
How it works
Each optional input - red_image, green_image, blue_image, alpha_image - is a grayscale source for that channel of the output; the node takes luma from each image. But there's a wrinkle that makes it more flexible: the matching mask inputs override the image inputs on the same channel. Wire a red_mask into red_image's slot and the mask wins, which means you can pack directly from MASK outputs (like the mask outputs from any of the pack's scalar-map nodes) without converting masks to images first. Unused channels fall back to fill_missing (0.0 default), and when output_mode is rgba, the alpha channel defaults to 1.0 if you didn't supply one.
Outputs are just two: image (the packed texture) and packing_info (a string telling you exactly which source each channel resolved to - the debug output that saves you from "why is my green channel black" sessions).
The workflow it slots into
The classic path: derive AO, roughness, and metalness maps from your base texture (the pack's x1AOFromHeight, x1RoughnessMap, x1MetalnessMap family), then pack all three into one ORM texture with this node. The author also lists mask atlases and shader-input consolidation. And if you need to pull channels out first, that's the breakout node's job.
Inputs that matter
output_mode-rgborrgba; decide this before wiring alphafill_missing- the fallback for empty channels; 0.0 gives black, but if you're packing into a mask slot that should read "none," a low value is your friend- The four
*_image/*_maskpairs - remember masks override images
Installing
ComfyUI Manager → search MKRShift Nodes, or:
cd ComfyUI/custom_nodes
git clone https://github.com/criskb/MKRShift_Nodes
Restart ComfyUI. No models, no new dependencies.
Where people get burned
- Masks overriding images. If you connected both an image and a mask on the same channel, the mask wins, silently. Check
packing_infoif a channel looks wrong. - Not checking
packing_info. It literally reports the resolved source per channel - read it before you export, because a texture packed with the wrong channel assignment is silent until it hits the renderer. - Inconsistent sizes across the four inputs - the node infers a reference shape and resamples to match, but feeding wildly different resolutions is how you get unexpected results.
If you're shipping a PBR set to a game engine or a renderer, this is the node that turns your derived grayscales into the one file the shader actually wants.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| output_mode | COMBO | 2 options: rgb, rgba | |
| fill_missing | FLOAT | 0.0000–1 | — |
| red_imageopt | IMAGE | — | |
| green_imageopt | IMAGE | — | |
| blue_imageopt | IMAGE | — | |
| alpha_imageopt | IMAGE | — | |
| red_maskopt | MASK | — | |
| green_maskopt | MASK | — | |
| blue_maskopt | MASK | — | |
| alpha_maskopt | MASK | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| packing_info | STRING | — |