Discarded fragments still cast shadows

I have a custom fragment shader (obtained by injecting some code using material.onBeforeCompile) which uses discard to remove certain fragments. It looks like these discarded fragments still participate in shadow casting (which makes sense, as that code is not run when computing the shadows). Is there any way to turn that off and make discarded fragments no longer produce a shadow? Can I inject into the shadow pass to do a similar discard there? Thanks!

related: Implement your own shadow mapping - #3 by martinRenou

I had the same problem today.
the shadow map is generated using a texture that is rendered using a depth material.
( check three.js/src/renderers/webgl/WebGLShadowMap.js at bd885e92f3fe8f71fc1160492e9a81ea9d8d94fe · mrdoob/three.js · GitHub)

I fixed it by applying the same discarding logic as in my own shader to the DepthMaterial using the onBeforeCompile function. (i apply it on the result object just before returning it in the getDepthMaterial)

function getDepthMaterial( object, material, light, type ) {

	let result = null;

	const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial;

	if ( customMaterial !== undefined ) {

		result = customMaterial;

	} else {

		result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial;

		if ( ( _renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) ||
			( material.displacementMap && material.displacementScale !== 0 ) ||
			( material.alphaMap && material.alphaTest > 0 ) ||
			( material.map && material.alphaTest > 0 ) ) {

			// in this case we need a unique material instance reflecting the
			// appropriate state

			const keyA = result.uuid, keyB = material.uuid;

			let materialsForVariant = _materialCache[ keyA ];

			if ( materialsForVariant === undefined ) {

				materialsForVariant = {};
				_materialCache[ keyA ] = materialsForVariant;

			}

			let cachedMaterial = materialsForVariant[ keyB ];

			if ( cachedMaterial === undefined ) {

				cachedMaterial = result.clone();
				materialsForVariant[ keyB ] = cachedMaterial;
				material.addEventListener( 'dispose', onMaterialDispose );

			}

			result = cachedMaterial;

		}

	}

	result.visible = material.visible;
	result.wireframe = material.wireframe;

	if ( type === VSMShadowMap ) {

		result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side;

	} else {

		result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ];

	}

	result.alphaMap = material.alphaMap;
	result.alphaTest = material.alphaTest;
	result.map = material.map;

	result.clipShadows = material.clipShadows;
	result.clippingPlanes = material.clippingPlanes;
	result.clipIntersection = material.clipIntersection;

	result.displacementMap = material.displacementMap;
	result.displacementScale = material.displacementScale;
	result.displacementBias = material.displacementBias;

	result.wireframeLinewidth = material.wireframeLinewidth;
	result.linewidth = material.linewidth;

	if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) {

		const materialProperties = _renderer.properties.get( result );
		materialProperties.light = light;

	}

	result.onBeforeCompile = discardHiddenOnBeforeCompileFunc // added to deal with discarded fragments

	return result;

}

while my discarding function just for the example looks like this :

let discardHiddenOnBeforeCompileFunc = (shader) => {
	  
	shader.vertexShader = shader.vertexShader.replace(
	  '#include <common>',
	  [`#include <common>`,
		`attribute float expressID;`,
		`attribute float hidden;`,
		`varying float vhidden;`].join('\n')

	);
	shader.vertexShader = shader.vertexShader.replace(
	  '#include <begin_vertex>',
	  [`#include <begin_vertex>`,
		`vhidden = hidden;`,
	].join('\n')

	);


	shader.fragmentShader = shader.fragmentShader.replace(
	  '#include <common>',
	  [`#include <common>`,
		`varying float vhidden;`].join('\n')
	);

	shader.fragmentShader = shader.fragmentShader.replace(
	  `#include <clipping_planes_fragment>`,
	  [`#include <clipping_planes_fragment>`,
		`if(vhidden  > 0.0) {`,
		`discard;`,
		`return;`,
		`}`,].join('\n')

	)


};

another option would be to create another depth material with this discard logic, and apply it to your own mesh. that way you don’t need to play with the source code of threejs.

yourMesh.customDepthMaterial = //assign your custom depth material

you might want to do the same for the customDistanceMaterial incase you are using a Point Light.

1 Like

For anyone else with this problem: in many situations, just setting alphaTest on the material will be sufficient, as this will remove the shadows cast by discarded fragments too.

1 Like