Skip to content

getShadowMap is not a function #2075

Description

@deadman2000

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions