DirLight castsShadow throws TypeError: light.getShadowMap is not a function (and shadow projection matrix is wrong)
Summary
Enabling directional light shadows on Meshes with PhongMaterial / MetallicMaterial / SpecularMaterial crashes at shader build time:
Uncaught TypeError: t.getShadowMap is not a function
There is a second related bug: the shadow projection matrix uniform is bound to getShadowViewMatrix() instead of getShadowProjMatrix(), so even after fixing (1), shadow projection would be incorrect.
Affected version: @xeokit/xeokit-sdk@2.6.112 (also present in current published sources on jsDelivr for that version).
Reproduction
Minimal setup:
import {
Viewer, Mesh, ReadableGeometry, buildBoxGeometry,
PhongMaterial, DirLight, AmbientLight
} from "@xeokit/xeokit-sdk";
const viewer = new Viewer({ canvasId: "myCanvas" });
viewer.scene.clearLights();
new AmbientLight(viewer.scene, { color: [0.5, 0.5, 0.5], intensity: 0.6 });
new DirLight(viewer.scene, {
dir: [0.4, -0.85, 0.35],
color: [1, 1, 1],
intensity: 1.2,
space: "world",
castsShadow: true // <-- triggers the bug
});
// Any mesh that receives shadows
new Mesh(viewer.scene, {
geometry: new ReadableGeometry(viewer.scene, buildBoxGeometry({ xSize: 1, ySize: 1, zSize: 1 })),
material: new PhongMaterial(viewer.scene, { diffuse: [0.7, 0.7, 0.7] }),
position: [0, 0.5, 0],
castsShadow: true,
receivesShadow: true
});
// Optional ground so a shadow is visible once fixed
new Mesh(viewer.scene, {
geometry: new ReadableGeometry(viewer.scene, buildBoxGeometry({ xSize: 10, ySize: 0.05, zSize: 10 })),
material: new PhongMaterial(viewer.scene, { diffuse: [0.8, 0.8, 0.8] }),
position: [0, -0.05, 0],
castsShadow: false,
receivesShadow: true
});
Expected: scene renders with shadows.
Actual: TypeError: … getShadowMap is not a function when building the Draw program.
Root cause
Bug 1 — wrong object for getShadowMap
In src/viewer/scene/webgl/WebGLRenderer.js, createLightSetup attaches shadow samplers under glslLight.shadowParameters:
shadowParameters: light.castsShadow && {
getShadowProjMatrix: () => lightUniforms.shadowProjMatrix,
getShadowViewMatrix: () => lightUniforms.shadowViewMatrix,
getShadowMap: () => lightUniforms.shadowMap
}
In src/viewer/scene/mesh/draw/DrawShaderSource.js, vertex varyings correctly use light.shadowParameters.getShadowProjMatrix() / getShadowViewMatrix(), but the fragment PCF loop incorrectly calls light.getShadowMap():
// DrawShaderSource.js — broken
src.push(`float texelDepth = dot(texture(${light.getShadowMap()}, ${vDirectionals[i].vShadowPosFromLight}.xy + vec2(x, y) * texelSize), bitShift);`);
light is the glslLight descriptor; it only has getColor, getDirection, isViewSpace, and shadowParameters. There is no getShadowMap on light itself → runtime TypeError when interpolating the shader source.
Bug 2 — projection matrix uniform uses view matrix getter
Same createLightSetup in WebGLRenderer.js:
// broken: both uniforms use getShadowViewMatrix
shadowProjMatrix: programVariables.createUniform("mat4", `shadowProjMatrix${i}`, (set) => set(lights[i].getShadowViewMatrix())),
shadowViewMatrix: programVariables.createUniform("mat4", `shadowViewMatrix${i}`, (set) => set(lights[i].getShadowViewMatrix())),
DirLight / light state expose both getShadowProjMatrix() and getShadowViewMatrix() (see DirLight.js). The projection uniform must use the projection getter.
Proposed fix
1. src/viewer/scene/mesh/draw/DrawShaderSource.js
- src.push(` float texelDepth = dot(texture(${light.getShadowMap()}, ${vDirectionals[i].vShadowPosFromLight}.xy + vec2(x, y) * texelSize), bitShift);`);
+ src.push(` float texelDepth = dot(texture(${light.shadowParameters.getShadowMap()}, ${vDirectionals[i].vShadowPosFromLight}.xy + vec2(x, y) * texelSize), bitShift);`);
(This is already guarded by if (light.shadowParameters) { … }.)
2. src/viewer/scene/webgl/WebGLRenderer.js (createLightSetup)
- shadowProjMatrix: programVariables.createUniform("mat4", `shadowProjMatrix${i}`, (set) => set(lights[i].getShadowViewMatrix())),
+ shadowProjMatrix: programVariables.createUniform("mat4", `shadowProjMatrix${i}`, (set) => set(lights[i].getShadowProjMatrix())),
shadowViewMatrix: programVariables.createUniform("mat4", `shadowViewMatrix${i}`, (set) => set(lights[i].getShadowViewMatrix())),
Optional follow-up (not required for the crash)
In the same PCF block the kernel is for x/y in [-3, 3] (7×7 = 49 samples) but the code does shadow /= 9.0. That should likely be 49.0 (or the loop range reduced to [-1, 1] if 3×3 was intended).
Workaround
Until fixed: keep DirLight.castsShadow = false and/or Mesh.receivesShadow = false.
Environment
@xeokit/xeokit-sdk@2.6.112
- WebGL2 / Chromium (also reproduced under WebView2)
- Path:
Mesh + PhongMaterial + DirLight with castsShadow: true and receivesShadow: true
DirLight castsShadow throws TypeError: light.getShadowMap is not a function (and shadow projection matrix is wrong)
Summary
Enabling directional light shadows on Meshes with
PhongMaterial/MetallicMaterial/SpecularMaterialcrashes at shader build time:There is a second related bug: the shadow projection matrix uniform is bound to
getShadowViewMatrix()instead ofgetShadowProjMatrix(), so even after fixing (1), shadow projection would be incorrect.Affected version:
@xeokit/xeokit-sdk@2.6.112(also present in current published sources on jsDelivr for that version).Reproduction
Minimal setup:
Expected: scene renders with shadows.
Actual:
TypeError: … getShadowMap is not a functionwhen building the Draw program.Root cause
Bug 1 — wrong object for
getShadowMapIn
src/viewer/scene/webgl/WebGLRenderer.js,createLightSetupattaches shadow samplers underglslLight.shadowParameters:In
src/viewer/scene/mesh/draw/DrawShaderSource.js, vertex varyings correctly uselight.shadowParameters.getShadowProjMatrix()/getShadowViewMatrix(), but the fragment PCF loop incorrectly callslight.getShadowMap():lightis theglslLightdescriptor; it only hasgetColor,getDirection,isViewSpace, andshadowParameters. There is nogetShadowMaponlightitself → runtime TypeError when interpolating the shader source.Bug 2 — projection matrix uniform uses view matrix getter
Same
createLightSetupinWebGLRenderer.js:DirLight/ light state expose bothgetShadowProjMatrix()andgetShadowViewMatrix()(seeDirLight.js). The projection uniform must use the projection getter.Proposed fix
1.
src/viewer/scene/mesh/draw/DrawShaderSource.js(This is already guarded by
if (light.shadowParameters) { … }.)2.
src/viewer/scene/webgl/WebGLRenderer.js(createLightSetup)Optional follow-up (not required for the crash)
In the same PCF block the kernel is
for x/y in [-3, 3](7×7 = 49 samples) but the code doesshadow /= 9.0. That should likely be49.0(or the loop range reduced to[-1, 1]if 3×3 was intended).Workaround
Until fixed: keep
DirLight.castsShadow = falseand/orMesh.receivesShadow = false.Environment
@xeokit/xeokit-sdk@2.6.112Mesh+PhongMaterial+DirLightwithcastsShadow: trueandreceivesShadow: true