Visitar URL original
[WebGPU] Shadow casters re-upload their uniform block every frame when the light moves · Issue #19086 · BabylonJS/Babylon.js · GitHub
Skip to content

[WebGPU] Shadow casters re-upload their uniform block every frame when the light moves #19086

Open

Description

@omicr0n

Summary

ShadowGenerator writes biasAndScaleSM, lightDataSM and depthValuesSM into each shadow-caster effect's own "leftover" uniform buffer on every shadow draw.

  • On WebGPU those buffers have one slot per draw context.
  • For a directional light, lightDataSM is the light direction.
  • So when the light direction changes every frame (a day/night cycle, or a moving sun), every caster's block is re-uploaded every frame, once per caster.

The value is the same for every caster in the pass, so it doesn't need to live in per-draw blocks.

Where

  • Lights/Shadows/shadowGenerator.js:948-956, in _renderSubMeshForShadowMap:
    effect.setFloat3("biasAndScaleSM", this.bias, this.normalBias, this.depthScale);
    if (this.getLight().getTypeID() === Light.LIGHTTYPEID_DIRECTIONALLIGHT) {
        effect.setVector3("lightDataSM", this._cachedDirection);
    } ...
    effect.setFloat2("depthValuesSM", ...);
  • Shader use: ShadersWGSL/ShadersInclude/shadowMapVertexNormalBias.js, where normalize(-uniforms.lightDataSM.xyz) is used for the normal bias when SM_DIRECTIONINLIGHTDATA == 1.

Repro: https://playground.babylonjs.com/#WOKT22 (WebGPU). With 50 shadow casters: 3.0 writeBuffer calls per frame with the light still, 55.0 with it rotating 0.01 degree per frame.

Measured in the game

CascadedShadowGenerator with 4 cascades, about 73 caster draws per frame, and the sun direction changing slightly every frame (race clock). queue.writeBuffer calls dropped from about 221 to about 111 per frame once lightDataSM stopped changing per draw. Almost all of the removed writes were 48- and 112-byte shadow-map leftover blocks. CPU gain is small (about 0.03–0.05 ms per frame). The bigger benefit is about 110 fewer queued commands per frame for the browser's GPU process.

Suggested fix (either)

  • Move lightDataSM, biasAndScaleSM and depthValuesSM into the per-pass scene uniform buffer the shadow generator already owns, or into a small per-generator uniform buffer, like the 9.32 CSM receiver cascades buffer.
  • For directional lights, derive the normal-bias direction in the shader from the pass's view matrix. The cascade camera looks along the light, so -vec3(view[0].z, view[1].z, view[2].z) is the normalized direction, and lightDataSM doesn't need to be declared.

Workaround we use

At startup, override the two WGSL includes in ShaderStore.IncludesShadersStoreWGSL:

  • shadowMapVertexExtraDeclaration: declare lightDataSM only when SM_DIRECTIONINLIGHTDATA == 0.
  • shadowMapVertexNormalBias: read the direction from scene.view.

The WebGPU pipeline context's setFloat3 already skips undeclared uniforms, so the per-draw write becomes a no-op. Frozen-frame captures were pixel-identical before and after in 9 of 10 test scenes, and the tenth differed only within that scene's normal run-to-run flicker.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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