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!
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.
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.