|
3 | 3 |
|
4 | 4 | // @ts-nocheck |
5 | 5 |
|
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(); |
7 | 61 |
|
8 | 62 | /** |
9 | 63 | * Custom LightingEffect for kepler.gl. |
10 | 64 | * |
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. |
20 | 70 | */ |
21 | 71 | class CustomDeckLightingEffect extends LightingEffect { |
22 | | - useOutputUniformShadow: boolean; |
| 72 | + outputUniformShadow: boolean; |
23 | 73 |
|
24 | 74 | constructor(props) { |
25 | 75 | 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 | + } |
27 | 99 | } |
28 | 100 |
|
29 | 101 | getShaderModuleProps(layer, otherShaderModuleProps) { |
30 | 102 | const props = super.getShaderModuleProps(layer, otherShaderModuleProps); |
31 | 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. |
| 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. |
36 | 107 | if (props.shadow && !props.shadow.dummyShadowMap && this.dummyShadowMap) { |
37 | 108 | props.shadow.dummyShadowMap = this.dummyShadowMap; |
38 | 109 | } |
39 | 110 |
|
| 111 | + // Pass outputUniformShadow through to the custom shadow module |
| 112 | + if (props.shadow) { |
| 113 | + props.shadow.outputUniformShadow = this.outputUniformShadow; |
| 114 | + } |
| 115 | + |
40 | 116 | return props; |
41 | 117 | } |
42 | 118 | } |
|
0 commit comments