Apply shadow in ShaderMaterial

Hi All,

I want to active shadow in shaderMaterial , How can I do that?
My code :

   const shaderMaterial = new ShaderMaterial({
      vertexShader: `
    varying vec2 vUv;
    
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
      fragmentShader: `
    uniform sampler2D texture1;
    uniform sampler2D texture2;
    uniform float xCount;
    uniform float yCount;
    varying vec2 vUv;
    
    void main() {
      vec2 dynamicUV = vec2(mod(vUv.x * xCount, 1.0) , mod(vUv.y * yCount, 1.0));
      vec4 color;
       
      if( ((vUv.y < (1.0 / yCount)) || (vUv.y > 1.0 - ( 1.0 / yCount ))) && (vUv.x < ( 1.0 / xCount )  || vUv.x > 1.0 - ( 1.0 / xCount ) )){
        color = texture2D(texture1, dynamicUV);
      }else{
        color = texture2D(texture2, dynamicUV);
      }
      
      gl_FragColor = color;
    }
  `,
      uniforms: UniformsUtils.merge([
        UniformsLib.shadowmap,
        UniformsLib.lights,
        UniformsLib.ambient,
        {
          texture1: { value: texture1 },
          texture2: { value: texture2 },
          xCount: { value: 3.0 },
          yCount: { value: 3.0 },
        },
      ]),

      lights: true,
      blending: NoBlending

    });


But the shadow and light were not applied.

With the recent changes on color management and soon lighting, I strongly recommend to inject your GLSL code into native three shaders using onBeforeCompile instead.
Because shaders chunks have become so convoluted (r151-154 is borderline pure madness), it’s not worth it to use shaderMaterial anymore if you seek shadows an lights. There is litterally hundreds of lines spread in 10+ chunks, to get it working.

For the defense of three maintainers, it’s an unavoidable situation as they try to transition to webGPU and nodes while avoiding potential technical debts from the decade old webGL

2 Likes

Thank you @Oxyn ,

I tried to import it in function OnBeforeCompile but I get the same result, the shadow does not appear:

let material = new ShaderMaterial({
      onBeforeCompile: (shader) => {
        shader.vertexShader = ` 
        varying vec2 vUv;
    
        void main() {
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    } 
        `;
  
        shader.fragmentShader = `
        uniform sampler2D anglesTexture;
        uniform sampler2D texture2;
        uniform sampler2D textureShape;
        uniform float xCount;
        uniform float yCount;
        varying vec2 vUv;
        
        void main() {
          vec4 color1 ;
          vec2 dynamicUV = vec2(mod(vUv.x * xCount, 1.0) , mod(vUv.y * yCount, 1.0));
          vec4 color;
           
          if( ((vUv.y < (1.0 / yCount)) || (vUv.y > 1.0 - ( 1.0 / yCount ))) && (vUv.x < ( 1.0 / xCount )  || vUv.x > 1.0 - ( 1.0 / xCount ) )){
            color = texture2D(anglesTexture, dynamicUV);
          }else{
            color = texture2D(texture2, dynamicUV);
          }
          
          if( (vUv.y >  0.1  && vUv.y < 0.9) &&  (vUv.x >  0.2  && vUv.x < 0.45) ){
            color1 = texture2D(textureShape, dynamicUV);
          }
           

          
          // Custom blending logic
          vec4 blendedColor = mix(color, color1, 0.9);
          
          gl_FragColor = blendedColor;
        }
  
       `;
  
  
        shader.uniforms = UniformsUtils.merge([
          UniformsLib.shadowmap,
          UniformsLib.lights,
          UniformsLib.ambient,
          {
            anglesTexture: { value: anglesTexture },
            texture2: { value: texture2 },
            textureShape: { value: textureShape },
            xCount: { value: 10.0 },
            yCount: { value: 10.0 },
          },
        ]);
  
        shader.lights = true;
      },
    });

Here the wall does not have a shadow from the cabinet.

You need to use a native shader, you still use ShaderMaterial.

Something like this:

//our new material will use the Phong shader as base
const material = new THREE.MeshPhongMaterial( {
		map: texture,
		color: 0xffffff
} );

//shader injection
material.onBeforeCompile = function ( shader ) {

	//exemple of uniforms definition for fragment
	shader.uniforms.myUniform = { value: myValue };
	shader.fragmentShader = 'uniform float myUniform;\n' + shader.fragmentShader;

	//exemple of replacing phong map_fragment chunk with custom code
	shader.fragmentShader = shader.fragmentShader.replace(
	'#include <map_fragment>',
	[
	//write custom code here
	].join( '\n' )
	);
};

To understand exactly which chunks you need to change. Look at the native shaders code here:
in my exemple I would open meshphong.glsl.js

Next look at the detailed code used by chunks here
In my exemple I need to open map_fragment.glsl.js to either edit this code, or replace it.

Since native shaders are very fragmented right now, it’s not a simple task either.
But your shader will work with every features available.

5 Likes

I made a plugin to make it much easier and cleaner to extend native shader. Also those with custom vertex transformations (see templates). If you only do minor changes you can also use ’ onBeforeCompile’ the patchShader of this lib however will also give you the cleaner patching functionality.

If you want to properly access uniforms you might want your materials to be based on ShaderMaterial then.

4 Likes

Thank you @Fyrestar ,

I tried your plugin , but I get white plane also , the code:

const myMaterial = extendMaterial(MeshPhongMaterial, {

	// Will be prepended to vertex and fragment code
	header: 'varying vec2 vUv;',

	// Will be prepended to vertex code
	headerVertex: 'varying vec2 vUv;',

	// Will be prepended to fragment code
	headerFragment: `uniform sampler2D anglesTexture; \n
  uniform sampler2D texture2; \n
  uniform sampler2D textureShape; \n
  uniform float xCount; \n
  uniform float yCount; \n
  varying vec2 vUv;`,



	// Insert code lines by hinting at a existing
	vertex: {

	

		'#include <fog_vertex>': 'vUv = uv;',

		
	},
	fragment: {
	
      '@gl_FragColor': `vec4 color1 ;
      vec2 dynamicUV = vec2(mod(vUv.x * xCount, 1.0) , mod(vUv.y * yCount, 1.0));
      vec4 color;

      if( ((vUv.y < (1.0 / yCount)) || (vUv.y > 1.0 - ( 1.0 / yCount ))) && (vUv.x < ( 1.0 / xCount )  || vUv.x > 1.0 - ( 1.0 / xCount ) )){
      color = texture2D(anglesTexture, dynamicUV);
       }else{
        color = texture2D(texture2, dynamicUV);
      }

      if( (vUv.y >  0.1  && vUv.y < 0.9) &&  (vUv.x >  0.2  && vUv.x < 0.45) ){
      vec2 offsetUV = vec2(0.0001, 0.0001);
      vec2 modifiedUV = dynamicUV + offsetUV;
      color1 = texture2D(textureShape, modifiedUV);
      }
      vec4 blendedColor;

      const float borderSize = 0.01; // Adjust the border size as needed

      // Check if the UV coordinates are near the border
      bool nearBorder = (vUv.x < borderSize || vUv.x > 1.0 - borderSize || vUv.y < borderSize || vUv.y > 1.0 - borderSize);

      if (nearBorder) {
      // Apply blending factor based on the distance to the border
      float blendFactor = smoothstep(0.0, borderSize, min(min(vUv.x, 1.0 - vUv.x), min(vUv.y, 1.0 - vUv.y)));
      blendedColor = mix(color, color1, blendFactor);
      } else {
      blendedColor = color * (1.0 - color1.a) + color1 * color1.a;
      }

      gl_FragColor = blendedColor;

      
      `
	},


	// Properties to apply to the new THREE.ShaderMaterial
	material: {
		skinning: true
	},


	// Uniforms (will be applied to existing or added) as value or uniform object
	uniforms: {

    anglesTexture : { value: anglesTexture },  
    texture2 : { value: texture2 },
    textureShape : { value: textureShape },
    xCount : { value: 9.0 },
    yCount : { value: 9.0 }

	}

});

You’d need to provide a codepen like this, you can fork as template.

This is the link : Apply shadow and shader

Thank you @Fyrestar

You need to replace a proper line, you only replaced gl_FragColor, since you don’t use a built-in map you also need to define USE_UV in defines, not declaring vUv yourself as it would conflict.

1 Like

Thank you very much @Fyrestar ,
I applied your code but the shadow is not applied.

Acutally i forgot shadow is what you wanted, place your code earlier like at

"#include <map_fragment>": "your code"

and write to diffuseColor not gl_FragColor

3 Likes

It worked :blue_heart:, Thank you very much @Fyrestar .

1 Like