Texture Lab

Check a PBR texture set, then fix normals, channels and edges for your engine.

A PBR material is several textures that have to agree: the same size, each map in the slot that expects it, colour maps imported as sRGB and data maps (normal, roughness, metallic, AO, height) as linear, packed channels in the order your engine reads, and a normal map in the engine's green-channel convention. Drop one material's maps into the Texture Lab: it sorts them by file name, checks the set against the workflow you choose (separate maps, Unity HDRP mask, Unity URP metallic, Godot ORM, Unreal ORM) and writes a JSON report with the number behind every issue. Its files are measured byte for byte; import into Unity, Unreal or Godot was not run.

Drop image files hereProcess many files at once
No upload · processed on this device

What a PBR set has to get right before import

Colour or data. Base colour (albedo) and emission are pictures authored in sRGB; the engine decodes them before lighting. Roughness, metallic, ambient occlusion, height and normal maps are numbers and must reach the shader unchanged, so they are imported as linear (non-colour) data. glTF 2.0 makes this normative: base colour RGB MUST use the sRGB transfer function, the metallic and roughness values MUST be linear. In Unity the switch is the importer's sRGB (Color Texture) checkbox; Godot's shading docs say colour textures get a source_color hint and normal, roughness, metallic and height textures do not.

Packing. To save texture samples, engines read several grey maps from the channels of one RGBA image, and every engine picks its own order: Unity HDRP's Mask Map is R metallic, G AO, B detail mask, A smoothness; URP's channel-packed texture is R metallic, G occlusion, A smoothness; glTF 2.0 reads roughness from G and metallic from B, occlusion from R (so one image can serve as "ORM"); Godot's ORMMaterial3D uses the same R/G/B order, which the Godot 4.4 shader source states and the manual does not. Unity stores smoothness, which is 1 − roughness.

Normals. Unity and Godot expect OpenGL-style normal maps (green = +Y, up). A DirectX-style map differs only in its green channel; the fix is 255 − g, not a re-bake. A single map cannot be measured to tell which one it is, so the Lab converts but does not guess.

Why measure in the Lab rather than a canvas: a browser canvas stores pixels premultiplied, so every texel with alpha 0 comes back as (0, 0, 0, 0) and the packed bytes under it are gone. The Lab decodes PNG files itself (all non-interlaced colour types, 1–16 bit) and marks JPEG/WebP inputs "not byte-exact".

sRGB / linear
How the engine interprets the 0–255 values: sRGB values are decoded as colour (128 → about 0.216), linear values are used as they are (128 → 0.502).
Packed (channel-packed) texture
One RGBA image whose four channels hold four unrelated grey maps.
ORM
Occlusion in R, roughness in G, metallic in B — the glTF 2.0 channel order, also read by Godot's ORMMaterial3D.
Smoothness
Unity's inverse of roughness: smoothness = 1 − roughness (255 − value in 8 bits).

Example: an ambientCG brick set checked against three workflows

Four 1024 × 1024 PNGs from one ambientCG set. The roles come from the file names; the issues are what the Lab's validator returns (the roughness file is assumed saved as RGB with three equal channels):

File                                     Role       Import as
Bricks076C_1K-PNG_Color.png              albedo     sRGB
Bricks076C_1K-PNG_Roughness.png          roughness  linear
Bricks076C_1K-PNG_AmbientOcclusion.png   ao         linear
Bricks076C_1K-PNG_Displacement.png       height     linear

Separate maps (glTF / generic)   warn  missing-recommended  normal, metallic
                                 info  gray-stored-as-rgb   Roughness.png (R = G = B)
Unity HDRP mask map              error missing-required     mask map
                                 warn  missing-recommended  normal
Godot 4 ORM                      warn  missing-recommended  normal, orm

The set is fine for separate maps once you add the normal map (ambientCG ships _NormalGL for Unity and Godot and _NormalDX; add only one, or the Lab warns about a duplicate role). For HDRP the mask map is an error because HDRP has no separate metallic or AO slot; there is no "pack available" hint here because the set has no metallic map — a metallic of 0 has to come from a constant channel in the mask packer.

Files you get

FileWhat it is for
texture-report.jsonThe check report: per texture its role, size, power of two, exact PNG channels, colour type, bit depth, srgbChunkPresent, colour-space guidance, alpha and normal measurements; every issue with its id, level and numbers.
rock-channels.zipFrom the Channels stage: rock-r-ao.png, rock-g-roughness.png … one 8-bit grey PNG per channel, byte-identical to the source.
rock-mask.pngFrom the Pack stage: grey maps packed in the chosen engine layout.
rock-directx.png / rock-normal.pngFrom the Normal stage: a converted map, or a normal map generated from height.
nerulio-textures.zipFrom Export: every file resized (optional), edge-bled (optional) and encoded, in that order.

Use the checked set in Unity, Godot or glTF

Settings below follow the engines' documentation; Nerulio did not run these imports.

  1. Unity: select every data texture (roughness, metallic, AO, mask map) and turn off sRGB (Color Texture) in the Inspector; keep it on for base colour and emission. HDRP additionally asks for Texture Type Default on the mask map.
  2. Unity normal maps: set Texture Type to Normal map. If the file is DirectX-style, tick Flip Green Channel in the same importer (Unity 6) or convert the file first.
  3. Unity URP Lit (Workflow Mode Metallic): assign the packed texture to Metallic Map and to Occlusion Map, and leave Smoothness › Source on Metallic Alpha. HDRP Lit: assign the mask map to Mask Map; the Metallic, Smoothness and AO Remapping sliders then appear.
  4. Godot 4: use an ORMMaterial3D for an ORM texture, or a StandardMaterial3D with separate maps. A packed texture of another layout still works in StandardMaterial3D: set roughness_texture_channel, metallic_texture_channel and ao_texture_channel to the channel that holds each map.
  5. Godot import: for a DirectX-style normal map enable Process › Normal Map Invert Y in the Import dock and click Reimport.
  6. glTF: put roughness in G and metallic in B of the metallicRoughness texture, occlusion in R (the same image may be referenced as the occlusion texture), and keep the normal map +Y up.

Check the result

  • Drop the processed files back into the Lab and choose the same workflow: the report should show 0 errors, and the Channels stage should show each channel with the min/max you expect (a constant channel reads min = max).
  • In the engine, look at a surface you know: a polished metal area should give a sharp highlight and a rough stone area a broad dull one. Everything glossy or everything matte usually means inverted roughness or a data map imported as sRGB.
  • Put a light above the material: raised details should be lit on their upper edge. Lit from below means the normal map is in the other convention.

Troubleshooting

SymptomLikely causeHow to checkFix
The whole material is too glossy or too matteA data map was imported as sRGB (a value of 128 is read as about 0.216), or roughness sits where smoothness is expectedImporter: is sRGB (Color Texture) on for the roughness, metallic or mask map? Unpack the channel and compare it with the sourceTurn sRGB off for data maps; invert roughness for Unity with roughness → smoothness
Metal or AO shows up in the wrong places after packingThe texture was packed in another engine's layout (glTF's G is roughness, HDRP's G is AO)Open the packed file in the Channels stage with each layout preset and read the channel statsRepack with the right preset in the mask packer
Bumps look like dents, or light seems to come from belowThe normal map is in the other green-channel conventionLight from above: which edge of a bump is bright?Convert with the normal map converter, or use Unity's Flip Green Channel / Godot's Normal Map Invert Y
URP material turns dark in its ambient light after assigning the packed texture to OcclusionURP reads occlusion from G; Nerulio's URP preset writes G = 0Channels stage: G reads min = max = 0Do not assign it to Occlusion, or pack AO into G with a custom mapping
Dark or light fringe around cut-out edges (leaves, decals)RGB under alpha 0 is averaged into the edge by filtering and mipmapsFix › Mipmaps preview at 1/4 … 1/16Bleed the edge colour with texture edge bleed
dimension-mismatch errorOne map was exported at another resolutionThe issue lists every file with its sizeExport all maps at one size; the Fix stage's power-of-two resize uses a quality resampler if you must resize

Other ways to do it

OptionWhen it is the better choice
The engine's own texture importerOne or two maps whose layout you already know: you set sRGB, Texture Type and the normal-map flip there anyway, and no extra files are made.
Your texturing application's export presetThe maps come from a painting tool: exporting straight into the engine's layout avoids a separate packing step. Godot's docs note that Substance Painter and ArmorPaint export ORM with their Unreal Engine preset.
PBR texture validatorYou only need the checklist (names, sizes, missing maps) and the JSON report, not the conversion stages.

What this page does not cover

  • The material preview is a one-light GGX approximation with flat ambient; judge lighting in the engine.
  • The Unreal ORM preset follows the glTF order, which Epic does not document as Unreal's own.

Versions and sources

Measured on the Texture Lab (docs/TEXTURE-LAB.md): channel unpack byte-identical on R, G, B and A (max difference 0) with 2048 alpha-0 texels kept; the ORM preset over AO 10 / roughness 80 / metallic 220 wrote (10, 80, 220, 255); OpenGL ↔ DirectX differed exactly in green; the check report's alpha.zeroPixels matched the fixture. Import into Unity, Unreal or Godot was not attempted: the layouts come from the documentation below, and Godot's ORM order from its 4.4 engine source, read directly.

Official documentation used

Texture Lab

Check a PBR texture set, then fix normals, channels and edges for your engine.

How it works

  1. Add images
  2. Adjust settings and review the result
  3. Download a new file

Supported formats

PNG / JPG / WebP / AVIF / HEIC → PNG / JSON / ZIP — Input decoding depends on browser support and optional engines.

What this tool does

Check a PBR texture set, then fix normals, channels and edges for your engine.

Implementation and verified quality

advanced · raw PNG channel reader / Sobel-Scharr normals / RNM combine / WebGL2 preview. Quality qualification is in progress. Pro/Flagship quality has not been established.

Before you export

Not an engine renderer: the preview is a one-light approximation, and colour space is guidance only — no profile is detected.

Frequently asked questions

What are this tool’s limitations?

Not an engine renderer: the preview is a one-light approximation, and colour space is guidance only — no profile is detected.

Are my files uploaded?

This tool processes the files you open on your device without uploading them. Images you explicitly attach to community posts are uploaded separately. Optional engines and models are downloaded from third parties; those requests expose connection information such as your IP address.

Will my work be saved automatically?

No. Images and editing state stay in this tab; media output can use local temporary storage. Changing language preserves your work; reloading, closing the tab, or following a link to another page can clear it.