brianzinn / react-babylonjs

React for Babylon 3D engine
https://brianzinn.github.io/react-babylonjs/
821 stars 105 forks source link

Missing declarative way to use shaderMaterial #277

Closed Xample closed 4 months ago

Xample commented 1 year ago

Hello, I suggest adding some example on how to use shaderMaterial declaratively. I found only 3 occurrences within the whole repo of shaderMaterial.

Actually, the hard part is how to pass variables into the shader, it could be either a mix of declarative, something like this:

  const shaderMaterial = useRef<ShaderMaterial>(null);

  if (shaderMaterial.current) {
    shaderMaterial.current.setFloat('test', 5.0);
  }

...

          <shaderMaterial
            ref={shaderMaterial}
            name={`shader-${i}`}
            setFloat={['test', 2]}
            shaderPath={'./custom-texture'}
            options={{
              attributes: ['position', 'normal', 'uv'],
              uniforms: [
                'worldViewProjection',
                'test',
              ],
            }}></shaderMaterial>

While it could be done declaratively

    <shaderMaterial
      name={`shader-${index}`}
      setFloat={['test', 5.0]} /// hard to understand
      shaderPath={'./custom-texture'}
      options={{
        attributes: ['position', 'normal', 'uv'],
        uniforms: ['worldViewProjection', 'test']
      }}></shaderMaterial>

Now, paying attention to this line:

setFloat={['test', 5.0]}

I'm facing 2 problems with this line, at first this is supposed to call the setFloat function of the wrapped shaderMaterial class, setFloat takes 2 arguments (the name and the value). It wasn't clear how to use it, but I thought that perhaps someone will spread the array operator to some arguments, and my assumption was correct.

Now another challenge, we could theoretically pass several arguments to the shaderMaterial, in the imperative way, we just call N times the setFloat function. This is however no possible to add several arguments to the component as it would immediately raise a ts error:

TS17001: JSX elements cannot have multiple attributes with the same name.

Last thing, if we need to frequently pass values to the shader (like the time), perhaps the most optimal way is to use the imperative way because otherwise, any change on the react component will recreate that component and this will slow down the whole rendering (one more time)

Any better way to achieve the same in a declarative way ?

brianzinn commented 1 year ago

The way you have mixed seems like the only way. I could add a "setFloats" that takes an array and calls setFloat for each item. That would work, but it be hard to discover and would need a different kind of equality check potentially for renderer.

In terms of calling frequently, definitely better to bypass the "React" render loop, as that will hamper performance significantly.

edit: You could "declaratively" add a useBeforeRender hook if you are updating as part of your render loop (like the time). That would run outside of React renderloop and you should be able to keep in a component?

brianzinn commented 4 months ago

just closing from inactivity and it seems at least partially resolved - kindly re-open if this is a feature request. cheers.