Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using MeshRefractionMaterial in non-React code #1181

Closed
gydence opened this issue Dec 13, 2022 · 1 comment
Closed

Using MeshRefractionMaterial in non-React code #1181

gydence opened this issue Dec 13, 2022 · 1 comment
Labels
question Further information is requested

Comments

@gydence
Copy link
Contributor

gydence commented Dec 13, 2022

Hi! I'm very new to React so apologies if this question doesn't make sense.

I'm working on a simple React app using A-Frame and I'm hoping to incorporate elements from drei (e.g. MeshRefractionMaterial). I just have a React component that has a top level <a-scene> and then some entities.

Is there a "proper" way to mix drei components with say, a custom A-Frame JS component? I can't just instantiate it in JS:

let material = new MeshRefractionMaterial({});

I understandably get:

Failure loading node:   Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

So I tried:

<a-scene>
  <Canvas>
    <MeshRefractionMaterial id="refractionMaterial" bounces={2} aberrationStrength={0.01} envMap={texture} toneMapped={false} />
  </Canvas>
</a-scene>

and then grabbing it in JS with:

document.querySelector("#refractionMaterial");

But that gives me:

Cannot assign to read only property 'id' of object '#<material>'

And even if the id worked, I'm not sure this is the correct way of doing this.

Any help is much appreciated, thank you!

EDIT:

Aha! I see that there are actually two MeshRefractionMaterials in the package, one from @react-three/drei, which is the React component, and one from @react-three/drei/materials/MeshRefractionMaterial which is the JS material I want, just without the setup. So I can do:

import { MeshRefractionMaterial } from "@react-three/drei/materials/MeshRefractionMaterial";
import { MeshBVH, SAH } from "three-mesh-bvh";
...
    // set the scene environment, which gets mapped to the material's envMap
    const pmremGenerator = new THREE.PMREMGenerator(renderer);
    scene.environment = pmremGenerator.fromScene(environment).texture;
...
    // initialize the material
    this.diamondMaterial = new MeshRefractionMaterial();
    this.diamondMaterial.uniforms.bounces.value = 2;
    this.diamondMaterial.uniforms.resolution.value.set(window.innerWidth, window.innerHeight);

    {
      const isCubeMap = scene.environment.isCubeTexture === true;
      this.diamondMaterial.defines = {
        ENVMAP_TYPE_CUBEM: isCubeMap,
        CHROMATIC_ABERRATIONS: false,
        FAST_CHROMA: true
      };
    }
...
    // later, when a mesh loads, apply the material:
    self.el.object3D.traverse(function (node) {
      node.material = self.diamondMaterial.clone();
      node.material.uniforms.bvh.value.updateFrom(new MeshBVH(node.geometry.toNonIndexed(), {
        lazyGeneration: false,
        strategy: SAH
      }));
  });

This gets me much closer, but now I see the mesh rendering all black and I get:

three.js:14285 THREE.WebGLProgram: Program Info Log: C:\fakepath(271,1-6): warning X4000: use of potentially uninitialized variable (dyn_index_vec3_int)


debug.js:39 THREE.WebGLState: TypeError: Failed to execute 'texSubImage2D' on 'WebGL2RenderingContext': Overload resolution failed.
    at e.executeFunction (<anonymous>:3:467593)
    at e.executeOriginFunction (<anonymous>:3:467224)
    at WebGL2RenderingContext.texSubImage2D (<anonymous>:3:499508)
    at Object.texSubImage2D (three.js:16431:1)
    at uploadTexture (three.js:17275:1)
    at WebGLTextures.setTexture2D (three.js:16905:1)
    at SingleUniform.setValueT1 [as setValue] (three.js:13414:1)
    at WebGLUniforms.upload (three.js:13905:1)
    at setProgram (three.js:20844:1)
    at WebGLRenderer.renderBufferDirect (three.js:20165:1)

EDIT 2:

It seems that the problem was the PMREM texture. If I instead load a texture using TextureLoader (and set some of the same props that the React MeshRefractionMaterial normally does for you):

    const texture = new THREE.TextureLoader().load(process.env.PUBLIC_URL + "/assets/card.png",
      function (texture) {
        self.diamondMaterial.uniforms.envMap.value = texture;
        self.diamondMaterial.uniforms.envMap.value.needsUpdate = true;
        const isCubeMap = texture.isCubeTexture === true;
        const w = isCubeMap ? texture.image[0].width : texture.image.width;
        const cubeSize = 0.25 * w;
        const _lodMax = Math.floor(Math.log2(cubeSize));
        const _cubeSize = Math.pow(2, _lodMax);
        const width = 3 * Math.max(_cubeSize, 16 * 7);
        const height = 4 * _cubeSize;
        self.diamondMaterial.defines = {
          ENVMAP_TYPE_CUBEM: isCubeMap,
          CUBEUV_MAX_MIP: `${_lodMax}.0`,
          CUBEUV_TEXEL_WIDTH: 1 / width,
          CUBEUV_TEXEL_HEIGHT: 1 / height,
          CHROMATIC_ABERRATIONS: false,
          FAST_CHROMA: false
        };
        self.diamondMaterial.needsUpdate = true;
      });

The errors go away, but the diamond renders mostly black with a sliver of my envMap colors around the edge:
diamond

@gydence gydence added the question Further information is requested label Dec 13, 2022
@gydence gydence changed the title How to properly mix React components with non-React code? Using MeshRefractionMaterial in non-React code Dec 14, 2022
@gydence
Copy link
Contributor Author

gydence commented Dec 14, 2022

That last issue ended up being an issue in the drei shader. 'vNormal` isn't normalized, so this fixes the issue:

    vNormal = normalize((viewMatrixInv * vec4(normalMatrix * transformedNormal.xyz, 0.0)).xyz);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

1 participant