three.js
three.js copied to clipboard
Blender / GLTF: Add Scene.environment intensity
Playing around with Blender HDRI workflows - and it looks seems there's no reasonable way of lowering the environment map intensity, to use it only as a supporting light (not that it's a vital feature - but a tiny missing link between Three and Blendor / glTF IBLs ref.)

Since scene.environment
is a "global" setting - would it make sense to add scene.environmentIntensity to uniforms
- overriding .envMapIntensity
if it is not defined? A bit ugly, but it'd be following the way envMap is currently inherited from the Scene.
(A cleaner way may probably be to split scene.environment
as = { map: [IBLTexture], intensity: [Number] }
and add backwards-compatibility in the setter?)
(A way cleaner way would be probably to add .intensity
directly as a new property of a Texture to generalise it - since there's already quite a few intensity-based textures in PBR materials - but that'll probably not be super backwards-compatible?)
Can't we use the A channel for intensity? Opacity and intensity are quite related things for textures.
I think it's better for asking users to change the envMapIntensity
of the material instead of introducing a texture or scene property that overwrites it. Otherwise devs could request the same for all other intensity types (aoMapIntensity
, lightMapIntensity
) which only introduces an unnecessary complexity.
In the game I am working on, it's not really feasible to adjust the envMapIntensity
of individual materials. I have materials coming from many different sources - some loaded from GLTF models, some are created programmatically. Some materials are owned by the terrain subsystem, the scenery subsystem, the actor subsystem and so on, and in many cases the reference to a given material may be buried deep within a complex data structure. Creating an iterator that is able to visit all materials, while remaining leak-free (that is, not holding on to references to materials after they are no longer needed) would be a challenge.
An easy way to make a global envMapIntensity
backwards compatible would be to set the default global intensity to 1, and then make the actual intensity the product of the global setting and the per-material setting. This avoids the need to check for null / undefined during processing, it's just a multiply.
Also, I think the slippery-slope argument doesn't hold, because there isn't a global aoMap setting. The environment
property is special because it's a property of the scene - other map types don't have a global settings (other than background).
Now that we have backgroundIntensity
(https://github.com/mrdoob/three.js/pull/24876) maybe it does make sense to add environmentIntensity
too...
/cc @WestLangley
I can add that if it is desired.
Thing is, regarding scene.environment
, the docs say:
However, it's not possible to overwrite an existing texture assigned to MeshStandardMaterial.envMap.
So, in that regard, would it be best to have scene.environmentIntensity
apply only to scene.environment
, and mesh.envMapIntensity
apply only to mesh.envMap
?
From my perspective, that's probably how I would use it - that is, I would only use the global intensity with the global map, and per-material intensity with per-material maps.
However, that's not 100% backwards compatible, since there could be existing users that set the global map, but per-material intensity. That's really a policy question, on which I have no opinion.
@WestLangley
So, in that regard, would it be best to have
scene.environmentIntensity
apply only toscene.environment
, andmesh.envMapIntensity
apply only tomesh.envMap
?
That makes sense to me.
I can add that if it is desired.
Thing is, regarding
scene.environment
, the docs say:However, it's not possible to overwrite an existing texture assigned to MeshStandardMaterial.envMap.
So, in that regard, would it be best to have
scene.environmentIntensity
apply only toscene.environment
, andmesh.envMapIntensity
apply only tomesh.envMap
?
i think this is too big of a breaking change, but also limiting. the setting should imo affect either the global or the local, whichever env map is used in the end. if they're all factors there's no conflict: environmentIntensity = 0.5 * mesh.envMapIntensity = 0.1 === 0.05.
otherwise, if you wanted a single object to reflect less and use a global env map, which most likely is the majority use case, then it wouldn't work no more, but it works now and is a very useful and expressive feature.
personally, i have hundreds of sandboxes where i use scene.environment + individual mesh.envMapIntensity.
personally, i have hundreds of sandboxes where i use scene.environment + individual mesh.envMapIntensity.
Oh...
So scene.environmentIntensity * material.envMapIntensity
then?
That should be backwards compatible and also like material.color
and material.map
work together.
scene.environmentIntensity * material.envMapIntensity then?
After trying to implement this API, it seems weird to me...
The environment map is either scene.environment
OR mesh.material.envMap
-- one or the other.
Yet, the intensity is the product of scene.environmentIntensity
AND material.envMapIntensity
-- even if scene.environment
is null
.
So, in that regard, would it be best to have scene.environmentIntensity apply only to scene.environment, and mesh.envMapIntensity apply only to mesh.envMap?
I think so, yes. It is also very clear.
If users want to have a different environment intensity for each mesh, then they can set envMap
per mesh.
How does that sound?
I can work with either of those approaches.
My primary use case here is day / night cycling in the game world. Currently I have the directional light (sunlight) as well as the ambient lights attenuated and tinted based on the time of day. However, since the global environment map never changes, things can look odd at night, depending on what type of shader is being used. Being able to set the environment intensity globally would fix this issue.
I don't use per-object environment maps at all, so I would only ever adjust the global intensity - it's not feasible to adjust the individual materials since there are so many in different places.
However, I'm not just thinking about my use case, but what other users might want. The current behavior is that the environment intensity setting on the material affects both global and per-mesh environment maps. The question is, how many existing users currently depend on the ability to be able to set the environment map globally, but be able to adjust the intensity locally?
One idea - a variation on what was proposed above - would be to have the global environment map intensity initially set to undefined
. If the intensity is set to a number, then it behaves as you have suggested: local env maps are affected only by local intensity, and global env maps are only affected by global intensity. However, if the global intensity is undefined, then local intensity setting affects both.
In other words, the calculation for global intensity becomes:
const intensity = scene.envMapIntensity ?? material.envMapIntensity ?? 1;
This approach would be completely backwards compatible.
blender has global environment intensity and rotation. the status of this ticket is closed, has the global scene environment enhancement been already implemented? thanks for having that.
scene.environmentIntensity
has not been added due to conflicting viewpoints. See the suggestions in https://github.com/mrdoob/three.js/issues/24665#issuecomment-1250682600, https://github.com/mrdoob/three.js/issues/24665#issuecomment-1485694349 and https://github.com/mrdoob/three.js/issues/24665#issuecomment-1530190026.
personally, i have hundreds of sandboxes where i use scene.environment + individual mesh.envMapIntensity.
Hmm... It appears that is true... material.envMapIntensity
attenuates either material.envMap
or scene.environment
-- whichever environment happens to apply.
It works fine, I guess. We definitely need to be able to attenuate scene.environment
, and this does it.
Everybody OK with this? @mrdoob @Mugen87 ?
I still really wish there was a global scene.environmentIntensity. It's a bit silly that we can set a global environment, but not it's intensity. It's also causing me problems; in that I have loaders in different areas loading meshes for different purposes. And trying to coordinate them all to ensure that they apply the current desired intensity on load is proving tricky. I pretty much never have a need for an individual mesh to treat the environment differently than other meshes. It's nice as an option, but a global would be more useful (to me at least).
I just had to add something along the lines of
const { nodes, materials } = useGLTF('/MyHouse.glb')
useEffect(() => {
for (const mat of Object.values(materials)) {
if (mat instanceof MeshStandardMaterial) {
mat.envMapIntensity = GLOBAL_LIGHT_INTENSITY;
}
}
}, [materials]);
in slightly different ways, across about 6 different files, to make sure the global lighting got applied to all the different types of things.
and then in my SceneLighting component.
useEffect(() => {
scene.traverse((child) => {
if (
child instanceof Mesh &&
child.material instanceof MeshStandardMaterial
) {
child.material.envMapIntensity = globalLightIntensity;
}
});
}, [globalLightIntensity, scene]);
Thats a lot of effort and overhead to apply a number to globally affect what is already a global value in
scene.environment = bgTexture;