three.js icon indicating copy to clipboard operation
three.js copied to clipboard

Blender / GLTF: Add Scene.environment intensity

Open mjurczyk opened this issue 2 years ago • 2 comments

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.)

kot

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?)

mjurczyk avatar Sep 18 '22 14:09 mjurczyk

Can't we use the A channel for intensity? Opacity and intensity are quite related things for textures.

LeviPesin avatar Sep 18 '22 16:09 LeviPesin

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.

Mugen87 avatar Sep 19 '22 07:09 Mugen87

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.

viridia avatar Mar 13 '23 18:03 viridia

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).

viridia avatar Mar 13 '23 18:03 viridia

Now that we have backgroundIntensity (https://github.com/mrdoob/three.js/pull/24876) maybe it does make sense to add environmentIntensity too...

/cc @WestLangley

mrdoob avatar Mar 13 '23 23:03 mrdoob

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?

WestLangley avatar Mar 14 '23 00:03 WestLangley

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.

viridia avatar Mar 14 '23 01:03 viridia

@WestLangley

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?

That makes sense to me.

mrdoob avatar Mar 14 '23 05:03 mrdoob

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?

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.

drcmda avatar Mar 14 '23 09:03 drcmda

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.

mrdoob avatar Mar 15 '23 02:03 mrdoob

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?

WestLangley avatar Mar 27 '23 18:03 WestLangley

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.

viridia avatar May 01 '23 20:05 viridia

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.

iefreer avatar Oct 12 '23 07:10 iefreer

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.

WestLangley avatar Oct 12 '23 11:10 WestLangley

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 ?

WestLangley avatar Feb 28 '24 01:02 WestLangley

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.

Archimagus avatar Feb 28 '24 19:02 Archimagus

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;

Archimagus avatar Feb 28 '24 20:02 Archimagus