PBR material looks too shiny in three.js

I am trying to create a 3D street scene using a fancy PBR style material. I am able to get pretty close to the render provided by the 3D material that I purchased from CGAxis.

See this example:


My A-Frame / three.js version is on the left, the render provided by CG Axis is on the right

However the threejs / a-frame version is much shinier. Am I doing something wrong?

Here are glitch project links:
https://aframepbr.glitch.me/sphere.html
https://aframepbr.glitch.me/index-old.html (same thing but plane instead of sphere)
https://glitch.com/~aframepbr (includes link to source code)

Comparing different rendering engines does not always work as expected since shaders are implemented differently. Even when using the same environment map, lighting can vary quite noticeable.

One way to tweak the final visual appearance is modulating WebGLRenderer.toneMappingExposure. When using ACESFilmicToneMapping in the latest release (r118), a value of 0.5 or 0.75 maybe fits better to your use case.

3 Likes

That version of A-Frame is using Three.js 111dev. We upgraded the PBR code in 112. https://github.com/mrdoob/three.js/pull/18004

2 Likes

That version of A-Frame is using Three.js 111dev. We upgraded the PBR code in 112.

Yes you are right @mrdoob , that example was using 111.6. Here is a screenshot comparing 111.6 (right) to 116 (left) which is used by A-Frame latest

It is getting closer with those updates!

Comparing different rendering engines does not always work as expected since shaders are implemented differently. Even when using the same environment map, lighting can vary quite noticeable.

Thanks @Mugen87, yes agreed. Sounds like my next step would be to actually manually modify the roughness / metalness to reduce the “sheen” of the roadway surface, using the output in threejs as the “source of truth” for optimizing the texture map files.

One way to tweak the final visual appearance is modulate WebGLRenderer.toneMappingExposure . When using ACESFilmicToneMapping in the latest release ( r118 ), a value of 0.5 or 0.75 maybe fits better to your use case.

I’ll also try playing with this tone mapping exposure setting, I hadn’t known about modifying it before!

Side note — if you’re adding a metalnessMap to a material, be sure to also set metalness to 1. It defaults to 0, and (because it’s multiplied against the metalnessMap) will cancel the map out.

Screen Shot 2020-06-30 at 12.47.18 PM

2 Likes

Thanks @donmccurdy you’re right the metalness map wasn’t applying.

I’ve now incorporated everyone’s feedback and also did some Photoshop work on the metallic, roughness and diffuse maps to achieve the visuals I was intending. Here it is in its full glory: (original left, improved right)!

2 Likes

@donmccurdy @mrdoob shouldn’t that be by default 1.0 if metalnessMap is defined (checked the docs and it is the case for roughness and roughnessMap) ? Sounds like a major overlook in three.js design :flushed: Tbh, I had to double check just to be sure myself, and by some luck my code already had it set to 1.0 if metalnessMap is present, but it seems so easy to miss if someone doesn’t read the docs line by line.

(Edit: I realise setting it to 1.0 would spawn a 1000 issues and tearful complaints on github, but it kinda feels like a good idea to fix it now and avoid future users complaining about PBR not working at all.)

I think 0.0 is the right default, here. A fully-metallic material really needs an environment map, and as a default this would create more long-term problems than it solves. See https://github.com/mrdoob/three.js/issues/18151 for the reasons.

2 Likes

Yes, that’s true, but the default when metalnessMap is present should be reset to 1.0 imo. When someone applies a metalness map, I’d assume they’d expect it to become the source of truth - so if the metalness map is white, metal, if black, not a metal. Right now, if I understand, it will always be a nonmetallic surface, unless you go through the docs to find the note about default metalness always being 0.0 ?

I wouldn’t expect anyone to mix using maps with float values - you either go with one or another. And right now float defaults break maps.

Typically defaults are static. They don’t change automatically when another property is added.

We get a lot of questions from users who don’t realize their models require environment maps, whereas the issue described here is quite rare. Mostly because it’s unusual to assemble a full PBR material with textures programmatically — if you have PBR textures designed for your model’s UVs, you probably are going to bundle them into a model.

The behavior of factors multiplying against textures is actually pretty important — especially for base color, emissive, and AO.

Feel free to comment on #18151, if you feel strongly about this. It’s ultimately not my decision, but I still think the current choice is correct.

2 Likes

Another example – emissive defaults to #000000, which similarly disables emissiveMap unless it’s changed. We could change it to #FFFFFF, but nobody wants their materials to be 100% emissive by default. Materials have a lot of numeric properties, and (for better or worse) 1.0 is not the right default for everything.

1 Like

Thanks all for the feedback, just posted a little writeup on the topic including the Photoshop changes to the textures to achieve the desired visual effect:

I see - well, personally I’d stand against multiplying scalar and bitmap values, but if it’s only my pov and a case in my projects then I think it’s better to leave as-is - you’re probably right about that assembling PBR on the go is not as popular as I thought.