Skip to content

Commit ec2ec63

Browse files
committed
fixes for light and shadow effect
Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>
1 parent 4a8b816 commit ec2ec63

File tree

2 files changed

+60
-115
lines changed

2 files changed

+60
-115
lines changed

src/effects/src/custom-deck-lighting-effect.ts

Lines changed: 24 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,43 @@
11
// SPDX-License-Identifier: MIT
22
// Copyright contributors to the kepler.gl project
33

4-
// @ts-nocheck This is a hack, don't check types
4+
// @ts-nocheck
55

6-
import {LightingEffect, shadow} from '@deck.gl/core';
6+
import {LightingEffect} from '@deck.gl/core';
77

88
/**
9-
* Inserts shader code before detected part.
10-
* @param {string} vs Original shader code.
11-
* @param {string} type Debug string.
12-
* @param {string} insertBeforeText Text chunk to insert before.
13-
* @param {string} textToInsert Text to insert.
14-
* @returns Modified shader code.
15-
*/
16-
export function insertBefore(vs, type, insertBeforeText, textToInsert) {
17-
const at = vs.indexOf(insertBeforeText);
18-
if (at < 0) {
19-
return vs;
20-
}
21-
22-
return vs.slice(0, at) + textToInsert + vs.slice(at);
23-
}
24-
25-
const CustomShadowModule = shadow ? {...shadow} : undefined;
26-
27-
/**
28-
* Custom shadow module
29-
* 1) Add u_outputUniformShadow uniform
30-
* 2) always produce full shadow when the uniform is set to true.
31-
*/
32-
if (CustomShadowModule?.fs) {
33-
CustomShadowModule.fs = insertBefore(
34-
CustomShadowModule.fs,
35-
'custom shadow #1',
36-
'uniform vec4 shadow_uColor;',
37-
'uniform bool u_outputUniformShadow;'
38-
);
39-
40-
CustomShadowModule.fs = insertBefore(
41-
CustomShadowModule.fs,
42-
'custom shadow #1',
43-
'vec4 rgbaDepth = texture2D(shadowMap, position.xy);',
44-
'if(u_outputUniformShadow) return 1.0;'
45-
);
46-
}
47-
48-
if (CustomShadowModule) {
49-
CustomShadowModule.getUniforms = (opts = {}, context = {}) => {
50-
const u = shadow.getUniforms(opts, context);
51-
if (opts.outputUniformShadow !== undefined) {
52-
u.u_outputUniformShadow = opts.outputUniformShadow;
53-
}
54-
return u;
55-
};
56-
}
57-
58-
/**
59-
* Custom LightingEffect
60-
* 1) adds CustomShadowModule
61-
* 2) pass outputUniformShadow as module parameters
62-
* 3) properly removes CustomShadowModule
9+
* Custom LightingEffect for kepler.gl.
10+
*
11+
* This is a thin wrapper around deck.gl's LightingEffect. The original
12+
* deck.gl 8 version patched the shadow shader module with custom GLSL,
13+
* but that approach doesn't work with deck.gl 9's UBO-based shadow module.
14+
*
15+
* We override getShaderModuleProps to always include dummyShadowMap in
16+
* the shadow props, even when shadows are disabled. This prevents "Bad
17+
* texture binding" errors: the shadow module's createShadowUniforms needs
18+
* a valid texture for shadow_uShadowMap0/1 bindings, and without
19+
* dummyShadowMap it would set them to undefined.
6320
*/
6421
class CustomDeckLightingEffect extends LightingEffect {
22+
useOutputUniformShadow: boolean;
23+
6524
constructor(props) {
6625
super(props);
6726
this.useOutputUniformShadow = false;
6827
}
6928

70-
preRender(context) {
71-
if (!this.shadow) return;
72-
73-
// In deck.gl 9.x, preRender receives a context object instead of positional args
74-
const device = context?.device;
75-
76-
this.shadowMatrices = this._calculateMatrices();
77-
78-
if (this.shadowPasses.length === 0) {
79-
this._createShadowPasses(device);
80-
}
81-
82-
if (!this.dummyShadowMap && device) {
83-
this.dummyShadowMap = device.createTexture({
84-
width: 1,
85-
height: 1
86-
});
87-
}
29+
getShaderModuleProps(layer, otherShaderModuleProps) {
30+
const props = super.getShaderModuleProps(layer, otherShaderModuleProps);
8831

89-
for (let i = 0; i < this.shadowPasses.length; i++) {
90-
const shadowPass = this.shadowPasses[i];
91-
shadowPass.render({
92-
layers: context.layers,
93-
layerFilter: context.layerFilter,
94-
viewports: context.viewports,
95-
onViewportActive: context.onViewportActive,
96-
views: context.views,
97-
moduleParameters: {
98-
shadowLightId: i,
99-
dummyShadowMap: this.dummyShadowMap,
100-
shadowMatrices: this.shadowMatrices,
101-
useOutputUniformShadow: false
102-
}
103-
});
32+
// When shadow is disabled, the parent returns shadow: {} without
33+
// dummyShadowMap. The shadow module's getUniforms then sets
34+
// shadow_uShadowMap0/1 to undefined, causing texture binding errors.
35+
// Always provide the dummy texture so bindings remain valid.
36+
if (props.shadow && !props.shadow.dummyShadowMap && this.dummyShadowMap) {
37+
props.shadow.dummyShadowMap = this.dummyShadowMap;
10438
}
105-
}
10639

107-
getModuleParameters(layer) {
108-
const parameters = super.getModuleParameters(layer);
109-
parameters.outputUniformShadow = this.outputUniformShadow;
110-
return parameters;
111-
}
112-
113-
cleanup() {
114-
for (const shadowPass of this.shadowPasses) {
115-
shadowPass.delete?.() || shadowPass.destroy?.();
116-
}
117-
this.shadowPasses.length = 0;
118-
this.shadowMaps.length = 0;
119-
120-
if (this.dummyShadowMap) {
121-
this.dummyShadowMap.delete?.() || this.dummyShadowMap.destroy?.();
122-
this.dummyShadowMap = null;
123-
}
124-
125-
if (this.shadow && this.programManager) {
126-
this.programManager.removeDefaultModule?.(CustomShadowModule);
127-
this.programManager = null;
128-
}
40+
return props;
12941
}
13042
}
13143

src/utils/src/effect-utils.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,20 @@ export function computeDeckEffects({
3131
return visState.effectOrder
3232
.map(effectId => {
3333
const effect = findById(effectId)(visState.effects) as Effect | undefined;
34-
if (effect?.isEnabled && effect.deckEffect) {
35-
updateEffect({visState, mapState, effect});
36-
return effect.deckEffect;
34+
if (effect?.deckEffect) {
35+
if (effect.isEnabled) {
36+
updateEffect({visState, mapState, effect});
37+
} else if (effect.type === LIGHT_AND_SHADOW_EFFECT.type) {
38+
// Keep lighting effects in the array even when disabled to avoid
39+
// removing the shadow shader module. Composite layer sublayers
40+
// don't regenerate models when default shader modules change,
41+
// leaving stale pipelines with shadow_uShadowMap bindings.
42+
// Disabling shadow on the lights avoids visual effects.
43+
disableLightingEffect(effect);
44+
}
45+
if (effect.isEnabled || effect.type === LIGHT_AND_SHADOW_EFFECT.type) {
46+
return effect.deckEffect;
47+
}
3748
}
3849
return null;
3950
})
@@ -78,11 +89,33 @@ function isDaytime(lat, lon, timestamp) {
7889
return date >= sunrise && date <= sunset;
7990
}
8091

92+
/**
93+
* Disable shadow rendering on a lighting effect without removing it.
94+
* This keeps the shadow shader module registered and prevents stale
95+
* texture binding errors in composite layer sublayers.
96+
*/
97+
function disableLightingEffect(effect: Effect) {
98+
const deckEffect = effect.deckEffect;
99+
if (!deckEffect) return;
100+
deckEffect.shadow = false;
101+
for (const light of deckEffect.directionalLights || []) {
102+
light.shadow = false;
103+
}
104+
}
105+
81106
/**
82107
* Update effect to match latest vis and map states
83108
*/
84109
function updateEffect({visState, mapState, effect}) {
85110
if (effect.type === LIGHT_AND_SHADOW_EFFECT.type) {
111+
// Re-enable shadow rendering in case it was previously disabled
112+
const deckEffect = effect.deckEffect;
113+
for (const light of deckEffect.directionalLights || []) {
114+
light._shadow = true;
115+
light.shadow = true;
116+
}
117+
deckEffect.shadow = deckEffect.directionalLights?.some(l => l.shadow) ?? false;
118+
86119
let {timestamp} = effect.parameters;
87120
const {timeMode} = effect.parameters;
88121
const sunLight = effect.deckEffect.directionalLights[0];

0 commit comments

Comments
 (0)