React-Threedrei EnvironmentMap Intensity and background visibility

Hello all, I’m totally new with React so probably I’m just doing something wrong.

I’m running into issues when using the ' helper.

Problem: When background is set to ‘false’ my ‘environmentIntensity’ is not working, instead it looks like it is set to full intensity causing my shadows to dissapear.

When background is set to ‘true’ my intensity is working (and my shadows visible) but I don’t want my environment map to be visible in my scene.

Running Three r163 and all other dependencies are also the latest.

Here is my related code:

/* index.jsx */

import ‘./style.css’
import ReactDOM from ‘react-dom/client’
import { Canvas } from ‘@react-three/fiber’
import { Environment } from ‘@react-three/drei’;
import App from ‘./App.jsx’
import * as THREE from ‘three’

const root = ReactDOM.createRoot(document.querySelector(‘#root’))

const cameraSettings = {
fov: 25,
near: 0.01,
far: 500,
position: [ 0, 0.420, 0.5]
}

const created = ({ gl }) => {
gl.setClearColor(‘#000000’, 1),
gl.antialias = true,
gl.toneMapping = THREE.ACESFilmicToneMapping,
gl.outputColorSpace = THREE.SRGBColorSpace
gl.physicallyCorrectLights = true
}

root.render(
<Canvas
shadows
onCreated={ created }
camera={ cameraSettings }
>



)

/*
App.jsx
*/
import { useFrame, useThree } from’@react-three/fiber’
import { Perf } from ‘r3f-perf’
import { Suspense, useRef } from ‘react’
import { OrbitControls, Html,useHelper } from ‘@react-three/drei’
import * as THREE from ‘three’
import Can from ‘./Can.jsx’

export default function App()
{

useFrame((state, delta) =>
{
const angle = state.clock.elapsedTime * 0.5
state.camera.position.x = Math.sin(angle) * 4
state.camera.position.z = Math.cos(angle) * 4
state.camera.lookAt(0, 0.25, 0)
})
return <>

    <Perf position='top-left' />

    <OrbitControls />

<directionalLight position={ [ 1.5, 2.5, 1.5 ] } lookAT={ [ 0, 0, 0 ] } intensity={ 1.5 } />

    <pointLight             
        position={ [ 1.5, 0.5, 1.5 ] }
        intensity={ 1 }
        distance={ 5 } 
        castShadow 
        shadow-mapSize={ [ 1024, 1024 ]}
        shadow-camera-far={ 100 }
        shadow-camera-top={ 2 }
        shadow-camera-right={ 2 }
        shadow-camera-bottom={ - 2 }
        shadow-camera-left={ - 2 }
/>
    <pointLight 
    ref={ pointLight } 
        position={ [ -0.5, 0.5, 1.5 ] } 
        intensity={ 1 }
        distance={ 5 } 
        castShadow 
        shadow-mapSize={ [ 1024, 1024 ]}
        shadow-camera-far={ 100 }
        shadow-camera-top={ 2 }
        shadow-camera-right={ 2 }
        shadow-camera-bottom={ - 2 }
        shadow-camera-left={ - 2 } 
    />
    <pointLight 
        position={ [ 0, 0.5, 0 ] }
        shadow-camera-far={ 100 } 
        intensity={ 1 }
        distance={ 5 } 
        castShadow 
        shadow-mapSize={ [ 1024, 1024 ] }
        shadow-camera-top={ 2 }
        shadow-camera-right={ 2 }
        shadow-camera-bottom={ - 2 }
        shadow-camera-left={ - 2 }  
    />

<Suspense>
    <Can />
</Suspense>

<ExportButton />

</>
}

/*
Can.jsx
*/

import React, { useEffect, useRef } from ‘react’
import { useGLTF } from ‘@react-three/drei’
import { Color } from ‘three’;

export default function Can(props) {
const { nodes, materials } = useGLTF(‘./models/optimizedCanTest.glb’)
const backdropRef = useRef();

useEffect(() => {
if (backdropRef.current) {
// Set the material color to a typical photography backdrop color
backdropRef.current.material.color = new Color(0xDDDDDD) // Neutral gray
}
}, );

return (
<group {…props} dispose={null} >
<mesh
ref={backdropRef}
receiveShadow
geometry={nodes.backdrop.geometry}
material={nodes.backdrop.material}
position={[0.202, 0, 0.002]}
rotation={[0, 0.017, 0]}
scale={[9.818, 4.922, 11.993]}

    />
        <mesh
    receiveShadow
    geometry={nodes.Reflector_plane.geometry}
    material={nodes.Reflector_plane.material}
    position={[-1.412, 1.061, 0.447]}
    rotation={[-0.005, -0.004, -1.626]}
    scale={[1.39, 1, 1]}
  
  />

  <mesh
    castShadow
    receiveShadow
    geometry={nodes.can_Baked.geometry}
    material={materials['can_Baked.015']}
    position={[0, 0.003, 0]}
    rotation={[Math.PI, -0.083, Math.PI]}
    scale={1.169}
    >
  <meshPhysicalMaterial
    attach="material"
    map={nodes.can_Baked.material.map}
    normalMap={nodes.can_Baked.material.normalMap}
    roughnessMap={nodes.can_Baked.material.roughnessMap}
    metalness={0.99}
    roughness={0.5}
    clearcoat={0.2}
    clearcoatRoughness={0.25}
    reflectivity={50}
    envMapIntensity={15}
      // Add any other properties you need
    />
    </mesh>
</group>

)
}

useGLTF.preload(‘./models/optimizedCanTest.glb’)

Thanks in advance for any help!

hard breaking change in recent threejs. the envMapIntensity property does not work like it always used to be, it only affects envMap, meaning you have to give the material its own environment map. if the material has none envMapIntensity does nothing.

generally, r3f is threejs, if anything doesn’t work, or works in a way you don’t expect, that’s merely threejs. unfortunately three changes wildly every month without indicating changes through semver, keeping track can be tiring. the easiest would be to take the offending thing and search through forums and github issues.

ps, THREE.Scene and <Environment> have new props https://codesandbox.io/p/sandbox/sleepy-jackson-hqct6n

Thanks for your response, that clears things up. I did look into some forums etc. but couldn’t find the breaking change. Anyways thanks for answering, now I know what I need to do :wink:

how are you approaching handling this currently @drcmda and is there some more descriptive documentation on how this change should be handled in general? do we have to keep a ref of <Environment/> and then feed it down to an iterative loop over all materials just to get the same visual result as before? I’m trying to set <Environment environmentIntensity={2} /> but even a value of 30 doesn’t have an effect…

EDIT: here’s a sandbox of what I’m referring to, if <Environment background={false} /> then <Environment environmentIntensity={50} /> stops having an effect… https://codesandbox.io/p/sandbox/lucid-bassi-ks3ymq?file=%2Fsrc%2FApp.js%3A20%2C1

i think the official migration would be to pass what you would normally drop into THREE.Scene into every material that requires a distinct intensity. in fiber it would be the same. there is a hook that does almost the same as environment but it doesn’t change the scene while the environment component also takes a map parameter which takes a texture sampler.

i have not tried but i hope that this is how it works.

const texture = useEnvironment({ preset: ..., files: ..., ... })
return (
  <>
    <Environment map={texture} />
    <mesh>
      <meshStandardMaterial envMap={texture} envMapIntensity={2} />
1 Like

that makes sense, I’m going to try this now, one thing that’s odd is the Edit^^^ above, here’s an image to demonstrate…


if <Environment background={false} /> then <Environment environmentIntensity={0} /> doesn’t have an effect

but if <Environment background={true} /> then environmentIntensity works as expected, why is the environmental intensity value controlling the scene.background intensity which in turn is then manipulating the materials intensity? and why isn’t the environmentIntensity just simply controlling scene.environment? isn’t an env map meant to be exactly that, a global environmental lighting source?

I was suffering from this change a week ago.

I think I managed to summarise it ok.

  • Three r163 added new property scene.environmentIntensity. Affects PBR materials with material.envMap = null.
  • Changing a material.envMapIntensity will now only work if you have set a fully loaded texture to the material.envMap property first.
  • Note that after setting material.envMap = someFullyLoadedTexture, it will no longer be affected by changes to scene.environmentIntensity.
1 Like

@drcmda can confirm your idea to use useEnvironment() and applying this on a material by material basis works, the caveat, now a global reference needs to be stored to a global env intensity value if you want to update all materials’ env Intensities at once anyway… there’s clearly good logic behind the change but it seems counter intuitive currently…

:face_with_monocle: oh, am i missing something in the following sandbox @seanwasere ? https://codesandbox.io/p/sandbox/lucid-bassi-ks3ymq?file=%2Fsrc%2FApp.js

if the Environment.background is set to false then environmentIntensity fails to affect any PBR materials, am i missing something or getting wires crossed between drei’s Environment and THREE’s ToneMappingExposure?

@Jeroen did you solve this?

I couldn’t get the sliders to work on that sandbox. Codesandbox rarely works for me recently.

So I created a similar demo as the @drcmda example, using same versions of fiber, drei, three.

  1. The Environment → environmentIntensity slider works on the meshStandardMaterial becuase its envMap is null by default.

  2. The meshStandardMaterial → envMapIntensity slider doesn’t work since it has no envMap

  3. Press the [Set Materials envMap] button at bottom of GUI.

  4. Now the Environment → environmentIntensity slider doesn’t work
    but instead, the meshStandardMaterial → envMapIntensity slider now works

  5. The Renderer -> toneMappingExposure effects intensity for everything before and after button press

If you want to set the background to false in the JSX, you can just edit the code in the demo, in the browser straight away and press ctrl-s to see the changes. No need to log in, and your laptop doesn’t sound like a rocket launching.

1 Like

Yes the environmentIntensity slider fails to work when background is set to false, however your demo looks like its working as expected :thinking:

I wonder if this is related here… Texture disposal in R3F - #8 by drcmda

it works for me on my demo when i set it to false

Yes

I’m trying to get to the bottom of the difference between both examples

It was already late at my end so didn’t try anything yet. But looks like @seanwasere shared already some working solutions. Will try today.

Thanks for your reply and your solutions, will try them today.

to confirm, copying your version over to codesandbox also works as expected… and has the solution, setting scene.environmentIntensity & other props scene.environmentRotation.y etc explicitly through useThree().scene works as expected, no need to manually set every materials envMap just to control the global scene.environment…

const { scene } = useThree()
scene.environmentIntensity = intensity
scene.environmentRotation.y = Math.PI

return (
  <Environment 
    background={false} 
    blur={blur} 
    files={[...files]}
  >
)
1 Like

these props are all on the environment component though, i added them all, environment and background props for intensty, blur and rotation.

<Environment
  environmentIntensity={intensity}
  environmentRotation={Math.PI}
  ...

this was a recent addition so most likely needs a dependency refresh. mutating the scene directly works but i would consider it not-so-good because once you unmount the component that did that it sticks. environment comp will undo whatever changes it made.

yes, not sure what’s happened, you can test in the following sandbox or your original by simply setting background={false}, it breaks environment intensity having an effect on the scene (defaults to 1?)…
https://codesandbox.io/p/sandbox/lucid-bassi-ks3ymq?file=%2Fsrc%2FApp.js%3A50%2C1

you can also uncomment these two lines to see the environmentIntensity behave as expected when background={false}…

  // const { scene, gl } = useThree()
  // scene.environmentIntensity = 20

the sandbox above and your original are both using the latest packages iirc…