ShaderMaterial particles not receiving shadows

I’ve been following Edan Kwan’s guide on shadows: http://blog.edankwan.com/post/three-js-advanced-tips-shadow . Specifically, I’ve been trying to apply the concepts to a field of Points. So the idea is that the particles can both cast shadows and receive shadows. I was able to cast shadows using customDistanceMaterial (since I’m using PointLights).

But I noticed that the ShaderChunk.shadowmap_fragment.glsl no longer exists, so I’m not really sure how to approach the problem of receiving shadows. I’ve tried using ShaderChunk.shadowmask_pras_fragment.glsl method - getShadowMask() , but I’m not quite sure if I’m doing it right (or even for the right purpose). Cause, I’m getting the value 1.0f for all vertices no matter what.

Just to make it clear, I’d like to receive shadows from other particles and other objects in the scene.
------------------------- (removed link, no longer relevant)

index.js:

var geo = new THREE.BufferGeometry();
geo.addAttribute( 'position', new THREE.BufferAttribute( position, 3 ));
geo.addAttribute( 'displacement', new THREE.BufferAttribute( displacement, 1 ));

var mat = new THREE.ShaderMaterial({
    uniforms: THREE.UniformsUtils.merge([
        THREE.UniformsLib.shadowmap,
        {
            color1: { type: 'c', value: undef },
            color2: { type: 'c', value: undef }
        }
    ]),
    vertexShader: shaderParse(document.getElementById( 'vertexShader' ).textContent),
    fragmentShader: shaderParse(document.getElementById( 'fragmentShader' ).textContent),
    blending: THREE.NoBlending
});

mat.uniforms.color1.value = new THREE.Color( 0x20cc31);
mat.uniforms.color2.value = new THREE.Color( 0x2095cc);

var mesh = new THREE.Points( geo, mat );

mesh.customDistanceMaterial = new THREE.ShaderMaterial( {
    uniforms: THREE.UniformsUtils.merge([
        THREE.UniformsLib.shadowmap,
        {
            lightPosition: {type: 'v3', value: new THREE.Vector3(0, 700, 0)},
        }
    ]),
    vertexShader: shaderParse(document.getElementById( 'distanceVertexShader' ).textContent),
    fragmentShader: shaderParse(document.getElementById( 'distanceFragmentShader' ).textContent),
    depthTest: true,
    depthWrite: true,
    side: THREE.BackSide,
    blending: THREE.NoBlending
});

mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);

// . . .

var pointLight = new THREE.PointLight( 0xffffff, 1, 700 );
pointLight.castShadow = true;
pointLight.shadow.camera.near = 10;
pointLight.shadow.camera.far = 1000;
pointLight.shadow.bias = 0.1;
pointLight.shadow.mapSize.width = 4096;
pointLight.shadow.mapSize.height = 4096;
lights.add( pointLight );

vertexShader.vert:

attribute float displacement;
varying float pos;

// chunk(shadowmap_pars_vertex);

void main() {
    vec4 worldPosition = modelMatrix * vec4( position.xyz, 1.0 );
    vec4 mvPosition = viewMatrix * worldPosition;
    // chunk(shadowmap_vertex);
    pos = displacement;
    gl_Position = projectionMatrix * mvPosition;
    gl_PointSize = 1300.0 / length( mvPosition.xyz ) * 0.1;
}

fragmentShader.frag:

// chunk(common);
// chunk(packing);
// chunk(fog_pars_fragment);
// chunk(bsdfs);
// chunk(lights_pars_begin);
// chunk(shadowmap_pars_fragment);
// chunk(shadowmask_pars_fragment);

varying float pos;

uniform vec3 color1;
uniform vec3 color2;

void main() {

    vec3 outgoingLight = mix(color2, color1, pos);

    // chunk(fog_fragment);

    outgoingLight *= getShadowMask();

    gl_FragColor = vec4( outgoingLight, 1.0) );

}

Thanks in advance.

EDIT :
Apparently, the reason why getShadowMask() always returns 1.0f is because the following piece of code: shadow *= bool( pointLight.shadow ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0; inside ShaderChunk.shadowmask_pars_fragment.glsl

I’ve ran some tests and pointLight.shadow is never set anywhere, so it never executes getPointShadow. On the other hand, getPointShadow always returns 0.0f … So I’m really not sure what’s happening here, maybe there’s something wrong with what I’m doing or maybe it’s just broken.

3 Likes

The link I gave is no longer relevant to the discussion, but I’ve managed to solve the initial problem I had.

If you want a custom shader to receive shadows correctly, you need to pass THREE.UniformsLib.lights uniforms and set lights: true on the shader options. Like the following:

var renderShader = new THREE.ShaderMaterial( {
    uniforms: THREE.UniformsUtils.merge([
        THREE.UniformsLib.shadowmap,
        THREE.UniformsLib.lights,
        THREE.UniformsLib.ambient,
        {
            positions: { type: "t", value: null },
            pointSize: { type: "f", value: 1 },
            color1: { type: 'c', value: ( new THREE.Color( 0x2095cc) ) },
            color2: { type: 'c', value: ( new THREE.Color( 0x20cc31) ) }
        } ]),
    vertexShader: shaderParse(document.getElementById( 'render.vert' ).textContent),
    fragmentShader: shaderParse(document.getElementById( 'render.frag' ).textContent),
    lights: true,
    blending: THREE.NoBlending
} );
4 Likes

Were you ever able to figure out why getPointShadow always returns 0? I’m having the same issue with trying to add shadows to my particles but getting undesirable results because getShadowMask is always either 1 or 0.

I have the same issue. getShadowMask returns either 0 or 1. the mesh’s customDistanceMaterial seems to be not working when set as shaderMaterial. I tested it under version 0.114

hi Grant_Yi, do you find solution on this?

Hey Xing,

Unfortunately, I never found a solution to this problem :confused: . What I ended up doing is using a given particle’s distance from a set point and altering opacity based on that. It faked shadows just enough for my use case.

I see. thanks for the reply.

Hello guys, sorry for the late response.

As previously mentioned, there are a couple of requirements for the shadow ShaderChunks to work correctly. The main reason why getShadowMask always return 0 is because it’s not receiving the correct light and shadow uniforms.

The way to solve this is to include the require uniforms to your shader

uniforms: THREE.UniformsUtils.merge([
    THREE.UniformsLib.lights,
    {  
       // custom shader uniforms
    }
] )

Also, it is required to set lights: true when creating the ShaderMaterial, so that it knows internally that the shader requires the internal light and shadow uniforms.

This should suffice for the behavior to work correctly, if you are still having problems. It’s worth checking if the particles object has castShadow = true and if the light shadows are properly set.

Let me know if you still have any trouble with that.

1 Like

hey sciecode,

Thanks for your response. I’ve already setup all the uniforms, plus some uniforms and properties are removed in latest version. I’ve created a pen for this https://codepen.io/xingway/pen/eYNERMg

Your custom distance shader material is incorrect. You are using position as the texture sampling coordinate, but you defined uv as the main sampling coordinate.

texture2D( texturePosition, uv.xy ).xyz;

Also, you never modified your lightPos uniform value.

lightPos: { value: new THREE.Vector3( 0, 600, 0 ) }

1 Like

im stuck here for 2 days. you are a lifesavor. salute thanks for your pacient help.

1 Like