Why float uniform is not updating?

Hello everyone!

I am playing around with react-three-fiber and I cannot figure out why, in this specific case, float uniform is never updated when Vector2 values are.

For some clarity here is a code sample :

const Material = () => {
  const { width, height } = useThree((state) => state.size);

  const [uniforms, setUniforms] = useState(() => ({
    u_resolution: { value: new Vector2(width, height) },
    u_scale: { value: 3 }
  }));

  useControls({
    scale: {
      value: uniforms.u_scale.value,
      min: 1,
      max: 10,
      step: 1,
      onChange: (value) => {
        //this uniform is never updated in the shader even though it is in the state
        setUniforms((u) => ({
          ...u,
          u_scale: { value: value }
        }));
      }
    }
  });

  useEffect(() => {
    //this uniform is correctly updating
    setUniforms((u) => ({
      ...u,
      u_resolution: {
        value: u.u_resolution.value.set(width, height)
      }
    }));
  }, [width, height]);

  return (
    <shaderMaterial
      vertexShader={vertexShader}
      fragmentShader={fragmentShader}
      uniforms={uniforms}
    />
  );
};

export default memo(Material);

Here is a link to a code sandbox showing the problem : affectionate-grass-0kli77 - CodeSandbox

Thank you very much for your help ! :smile:

teh most funny thing, actually, if you put this:

  useEffect(() => {
    setUniforms((u) => { alert(u.u_scale.value); return({
      ...u,
      u_resolution: {
        value: u.u_resolution.value.set(width, height)
      }
    })});
  }, [width, height]);

you can see changed u_scale value on resize

… any way, doing

<shaderMaterial
      key={uniforms.u_scale.value}

solves it

1 Like

woah thank you :hugs:

Do you have any idea why it doesn’t work the way it’s written in my code sample ?

I even tried to console.log( meshRef.current.material.uniforms.u_scale.value) and it gives me the correct output …

no. you need to ask @drcmda

You need to use the same object for uniforms, what you’re doing is replacing the whole object instead of updating its properties. While assigning a “key” might solve the issue, but I think, every time the key value changes it creates a new shaderMaterial.

First, make sure uniform object doesn’t get changed. You can memoize it:

// this always returns the same object, 
// no matter how many times the component get rerendered
const uniforms = useMemo(() => ({
  u_resolution: { value: new Vector2(width, height) },
  u_scale: { value: 3 }
}), []);

Then what you can do is to assign a ref to your material component:

const materialRef = useRef(null);

// ...

<shaderMaterial
  ref={materialRef}
  vertexShader={vertexShader}
  fragmentShader={fragmentShader}
  uniforms={uniforms}
/>

And finally use the ref and the useEffect hook to update the uniforms object:

useEffect(() => {
  materialRef.current.unifroms.u_resolution.value = /* your new value here ...*/;
}, [width, height]);
2 Likes

at this point, the question is why changing u_resolution in that way works? one would think it is either both work or none.

uniforms can’t be changed like that in threejs, you can’t just overwrite them. you need to create getters/setters for them or else they remain inaccessible.

i also think that defining a shader in jsx is not ideal. why not just use plain THREE.ShaderMaterial (or drei/shaderMaterial, perhaps better/easier) and then extend? this way your material is re-usable:

import { shaderMaterial } from '@react-three/drei'
import { Canvas, extend } from '@react-three/fiber'

const FooMaterial = shaderMaterial(
  { resolution: 1, scale: 3 },
  `...`, // vert
  `...`, // frag
)

extend({ FooMaterial })

...
<Canvas>
  <mesh>
    <planeGeometry />
    <fooMaterial scale={4} resultion={200} />

here’s a real example:

ps. drei/shaderMaterial creates a plain THREE.ShaderMaterial but it takes out some boilerplate as it creates auto setter/getters for uniforms. also defining uniforms is easier. not react specific, could also be used in vanilla:

const FooMaterial = shaderMaterial(uniforms, vert, frag)
const mesh = new THREE.Mesh(new THREE.BoxGeometry(), new FooMaterial())

Thank you for this post. I have been scratching my head for hours in why my uniform property wouldn’t update. This not only corrected it but also set it as ‘the same object’. Glad I found this. :smiling_face_with_three_hearts:

1 Like