PBR Texture Packing


Ahh, thanks. Have overlooked that @RiccardoBlb mentioned it.

Yes,… just as roughness and metallic albedo RGB is combined into single albedo map, I combined roughness, metal, emissive RGB maps into single one. So that’s why its one channel in my case, which represents the emissive intensity.

I don’t think this is correct. There is never a case where 5 channels are combined into 3 in our engine.

metallic = 1 channel
roughness = 1 channel
albedo = diffuse = 3 or 4 channel

Yes, I agree with the above.

By the sentence:

The metal workflow uses an albedo map to define both the diffuse and specular content.
Insulators take diffuse from albedo, reflectivity color is white.
For metals the diffuse is black, reflectivity color is taken from albedo.

Thus metal workflow combines the following:

RGB for diffuse = 3 channel
RGB for specular = 3 channels

into =>
RGB albedo = 3 channel

Now in my case I combined:
RGB for diffuse = 3 channel
RGB for specular = 3 channels
RGB for emissive = 3 channels

into =>
RGB albedo = 3 channel

4 channels, diffuse rgb + spec map (1 channel)

Ok, here your describing the 2 extremes of the roughness map (insulater = rough, metals = smooth) ?

I’m not too sure what you mean here, but roughness will need to be a single channel of a texture, and so will metallic = 2 channels.

There is also no way to derive a roughness or metallic value from a 3 channel albedo or diffuse map.

reflectivity color should not be passed into a pbr shader ? This is something calculated based on the light probes.

Now when you say “combines”, what do you mean? how are you turning 9 channels into 3?


The following link explains Metalness Workflow in more detail:
PBR: The Problem with Greyscale Metalness — polycount

If you understand what Metalness value stands for, you will know it should have values 0 or 1. Eg. 0 for diffuse material, 1 for metal.

If metalness is 0, the pixel from albedo map is treated as diffuse color, and specular color of the material is set to white, if metalness is 1, the pixel form albedo map is treated as specular color and diffuse color is set to black.

While you can use metalness between 0-1, it is no longer physically based.

So metalness parameter acts as an optimisation, otherwise you would have to feed the shader: Diffuse Map(RGB) and Specular Map(RGB) of your material. Furthermore, it limits what materials you can make, however it makes the creation of material arguably easier and simpler.

The shader needs to know diffuse RGB, specular RGB, emissive RGB.

Given one RGB albedo and metalness(0-1), emissive(0-1).
If emissive = 1, I treat albedo as emissive RGB, diffuse is black,specular is black if metal = 0, white if metal = 1 (interpolated if between 0-1)

If emissive = 0, emissive its black and its just like your regular PBR shader as I stated above.

I was not describing roughness, but metalness, material is insulator when metalness = 0, and a metal when metalness = 1.

Rougness a value between 0-1, determines how rough a material is. Eg. for metal, rougness of 0 would yield mirror like reflection, increasing it would blurry them.
For diffuse material it may represent the interpolation between Lambert and Oren-Nayar shading.

I think the key here is that you are describing something other than a PBR shader and definitely something other than JME’s PBR shader.

That could be where the confusion was.

Unless I missed something because I skimmed over it, that forum post is detailing an extreme edge case of a grey metal material, and at great lengths, ultimately solving a virtually non-existent problem with hard coded solutions.

Applying knowledge taken from this post can not be applied to any PBR material other than a gray metal.

So I think we could include your workflow method as one of the popular schemes users could pick from (using @pspeeds idea of material param layout). Now that gray metal is sorted, you will just need to lock down how you are going to handle the other 99.98% of material types.

At some point I need to learn more about using texture packiing PBR textures and switching to DXT formats.

EDIT: … sorry about the out of context post. Reading this thread just showed me a hole in my knowledge that I should fill.

To clear the confusion, the PBR shader/library mostly stays the same, what we are talking about is just how to pack the inputs. As @pspeed said we can use material parameters to distinguish between gltf, unity, ue etc… Or use different materials. Or convert gltf/unity/ue maps to a single jME standard, eg gltf. Each of these, is pretty convenient so I do not have a preference here. But the first/second sounds more convenient to beginners.

Since I mentioned my custom PBR texture packing, this got little off topic as I tried to answer questions regarding the packing of emission RGB into albedo. I do not expect you to include my custom packing solution(but of course you can if you’d like to).

Sorry to keep going back to this, but I’m curious how your custom packing solution works ?

How does it pack 6 channels (albedo/diffuse x 3 + emission RGB x 3) into albedo (3 or 4 channels) ?

No problem, I guess I’ll explain it with code.

//my texture inputs: 
vec4 albedo = read albedo map (RGB + A alpha)
vec4 normalH = read normal map (XYZ + H height)
vec4 RMAE = read rmae map (rough, metal, ao, emission)

float emission = RMAE.a;//read from RMAE map

vec3 emissiveRGB = mix(vec3(0.0), albedo.rgb,emission);
vec3 emissiveRGB = mix(vec3(0.0), m_Emissive,emission);

So hopefully, this finally answers the question,if not feel free to ask for clarification. As you can see I obtain emissiveRGB from albedo map and emission parameter (E in RMAE map).

I hope you see its drawback also now: If emissive color is read from albedo map, the emissive parts of your material have the same diffuse/specular color as well.

However, if there is just a single emissive color for material, then you don’t need a texture for emissiveRGB, just a uniform + 1 channel where to apply it.

oooooh OK, I think I’m with you now.

It can be rearranged like so :

vec4 albedo = read albedo map (RGB + A alpha)
vec4 normalH = read normal map (XYZ + H height)
vec4 RMAE = read rmae map (rough, metal, ao, emission)

float emission = RMAE.a;//read from RMAE map

vec3  emissiveRGB = albedo.rgb; 

  emissiveRGB = m_Emissive; 

emissiveRGB *= emission;

The reasoning behind the rearrangement:

  • to avoid the overheads of 2 unnecessary mix calls;
  • to keep the code styling more in line with how things are done internally: set a baseline value, then overwrite or append as required; and
  • I also pulled the emission multiplication out of the branches so it’s only in one place = easier maintenance.
  • plus, although the glsl control flow allows for the double variable declaration like you’ve done, I don’t like using it that way, so I avoid it where I can. There is no performance or technical reason behind this decision.

tl;dr: it boils down to setting a flag for using diffuse (albedo) as the emission colour… something I have no need for, but that is why its jME and not jTheToucherE :wink:

Yes, finally you got it. The above was just psudo-code, but yes multiplication is better than mix.

I use it this way, since I think I will hardly need an emissive material with different diffuse/spec for my purposes. I then pack them into a single RGBA32UI, with space to spare for deffered purposes. But that’s for another topic.

I submitted a pull request that updates PBRLighting.frag as well as PBRLighting.j3md in the master branch to have an option to read the AO map from the red channel of the packed metallic roughness map.

I’m not great with github so it made two separate pull request, one for the frag file and one for the j3md file

I tested the updated shader with a packed ao map in the scene composer, and it appears to work and produce the same visual output when you use a standalone lightmap as the AO map