I am having trouble getting my model with a custom shader to cast correct shadows.
I have made a slightly modified vertex shader chunk for the THREE.MeshPhysicalMaterial that allows me to tweak the behavior of the morph targets. Specifically, I am moving the position variable transformed around in the vertex shader to morph different parts of the model based on an input uniform instead of all at once like is normal. This works as intended for the rendered geometry, but that shadow that it projects onto objects like the ground and itself does not reflect the changes. I have attached an image that illustrates the problem. As the morph is applied the model breaks into little chunks, but the shadow on the ground still shows a solid ring shape.
I think it has something to do with needing a customDepthMaterial, but I can't find much information on it to know how I should integrate my tweaks. Any insight you can provide would be appreciated.
Here is my vertex fragment chunk that replaces the standard morph target chunk: morphtarget_vertex
float t = fillAmount;
float u = uv.x;
t = fit(t, 0.0, 1.0, -fillWidth, 1.0 + fillWidth);
t = fit(u, t, t + fillWidth, 1.0, 0.0);
transformed *= morphTargetBaseInfluence;
transformed += morphTarget0 * t;
#ifndef USE_MORPHNORMALS
transformed += morphTarget4 * t;
#endif
Here is my code to create a modified MeshPhysicalMaterial that uses my custom vertex shader chunk
function getModifiedMaterial()
{
return new Promise((resolve, reject) =>
{
Promise.all([
fetch("donut.prefix.vertex").then(res => res.text()),
fetch("donut.vertex").then(res => res.text())
])
.then(results =>
{
var prefix = results[0];
var body = results[1];
var material = new THREE.MeshPhysicalMaterial(
{
morphTargets: true,
vertexColors: true
}).clone();
material.userData.fillAmount = { value: 0.9 };
material.userData.fillWidth = { value: 0.0 };
material.customProgramCacheKey = () => "donut";
material.onBeforeCompile = shader =>
{
shader.uniforms.fillAmount = material.userData.fillAmount;
shader.uniforms.fillWidth = material.userData.fillWidth;[![enter image description here][1]][1]
shader.vertexShader = prefix + "\n" + shader.vertexShader.replace("#include <morphtarget_vertex>", body);
};
resolve(material);
});
});
}
The answer really came from WestLangley's comment, but here is some additional details:
You create an instance of THREE.MeshDepthMaterial with the same settings for vertex colors, and morph targets, and then make the same modifications to the vertexShader as the main material. Then you assign this new material to the mesh.customDepthMaterial slot. One thing you may run into is that the while the main shader will have access to normals and vertex color the depth shader will not. You can work around this by adding an entry to the shader's defines like this: shaders.defines.ISDEPTH = "" which you can then check for in the shader with #ifdef ISDEPTH
My material function became this
function getModifiedMaterial()
{
return new Promise((resolve, reject) =>
{
Promise.all([
fetch("donut.prefix.vertex").then(res => res.text()),
fetch("donut.vertex").then(res => res.text())
])
.then(results =>
{
var prefix = results[0];
var body = results[1];
var material = new THREE.MeshPhysicalMaterial(
{
morphTargets: true,
vertexColors: true
});
material.userData.fillAmount = { value: 0.9 };
material.userData.fillWidth = { value: 0.0 };
material.customProgramCacheKey = () => "donut";
material.onBeforeCompile = shader =>
{
shader.defines.FULL = "";
shader.uniforms.fillAmount = material.userData.fillAmount;
shader.uniforms.fillWidth = material.userData.fillWidth;
shader.vertexShader = prefix + "\n" + shader.vertexShader.replace("#include <morphtarget_vertex>", body);
};
var depthMaterial = new THREE.MeshDepthMaterial(
{
depthPacking: THREE.RGBADepthPacking,
morphTargets: true,
vertexColors: true
});
depthMaterial.customProgramCacheKey = () => "donutdepth";
depthMaterial.onBeforeCompile = shader =>
{
shader.uniforms.fillAmount = material.userData.fillAmount;
shader.uniforms.fillWidth = material.userData.fillWidth;
shader.vertexShader = prefix + "\n" + shader.vertexShader.replace("#include <morphtarget_vertex>", body);
};
resolve({ main: material, depth: depthMaterial });
});
});
}
And you can assign the results like this
getModifiedMaterial().then(materials =>
{
mesh.material = materials.main;
mesh.customDepthMaterial = materials.depth;
scene.add(mesh);
resolve();
});