Skip to content

Commit a199c5a

Browse files
committed
shadow and light effect - restore uniform shadow during the nighttime
Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>
1 parent ec2ec63 commit a199c5a

File tree

3 files changed

+95
-16
lines changed

3 files changed

+95
-16
lines changed

src/deckgl-augmentations.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ declare module '@deck.gl/core' {
4545
export type RGBAColor = [number, number, number, number];
4646
export type RGBColor = [number, number, number];
4747

48+
export const shadow: any;
49+
4850
export class DeckRenderer {
4951
device: any;
5052
renderBuffers: any[];

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

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,116 @@
33

44
// @ts-nocheck
55

6-
import {LightingEffect} from '@deck.gl/core';
6+
import {LightingEffect, shadow} from '@deck.gl/core';
7+
8+
/**
9+
* Insert text before a target string in shader source.
10+
*/
11+
function insertBefore(source: string, target: string, textToInsert: string): string {
12+
const at = source.indexOf(target);
13+
if (at < 0) return source;
14+
return source.slice(0, at) + textToInsert + source.slice(at);
15+
}
16+
17+
/**
18+
* Create a patched shadow module that adds `outputUniformShadow` to the
19+
* shadow UBO. When true, `shadow_getShadowWeight` returns 1.0 (full
20+
* uniform shadow) instead of sampling the shadow map. Used for nighttime
21+
* rendering to avoid partial shadows from below.
22+
*/
23+
function createCustomShadowModule() {
24+
if (!shadow) return null;
25+
26+
const mod = {...shadow};
27+
28+
// Add outputUniformShadow to the UBO block (present in both vs and fs)
29+
const uboField = ' bool outputUniformShadow;\n';
30+
mod.vs = insertBefore(mod.vs, '} shadow;', uboField);
31+
mod.fs = insertBefore(mod.fs, '} shadow;', uboField);
32+
33+
// Early return in shadow_getShadowWeight when outputUniformShadow is set
34+
mod.fs = insertBefore(
35+
mod.fs,
36+
'vec4 rgbaDepth = texture(shadowMap, position.xy);',
37+
'if (shadow.outputUniformShadow) return 1.0;\n '
38+
);
39+
40+
mod.uniformTypes = {
41+
...shadow.uniformTypes,
42+
outputUniformShadow: 'f32'
43+
};
44+
45+
// Wrap getUniforms to include outputUniformShadow in the UBO
46+
const originalGetUniforms = shadow.getUniforms;
47+
mod.getUniforms = (opts = {}, context = {}) => {
48+
const u = originalGetUniforms(opts, context);
49+
if (opts.outputUniformShadow !== undefined) {
50+
u.outputUniformShadow = opts.outputUniformShadow;
51+
} else {
52+
u.outputUniformShadow = false;
53+
}
54+
return u;
55+
};
56+
57+
return mod;
58+
}
59+
60+
const CustomShadowModule = createCustomShadowModule();
761

862
/**
963
* Custom LightingEffect for kepler.gl.
1064
*
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.
65+
* Extends deck.gl's LightingEffect with:
66+
* - A patched shadow module with `outputUniformShadow` for uniform shadow
67+
* during nighttime (avoids partial shadows from below).
68+
* - getShaderModuleProps override that always provides dummyShadowMap
69+
* to prevent "Bad texture binding" errors when shadows are disabled.
2070
*/
2171
class CustomDeckLightingEffect extends LightingEffect {
22-
useOutputUniformShadow: boolean;
72+
outputUniformShadow: boolean;
2373

2474
constructor(props) {
2575
super(props);
26-
this.useOutputUniformShadow = false;
76+
this.outputUniformShadow = false;
77+
}
78+
79+
setup(context) {
80+
this.context = context;
81+
const {device, deck} = context;
82+
if (this.shadow && !this.dummyShadowMap) {
83+
this._createShadowPasses(device);
84+
deck._addDefaultShaderModule(CustomShadowModule || shadow);
85+
this.dummyShadowMap = device.createTexture({width: 1, height: 1});
86+
}
87+
}
88+
89+
cleanup(context) {
90+
for (const shadowPass of this.shadowPasses) {
91+
shadowPass.delete();
92+
}
93+
this.shadowPasses.length = 0;
94+
if (this.dummyShadowMap) {
95+
this.dummyShadowMap.destroy();
96+
this.dummyShadowMap = null;
97+
context.deck._removeDefaultShaderModule(CustomShadowModule || shadow);
98+
}
2799
}
28100

29101
getShaderModuleProps(layer, otherShaderModuleProps) {
30102
const props = super.getShaderModuleProps(layer, otherShaderModuleProps);
31103

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.
104+
// Always provide dummyShadowMap so texture bindings are never undefined.
105+
// Prevents "Bad texture binding" errors in composite layer sublayers
106+
// when shadows are disabled.
36107
if (props.shadow && !props.shadow.dummyShadowMap && this.dummyShadowMap) {
37108
props.shadow.dummyShadowMap = this.dummyShadowMap;
38109
}
39110

111+
// Pass outputUniformShadow through to the custom shadow module
112+
if (props.shadow) {
113+
props.shadow.outputUniformShadow = this.outputUniformShadow;
114+
}
115+
40116
return props;
41117
}
42118
}

src/utils/src/effect-utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ function disableLightingEffect(effect: Effect) {
9898
const deckEffect = effect.deckEffect;
9999
if (!deckEffect) return;
100100
deckEffect.shadow = false;
101+
deckEffect.outputUniformShadow = false;
101102
for (const light of deckEffect.directionalLights || []) {
102103
light.shadow = false;
103104
}

0 commit comments

Comments
 (0)