FarazzShaikh / THREE-CustomShaderMaterial

🧩 Extend Three.js standard materials with your own shaders!
https://farazzshaikh.github.io/THREE-CustomShaderMaterial/
MIT License
890 stars 56 forks source link

Textures #53

Closed mwmwmw closed 6 months ago

mwmwmw commented 6 months ago

I'm having trouble adding new textures to a material.

I see them in the uniforms, if I add the texture using map (or any of the other slots) they work fine but if I try to access the textures in the shader, nothing.

I'm going to assume this is a problem with something I am doing, but maybe you can see a mistake.

const uniforms = {
myTexture: DEFAULT_TEXTURE // just a data texture that goes from white to black
}

export const MaterialProps = {
  baseMaterial: MeshStandardMaterial,
  vertexShader, // set elsewhere
  fragmentShader, // set elsewhere
  uniforms,
};

<CustomShaderMaterial 
    map={myTexture}
    myTexture={myTexture}
    {...MaterialProps} 
/> 

Then in the frag shader, this won't throw an error but the texture will not show up.

uniform sampler2D myTexture; 
 void main() {
  csm_DiffuseColor = texture2D(myTexture, vUUv); // a custom UV confirmed to work
 }

if I replace this with this whetever is assigned to map shows up.

uniform sampler2D map; 
 void main() {
  csm_DiffuseColor = texture2D(map, vUUv);
 }

So it just doesn't like the new texture uniform.

Any help or direction is greatly appreciated.

mwmwmw commented 6 months ago

NVM answered my own question.

I was just setting the prop on the CustomMaterial, but I needed to update the uniform value. Passing it in via a prop doesn't work.

Bad:

<CustomShaderMaterial
    myTexture={myTexture} 
    {...MaterialProps} 
 />

Good (maybe a nicer way to do this?, But it works)

 useEffect(()=>{
  ref.current.uniforms.myTexture.value = myTexture;
 },[])

 <CustomShaderMaterial
     ref={ref}
     {...MaterialProps} 
 />
FarazzShaikh commented 6 months ago

This is intentional as an API goal for CSM is to emulate standard ShaderMaterial syntax and that’s just how it’s done with shaders.

A nicer way to do this is to extend CSM/vanilla and make your own material class. Then you can define setters/getters to map JSX props to uniforms.

Untested pseudocode:

import CSM from "three-custom-shader-material/vanilla"
import { extend } from "@react-three/fiber"

class MyCustomMaterial extends CSM {
   constructor() {
      super({
         baseMaterial: ...,
         uniforms: {
            myTexture: { value: null }
         }
      })
   }

   set myTexture(value) {
      this.uniforms.myTexture.value = value;
   }

   get myTexture() {
      return this.uniforms.myTexture.value
   }
}

extend({ MyCustomMaterial: MyCustomMaterial })

<MyCustomMaterial 
   myTexture={texture}
/>

Unfortunately, having CSM handle this is infeasible as it requires dynamically generating getters/setters which may override setters/getters on the base material causing unintended effects. Its better to leave this in user-land

mwmwmw commented 6 months ago

Yeah it works well. I was porting a shader over from Drei's shaderMaterial() and it did things just differently enough to throw a wrench in my brain.

Extending the base class is probably overkill for what I'm doing, but I like the example.