brianzinn / react-babylonjs

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

Shader issues, setting the sampler texture and visual bugs #177

Closed DefaultV closed 2 years ago

DefaultV commented 2 years ago

Hello! I'm currently trying to setup a shader (shaderMaterial) for my application.

The shader basically takes an image and makes it blurry and transparent. I've stored these shaders locally as *.vertex/fragment.fx files.

I can load it via the following example:

return (
    <plane name="shadow" position={position} width={width} height={height}>
      <shaderMaterial
        ref={shaderRef}
        name="artworkShadow"
        shaderPath="/shaders/shadowShader"
        options={{
          needAlphaBlending: true,
          attributes: ["position", "normal", "uv"],
          uniforms: [
            "world",
            "worldView",
            "worldViewProjection",
            "view",
            "projection",
          ],
        }}
      >
      </shaderMaterial>
    </plane>
  );

Then in a useEffect I set the sampler texture. (Bonus question, how do I set this sampler texture declaratively?) The component is a child to other components.

The shader code works fine when used in the babylon playground.

However, the sample texture it ends up using switches between multiple others. It seems to switch depending on the angle the camera is viewing the plane from and seemingly random textures that are loaded into the scene. I hope this is enough information to go on, I cannot really share more of the sourcecode.

brianzinn commented 2 years ago

Can you include your useEffect code that sets the texture?

DefaultV commented 2 years ago

Here's the useEffect, however, even if I comment out this code, the textureSampler still gets set somehow (sampler2D textureSampler in the shader code)

useEffect(() => {
    shaderRef.current.setTexture(
      "textureSampler",
      new Texture(textureUrl, scene)
    );
    console.log(textureUrl);
  }, []);
brianzinn commented 2 years ago

Is the "textureSampler" just an example - It's supposed to match the name of a uniform sampler as defined in the shader isn't it? Otherwise if you can share a playground example that does work and then I can work my way backwards.

Setting declaratively I do not see a good way, because there is no property to set it to without first getting a Ref.

<texture ref={textureRef ...>
...
<shaderMaterial ... setTexture{['textureSample', textureRef.current]} />

What I do is destructure that array and pass it to setTexture. It will call on every render though unfortunately, because I don't don't check equality well.

shaderMaterialInstance['setTexture'](...values);

What would work though is if you use an array from a ref because it would match the equality check. This would also mean the texture is loaded before being assigned.

const textureArrayRef = useRef(null);

// once texture is loaded
textureArrayRef.current = ['textureSample', textureRef.current];

textureArrayRef.current && {
  <shaderMaterial ... setTexture(textureArrayRef.current) />
}
DefaultV commented 2 years ago

I've tried to replicate the bug here, however, it seems there's an issue with loading the shader from the path. The branch is here: https://github.com/DefaultV/create-react-app-typescript-babylonjs/tree/Bug-shaderPath

I get the following error.

image

It seems to be trying to load the html file contents no matter what. Might be related.

brianzinn commented 2 years ago

You got it - you just need to delete the homepage in your package.json - or otherwise include that as part of the base_url if you want to deploy like I do to a sub folder (like github pages requires). In which case, you just need to add to your paths process.env.PUBLIC_URL as follows: https://github.com/brianzinn/create-react-app-babylonjs/blob/master/src/withSkybox.js#L11

If you wanted some help with declaratively adding textures to the shader that would make a good storybook.

Also, if you want to load shaders without HTTP requests have a look at VaporWave procedural texture: https://github.com/brianzinn/react-babylonjs/blob/master/storybook/stories/babylonjs/Integrations/pixi-demo/VaporWave.js

DefaultV commented 2 years ago

Thanks for the tip, alright, I think we're at the reproducible stage now, however it doesn't seem to switch between multiple textures but, it does seem to only apply the only most recent texture to the sampler even though specified otherwise.

image

The checkerboard was loaded last, the dog should be the applied sampler (Top-left is the plane with the shader material, so that should have a dog too)

Same branch, the file is planeShader.tsx https://github.com/DefaultV/create-react-app-typescript-babylonjs/tree/Bug-shaderPath https://github.com/DefaultV/create-react-app-typescript-babylonjs/blob/Bug-shaderPath/src/planeShader.tsx

textureArrayRef.current = ["textureSample", textureRef.current];
{textureArrayRef.current && (
<shaderMaterial
    name="artworkShadow"
    shaderPath="/shaders/shadowShader"
    setTexture={textureArrayRef.current}
    options={{
        needAlphaBlending: true,
        attributes: ["position", "normal", "uv"],
        uniforms: [
            "world",
            "worldView",
            "worldViewProjection",
            "view",
            "projection",
        ],
    }}
></shaderMaterial>
)}
DefaultV commented 2 years ago

I got this to work in my other project. It was seemingly due to folder hierarchies. What I did in the end was to make sure all naming conventions were correct and renamed the sampler, instead of textureSampler I put MAIN_TEXTURE. I know this doesn't seem like it should fix it, but it did somehow.

You can close the issue if you want =) Thanks for the aid!