PBR Texture Packing

I’ve been toying with the pbr shaders and noticed that the AO texture isn’t packed with the metallic and roughness.

In the unreal documentation they outline their format as:

R = Metallic
G = Roughness
B = Ambient Occlusion

And in another UE article it’s:

R = Ambient Occlusion
G = Metallic
B = Roughness

Whereas ours is:

G = Roughness
B = Metallic

I’m not certain if there’s any kind of industry standard to the order then, but seems like packing AO in there is common practice.

I guess I should give @nehon a ping about it. if we just used the red channel it would avoid any issues for existing users. I’m not sure about how well one channel compresses vs the others or if that’s even taken into account either. Apparently DXT has a quirk where the green(?) channel compresses better or something. I’m no expert :confused:

“In the standard DXT compression, the Green channel has 1 extra bit compared to the R and B channels, which makes it 565. If you had an alpha to that, I believe it becomes 555+1.”

What about the height map? Is AO more important/commonly used?

Height map can already be packed with the normal map… which makes a ton more sense than packing it anywhere else.

Is the normal map supposed to be compressed into dxt5 then? Or be uncompressed and have one whole uncompressed channel for the height map?

IMO if the goal is saving memory, this is the layout that makes more sense

  • dxt1
    • RGB albedo
  • dxt1
    • R metallic
    • G rough
    • B height
  • dxt1
    • RGB emission
  • rgtc2
    • RG normal

Unless ao is used more than heigh of course.

AO should not be used for height ?

It is also used in the lighting calculations.

What about alpha in the albedo channel?

There is also:

  • SpecularMap
  • GlossinessMap

or the combined variant :

  • SpecularGlossinessMap

Is it worth thinking about using the 4th channel in each map?

I don’t think so, unless you have a special case where it can be used

Yeah, i see now that the new pbr version uses it to soften the lights, this might make it more worthy than the heightmap then, since you can’t get the same effect with ssao.

Maybe, but dxt3 and dxt5 are ~twice as big as dxt1

It sounds like this is the way the GLTF exporter does it as well, so maybe we would want to stay consistent with the way they handle packing the AO map https://github.com/KhronosGroup/glTF/issues/857#issuecomment-290530762

ouch, didn’t know this.

Make sure not to dismiss the importance of heighmaps, they are in displacement and parrallax to great effect. =)

Also for future proofing, 3 channels may been required for displacement at some point.

It seams vice versa . Based on what I read in different places :


I thought that was what LightMapAsAOMap toggle was for. Just use the lightmap slot?


Edit: What does the order matter as long as you pack them accordingly?

I could be wrong about this but I thought it was like this because the LightMap may have color so the -LINEAR flag couldnt be used like it would be with a AO map?

I also thought this was like this because people using the old system already usually had LightMaps and it would make using PBR easier to convert because they already had the assets.

I could be way off base though.

Still, if you use the same texture for AO as for metallic/roughness then it works… because it’s using the R channel for AO and the G, B channels for the others.

Well its clear that the GTLF file format was one of the factors in the wiring of textures and channels. Makes sense considering GTLF is our “go-to”. Not sure how this would break things if I submitted a PR. Would be nice to see what the author has to say first I guess.

It’s a little hacky but we could always create a material parameter to set the layout. 0 for gltf, 1 for Unity, 2 for UE… whatever. Make it a define in the shader.

I don’t know how ugly that makes the shader code but it would be the most inclusive way. Easier maybe with shader nodes.

In my custom PBR, I use three textures:

  1. Albedo (RGBA)
  2. Normal-height (XYZH)
  3. Rougness,Metal,AO,Emission (RMAE)

Thus it also includes Emission as well. And instead of making the shaders adapt to different maps, I made a converter which converts textures to this format.

Well the primary concern is that the blender -> GTLF -> Import workflow works - so we can’t just make up our own. We are at Khronos’s whim.

Just out of interest, how do you pack a color + intensity in a single channel? 8 bits per color (RGB) + 8 bits for intensity? It seems a bit of a nightmare to get photoshop/gimp to work with that flow.

I like the sound of this, we can implement the current best standards popular schemes, this should cover off basic and intermediate users, if a user needs to leverage different workflow/pipe line, I think it’s fairly safe to expect them to have the know how to change the shader themselves.

To make it simple there could be a ‘getMappedTextures’ or ‘configurePipeline’ method at the top of the shader, called at the start of main(), that maps values to texture channels using the specified pipeline, with a bare-bones ‘custom’ pipeline at the end for user input. I guess this mapping could even be passed to the shader as a CSV but I have put 0 thought into this idea.

Apps like Substance Painter let you choose which work flow you are working in (and when exporting), it only makes sense we look to these for inspiration.


I just wanted to point out the “Emission” texture component, since it was not yet mentioned in this thread, so as not to forget it.

Albedo is represented by RGB, A is for transparency.
Regarding your question what do you mean by intensity? I haven’t heard of intensity maps. In case you mean specular intensity map then, the albedo map contains both diffuse and specular.

Emission maps are usually 3 channels since you only listed one channel for it, he was wondering how you packed all 3 channels into 1. I think it is just a misunderstanding, we are assuming you knew emission was 3 channels and were doing some magic to get that data to fit into 1 channel, however, this is probably not the case.

1 Like