@luma.gl/shaders 10.0.0-alpha.10 → 10.0.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dist.dev.js +1776 -58
- package/dist/dist.min.js +1394 -147
- package/dist/index.cjs +35 -0
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/lighting/clustered-light-grid/clustered-light-grid.cjs +40 -0
- package/dist/modules/lighting/clustered-light-grid/clustered-light-grid.d.ts +6 -0
- package/dist/modules/lighting/clustered-light-grid/clustered-light-grid.d.ts.map +1 -0
- package/dist/modules/lighting/clustered-light-grid/clustered-light-grid.js +15 -0
- package/dist/modules/lighting/clustered-light-grid/clustered-light-grid.js.map +1 -0
- package/dist/modules/lighting/contact-shadow/contact-shadow.cjs +348 -0
- package/dist/modules/lighting/contact-shadow/contact-shadow.d.ts +188 -0
- package/dist/modules/lighting/contact-shadow/contact-shadow.d.ts.map +1 -0
- package/dist/modules/lighting/contact-shadow/contact-shadow.js +307 -0
- package/dist/modules/lighting/contact-shadow/contact-shadow.js.map +1 -0
- package/dist/modules/lighting/pbr-ambient-occlusion/pbr-ambient-occlusion.cjs +87 -0
- package/dist/modules/lighting/pbr-ambient-occlusion/pbr-ambient-occlusion.d.ts +21 -0
- package/dist/modules/lighting/pbr-ambient-occlusion/pbr-ambient-occlusion.d.ts.map +1 -0
- package/dist/modules/lighting/pbr-ambient-occlusion/pbr-ambient-occlusion.js +61 -0
- package/dist/modules/lighting/pbr-ambient-occlusion/pbr-ambient-occlusion.js.map +1 -0
- package/dist/modules/lighting/pbr-contact-shadow/pbr-contact-shadow.cjs +87 -0
- package/dist/modules/lighting/pbr-contact-shadow/pbr-contact-shadow.d.ts +21 -0
- package/dist/modules/lighting/pbr-contact-shadow/pbr-contact-shadow.d.ts.map +1 -0
- package/dist/modules/lighting/pbr-contact-shadow/pbr-contact-shadow.js +61 -0
- package/dist/modules/lighting/pbr-contact-shadow/pbr-contact-shadow.js.map +1 -0
- package/dist/modules/lighting/pbr-material/pbr-material-glsl.cjs +33 -11
- package/dist/modules/lighting/pbr-material/pbr-material-glsl.d.ts +2 -2
- package/dist/modules/lighting/pbr-material/pbr-material-glsl.d.ts.map +1 -1
- package/dist/modules/lighting/pbr-material/pbr-material-glsl.js +33 -11
- package/dist/modules/lighting/pbr-material/pbr-material-glsl.js.map +1 -1
- package/dist/modules/lighting/pbr-material/pbr-material-wgsl.cjs +33 -11
- package/dist/modules/lighting/pbr-material/pbr-material-wgsl.d.ts +1 -1
- package/dist/modules/lighting/pbr-material/pbr-material-wgsl.d.ts.map +1 -1
- package/dist/modules/lighting/pbr-material/pbr-material-wgsl.js +33 -11
- package/dist/modules/lighting/pbr-material/pbr-material-wgsl.js.map +1 -1
- package/dist/modules/lighting/pbr-material/pbr-material.d.ts +3 -3
- package/dist/modules/lighting/pbr-shadow/pbr-shadow.cjs +97 -0
- package/dist/modules/lighting/pbr-shadow/pbr-shadow.d.ts +15 -0
- package/dist/modules/lighting/pbr-shadow/pbr-shadow.d.ts.map +1 -0
- package/dist/modules/lighting/pbr-shadow/pbr-shadow.js +69 -0
- package/dist/modules/lighting/pbr-shadow/pbr-shadow.js.map +1 -0
- package/dist/modules/lighting/pbr-surface/pbr-surface.cjs +114 -0
- package/dist/modules/lighting/pbr-surface/pbr-surface.d.ts +15 -0
- package/dist/modules/lighting/pbr-surface/pbr-surface.d.ts.map +1 -0
- package/dist/modules/lighting/pbr-surface/pbr-surface.js +88 -0
- package/dist/modules/lighting/pbr-surface/pbr-surface.js.map +1 -0
- package/dist/modules/lighting/prefiltered-environment/prefiltered-environment.cjs +379 -0
- package/dist/modules/lighting/prefiltered-environment/prefiltered-environment.d.ts +41 -0
- package/dist/modules/lighting/prefiltered-environment/prefiltered-environment.d.ts.map +1 -0
- package/dist/modules/lighting/prefiltered-environment/prefiltered-environment.js +345 -0
- package/dist/modules/lighting/prefiltered-environment/prefiltered-environment.js.map +1 -0
- package/dist/modules/lighting/shadow/shadow-glsl.cjs +228 -0
- package/dist/modules/lighting/shadow/shadow-glsl.d.ts +3 -0
- package/dist/modules/lighting/shadow/shadow-glsl.d.ts.map +1 -0
- package/dist/modules/lighting/shadow/shadow-glsl.js +204 -0
- package/dist/modules/lighting/shadow/shadow-glsl.js.map +1 -0
- package/dist/modules/lighting/shadow/shadow-types.cjs +15 -0
- package/dist/modules/lighting/shadow/shadow-types.d.ts +63 -0
- package/dist/modules/lighting/shadow/shadow-types.d.ts.map +1 -0
- package/dist/modules/lighting/shadow/shadow-types.js +5 -0
- package/dist/modules/lighting/shadow/shadow-types.js.map +1 -0
- package/dist/modules/lighting/shadow/shadow.cjs +458 -0
- package/dist/modules/lighting/shadow/shadow.d.ts +117 -0
- package/dist/modules/lighting/shadow/shadow.d.ts.map +1 -0
- package/dist/modules/lighting/shadow/shadow.js +450 -0
- package/dist/modules/lighting/shadow/shadow.js.map +1 -0
- package/dist/modules/lighting/unlit-material/unlit-material.cjs +60 -0
- package/dist/modules/lighting/unlit-material/unlit-material.d.ts +9 -0
- package/dist/modules/lighting/unlit-material/unlit-material.d.ts.map +1 -0
- package/dist/modules/lighting/unlit-material/unlit-material.js +40 -0
- package/dist/modules/lighting/unlit-material/unlit-material.js.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +48 -0
- package/src/modules/lighting/clustered-light-grid/clustered-light-grid.ts +17 -0
- package/src/modules/lighting/contact-shadow/contact-shadow.ts +365 -0
- package/src/modules/lighting/pbr-ambient-occlusion/pbr-ambient-occlusion.ts +78 -0
- package/src/modules/lighting/pbr-contact-shadow/pbr-contact-shadow.ts +78 -0
- package/src/modules/lighting/pbr-material/pbr-material-glsl.ts +33 -11
- package/src/modules/lighting/pbr-material/pbr-material-wgsl.ts +33 -11
- package/src/modules/lighting/pbr-shadow/pbr-shadow.ts +85 -0
- package/src/modules/lighting/pbr-surface/pbr-surface.ts +103 -0
- package/src/modules/lighting/prefiltered-environment/prefiltered-environment.ts +370 -0
- package/src/modules/lighting/shadow/shadow-glsl.ts +204 -0
- package/src/modules/lighting/shadow/shadow-types.ts +68 -0
- package/src/modules/lighting/shadow/shadow.ts +536 -0
- package/src/modules/lighting/unlit-material/unlit-material.ts +44 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
// luma.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// SPDX-FileCopyrightText: Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {SHADOW_GLSL} from './shadow-glsl';
|
|
6
|
+
import type {NumberArray3, NumberArray4, NumberArray16} from '@math.gl/core';
|
|
7
|
+
import type {ShaderModule} from '../../../lib/shader-module/shader-module';
|
|
8
|
+
import type {
|
|
9
|
+
DirectionalShadowLight,
|
|
10
|
+
PointShadowLight,
|
|
11
|
+
ShadowProps,
|
|
12
|
+
SpotShadowLight
|
|
13
|
+
} from './shadow-types';
|
|
14
|
+
|
|
15
|
+
const MAX_CASCADES = 4;
|
|
16
|
+
const MAX_SPOT_LIGHTS = 4;
|
|
17
|
+
const MAX_POINT_LIGHTS = 4;
|
|
18
|
+
const MAX_POINT_MATRICES = 24;
|
|
19
|
+
|
|
20
|
+
type DirectionalShadowUniform = {
|
|
21
|
+
direction: Readonly<NumberArray3>;
|
|
22
|
+
strength: number;
|
|
23
|
+
normalBias: number;
|
|
24
|
+
sourceAngularRadius: number;
|
|
25
|
+
cascadeBlendFraction: number;
|
|
26
|
+
farFadeFraction: number;
|
|
27
|
+
shadowDistance: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type SpotShadowUniform = {
|
|
31
|
+
position: Readonly<NumberArray3>;
|
|
32
|
+
range: number;
|
|
33
|
+
direction: Readonly<NumberArray3>;
|
|
34
|
+
outerConeCos: number;
|
|
35
|
+
sourceRadius: number;
|
|
36
|
+
normalBias: number;
|
|
37
|
+
strength: number;
|
|
38
|
+
nearPlane: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type PointShadowUniform = {
|
|
42
|
+
position: Readonly<NumberArray3>;
|
|
43
|
+
range: number;
|
|
44
|
+
sourceRadius: number;
|
|
45
|
+
normalBias: number;
|
|
46
|
+
strength: number;
|
|
47
|
+
nearPlane: number;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type ShadowUniforms = {
|
|
51
|
+
directionalLightCount: number;
|
|
52
|
+
spotLightCount: number;
|
|
53
|
+
pointLightCount: number;
|
|
54
|
+
cascadeCount: number;
|
|
55
|
+
blockerSampleCount: number;
|
|
56
|
+
filterSampleCount: number;
|
|
57
|
+
cascadeSplits: Readonly<NumberArray4>;
|
|
58
|
+
directionalViewProjectionMatrices: readonly Readonly<NumberArray16>[];
|
|
59
|
+
spotViewProjectionMatrices: readonly Readonly<NumberArray16>[];
|
|
60
|
+
pointViewProjectionMatrices: readonly Readonly<NumberArray16>[];
|
|
61
|
+
directionalLights: readonly DirectionalShadowUniform[];
|
|
62
|
+
spotLights: readonly SpotShadowUniform[];
|
|
63
|
+
pointLights: readonly PointShadowUniform[];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type ShadowBindings<TextureT = unknown, SamplerT = unknown> = {
|
|
67
|
+
directionalShadowTexture: TextureT;
|
|
68
|
+
spotShadowTexture: TextureT;
|
|
69
|
+
pointShadowTexture: TextureT;
|
|
70
|
+
shadowComparisonSampler?: SamplerT;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const DIRECTIONAL_LIGHT_UNIFORM_TYPE = {
|
|
74
|
+
direction: 'vec3<f32>',
|
|
75
|
+
strength: 'f32',
|
|
76
|
+
normalBias: 'f32',
|
|
77
|
+
sourceAngularRadius: 'f32',
|
|
78
|
+
cascadeBlendFraction: 'f32',
|
|
79
|
+
farFadeFraction: 'f32',
|
|
80
|
+
shadowDistance: 'f32'
|
|
81
|
+
} as const;
|
|
82
|
+
|
|
83
|
+
const SPOT_LIGHT_UNIFORM_TYPE = {
|
|
84
|
+
position: 'vec3<f32>',
|
|
85
|
+
range: 'f32',
|
|
86
|
+
direction: 'vec3<f32>',
|
|
87
|
+
outerConeCos: 'f32',
|
|
88
|
+
sourceRadius: 'f32',
|
|
89
|
+
normalBias: 'f32',
|
|
90
|
+
strength: 'f32',
|
|
91
|
+
nearPlane: 'f32'
|
|
92
|
+
} as const;
|
|
93
|
+
|
|
94
|
+
const POINT_LIGHT_UNIFORM_TYPE = {
|
|
95
|
+
position: 'vec3<f32>',
|
|
96
|
+
range: 'f32',
|
|
97
|
+
sourceRadius: 'f32',
|
|
98
|
+
normalBias: 'f32',
|
|
99
|
+
strength: 'f32',
|
|
100
|
+
nearPlane: 'f32'
|
|
101
|
+
} as const;
|
|
102
|
+
|
|
103
|
+
const SHADOW_WGSL = /* wgsl */ `\
|
|
104
|
+
const SHADOW_PI: f32 = 3.141592653589793;
|
|
105
|
+
|
|
106
|
+
struct DirectionalShadowLightUniform {
|
|
107
|
+
direction: vec3f,
|
|
108
|
+
strength: f32,
|
|
109
|
+
normalBias: f32,
|
|
110
|
+
sourceAngularRadius: f32,
|
|
111
|
+
cascadeBlendFraction: f32,
|
|
112
|
+
farFadeFraction: f32,
|
|
113
|
+
shadowDistance: f32,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
struct SpotShadowLightUniform {
|
|
117
|
+
position: vec3f,
|
|
118
|
+
range: f32,
|
|
119
|
+
direction: vec3f,
|
|
120
|
+
outerConeCos: f32,
|
|
121
|
+
sourceRadius: f32,
|
|
122
|
+
normalBias: f32,
|
|
123
|
+
strength: f32,
|
|
124
|
+
nearPlane: f32,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
struct PointShadowLightUniform {
|
|
128
|
+
position: vec3f,
|
|
129
|
+
range: f32,
|
|
130
|
+
sourceRadius: f32,
|
|
131
|
+
normalBias: f32,
|
|
132
|
+
strength: f32,
|
|
133
|
+
nearPlane: f32,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
struct ShadowUniforms {
|
|
137
|
+
directionalLightCount: i32,
|
|
138
|
+
spotLightCount: i32,
|
|
139
|
+
pointLightCount: i32,
|
|
140
|
+
cascadeCount: i32,
|
|
141
|
+
blockerSampleCount: i32,
|
|
142
|
+
filterSampleCount: i32,
|
|
143
|
+
cascadeSplits: vec4f,
|
|
144
|
+
directionalViewProjectionMatrices: array<mat4x4f, 4>,
|
|
145
|
+
spotViewProjectionMatrices: array<mat4x4f, 4>,
|
|
146
|
+
pointViewProjectionMatrices: array<mat4x4f, 24>,
|
|
147
|
+
directionalLights: array<DirectionalShadowLightUniform, 1>,
|
|
148
|
+
spotLights: array<SpotShadowLightUniform, 4>,
|
|
149
|
+
pointLights: array<PointShadowLightUniform, 4>,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
@group(2) @binding(auto) var<uniform> shadow: ShadowUniforms;
|
|
153
|
+
@group(2) @binding(auto) var directionalShadowTexture: texture_depth_2d_array;
|
|
154
|
+
@group(2) @binding(auto) var spotShadowTexture: texture_depth_2d_array;
|
|
155
|
+
@group(2) @binding(auto) var pointShadowTexture: texture_depth_cube_array;
|
|
156
|
+
@group(2) @binding(auto) var directionalShadowTextureSampler: sampler;
|
|
157
|
+
@group(2) @binding(auto) var shadowComparisonSampler: sampler_comparison;
|
|
158
|
+
|
|
159
|
+
fn shadow_hash(position: vec3f) -> f32 {
|
|
160
|
+
return fract(sin(dot(position, vec3f(12.9898, 78.233, 37.719))) * 43758.5453);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn shadow_diskSample(index: i32, count: i32, rotation: f32) -> vec2f {
|
|
164
|
+
let fraction = (f32(index) + 0.5) / max(f32(count), 1.0);
|
|
165
|
+
let angle = f32(index) * 2.39996323 + rotation;
|
|
166
|
+
return sqrt(fraction) * vec2f(cos(angle), sin(angle));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn shadow_project(matrix: mat4x4f, worldPosition: vec3f) -> vec3f {
|
|
170
|
+
let clip = matrix * vec4f(worldPosition, 1.0);
|
|
171
|
+
let ndc = clip.xyz / max(abs(clip.w), 0.00001);
|
|
172
|
+
return vec3f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5, ndc.z);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fn shadow_validProjection(projected: vec3f) -> bool {
|
|
176
|
+
return all(projected.xy >= vec2f(0.0)) && all(projected.xy <= vec2f(1.0)) &&
|
|
177
|
+
projected.z >= 0.0 && projected.z <= 1.0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fn shadow_directionalCascadeIndex(viewDepth: f32) -> i32 {
|
|
181
|
+
var cascadeIndex = 0;
|
|
182
|
+
for (var index: i32 = 0; index < 4; index++) {
|
|
183
|
+
if (index >= shadow.cascadeCount) { break; }
|
|
184
|
+
cascadeIndex = index;
|
|
185
|
+
if (viewDepth <= shadow.cascadeSplits[index]) { break; }
|
|
186
|
+
}
|
|
187
|
+
return cascadeIndex;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fn shadow_getDirectionalCascadeIndex(viewDepth: f32) -> i32 {
|
|
191
|
+
if (shadow.directionalLightCount == 0) { return -1; }
|
|
192
|
+
return shadow_directionalCascadeIndex(viewDepth);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
fn shadow_directionalPCSS(cascadeIndex: i32, worldPosition: vec3f, viewDepth: f32) -> f32 {
|
|
196
|
+
let projected = shadow_project(shadow.directionalViewProjectionMatrices[cascadeIndex], worldPosition);
|
|
197
|
+
if (!shadow_validProjection(projected)) { return 1.0; }
|
|
198
|
+
let dimensions = vec2f(textureDimensions(directionalShadowTexture));
|
|
199
|
+
let texel = 1.0 / dimensions;
|
|
200
|
+
let light = shadow.directionalLights[0];
|
|
201
|
+
let rotation = shadow_hash(worldPosition) * 2.0 * SHADOW_PI;
|
|
202
|
+
let searchRadius = max(texel.x, light.sourceAngularRadius * viewDepth / max(light.shadowDistance, 0.001));
|
|
203
|
+
var blockerDepth = 0.0;
|
|
204
|
+
var blockerCount = 0.0;
|
|
205
|
+
for (var index: i32 = 0; index < 24; index++) {
|
|
206
|
+
if (index >= shadow.blockerSampleCount) { break; }
|
|
207
|
+
let offset = shadow_diskSample(index, shadow.blockerSampleCount, rotation) * searchRadius;
|
|
208
|
+
let sampleDepth = textureSampleLevel(
|
|
209
|
+
directionalShadowTexture,
|
|
210
|
+
directionalShadowTextureSampler,
|
|
211
|
+
clamp(projected.xy + offset, vec2f(0.0), vec2f(1.0)),
|
|
212
|
+
cascadeIndex,
|
|
213
|
+
0
|
|
214
|
+
);
|
|
215
|
+
if (sampleDepth < projected.z) {
|
|
216
|
+
blockerDepth += sampleDepth;
|
|
217
|
+
blockerCount += 1.0;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (blockerCount == 0.0) { return 1.0; }
|
|
221
|
+
let averageBlocker = blockerDepth / blockerCount;
|
|
222
|
+
let penumbra = clamp((projected.z - averageBlocker) / max(abs(averageBlocker), 0.001), 0.0, 1.0);
|
|
223
|
+
let filterRadius = max(texel.x, searchRadius * (1.0 + penumbra * 18.0));
|
|
224
|
+
var visibility = 0.0;
|
|
225
|
+
for (var index: i32 = 0; index < 48; index++) {
|
|
226
|
+
if (index >= shadow.filterSampleCount) { break; }
|
|
227
|
+
let offset = shadow_diskSample(index, shadow.filterSampleCount, rotation) * filterRadius;
|
|
228
|
+
visibility += textureSampleCompareLevel(
|
|
229
|
+
directionalShadowTexture,
|
|
230
|
+
shadowComparisonSampler,
|
|
231
|
+
clamp(projected.xy + offset, vec2f(0.0), vec2f(1.0)),
|
|
232
|
+
cascadeIndex,
|
|
233
|
+
projected.z
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return visibility / max(f32(shadow.filterSampleCount), 1.0);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fn shadow_getDirectionalFactor(worldPosition: vec3f, worldNormal: vec3f, viewDepth: f32) -> f32 {
|
|
240
|
+
if (shadow.directionalLightCount == 0 || viewDepth <= 0.0) { return 1.0; }
|
|
241
|
+
let light = shadow.directionalLights[0];
|
|
242
|
+
if (viewDepth >= light.shadowDistance) { return 1.0; }
|
|
243
|
+
let biasedPosition = worldPosition + normalize(worldNormal) * light.normalBias;
|
|
244
|
+
let cascadeIndex = shadow_directionalCascadeIndex(viewDepth);
|
|
245
|
+
var visibility = shadow_directionalPCSS(cascadeIndex, biasedPosition, viewDepth);
|
|
246
|
+
if (cascadeIndex + 1 < shadow.cascadeCount) {
|
|
247
|
+
var cascadeStart = 0.0;
|
|
248
|
+
if (cascadeIndex > 0) { cascadeStart = shadow.cascadeSplits[cascadeIndex - 1]; }
|
|
249
|
+
let cascadeEnd = shadow.cascadeSplits[cascadeIndex];
|
|
250
|
+
let blendWidth = max((cascadeEnd - cascadeStart) * light.cascadeBlendFraction, 0.0001);
|
|
251
|
+
let blend = smoothstep(cascadeEnd - blendWidth, cascadeEnd, viewDepth);
|
|
252
|
+
if (blend > 0.0) {
|
|
253
|
+
visibility = mix(
|
|
254
|
+
visibility,
|
|
255
|
+
shadow_directionalPCSS(cascadeIndex + 1, biasedPosition, viewDepth),
|
|
256
|
+
blend
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
let fadeWidth = max(light.shadowDistance * light.farFadeFraction, 0.0001);
|
|
261
|
+
let farFade = smoothstep(light.shadowDistance - fadeWidth, light.shadowDistance, viewDepth);
|
|
262
|
+
visibility = mix(visibility, 1.0, farFade);
|
|
263
|
+
return mix(1.0, visibility, light.strength);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
fn shadow_spotPCSS(lightIndex: i32, worldPosition: vec3f) -> f32 {
|
|
267
|
+
let projected = shadow_project(shadow.spotViewProjectionMatrices[lightIndex], worldPosition);
|
|
268
|
+
if (!shadow_validProjection(projected)) { return 1.0; }
|
|
269
|
+
let light = shadow.spotLights[lightIndex];
|
|
270
|
+
let dimensions = vec2f(textureDimensions(spotShadowTexture));
|
|
271
|
+
let texel = 1.0 / dimensions;
|
|
272
|
+
let distanceToLight = length(worldPosition - light.position);
|
|
273
|
+
let searchRadius = max(texel.x, light.sourceRadius / max(distanceToLight, 0.001) * 0.5);
|
|
274
|
+
let rotation = shadow_hash(worldPosition) * 2.0 * SHADOW_PI;
|
|
275
|
+
var blockerDepth = 0.0;
|
|
276
|
+
var blockerCount = 0.0;
|
|
277
|
+
for (var index: i32 = 0; index < 24; index++) {
|
|
278
|
+
if (index >= shadow.blockerSampleCount) { break; }
|
|
279
|
+
let offset = shadow_diskSample(index, shadow.blockerSampleCount, rotation) * searchRadius;
|
|
280
|
+
let sampleDepth = textureSampleLevel(
|
|
281
|
+
spotShadowTexture,
|
|
282
|
+
directionalShadowTextureSampler,
|
|
283
|
+
clamp(projected.xy + offset, vec2f(0.0), vec2f(1.0)),
|
|
284
|
+
lightIndex,
|
|
285
|
+
0
|
|
286
|
+
);
|
|
287
|
+
if (sampleDepth < projected.z) { blockerDepth += sampleDepth; blockerCount += 1.0; }
|
|
288
|
+
}
|
|
289
|
+
if (blockerCount == 0.0) { return 1.0; }
|
|
290
|
+
let averageBlocker = blockerDepth / blockerCount;
|
|
291
|
+
let penumbra = clamp((projected.z - averageBlocker) / max(abs(averageBlocker), 0.001), 0.0, 1.0);
|
|
292
|
+
let filterRadius = max(texel.x, searchRadius * (1.0 + penumbra * 16.0));
|
|
293
|
+
var visibility = 0.0;
|
|
294
|
+
for (var index: i32 = 0; index < 48; index++) {
|
|
295
|
+
if (index >= shadow.filterSampleCount) { break; }
|
|
296
|
+
let offset = shadow_diskSample(index, shadow.filterSampleCount, rotation) * filterRadius;
|
|
297
|
+
visibility += textureSampleCompareLevel(
|
|
298
|
+
spotShadowTexture,
|
|
299
|
+
shadowComparisonSampler,
|
|
300
|
+
clamp(projected.xy + offset, vec2f(0.0), vec2f(1.0)),
|
|
301
|
+
lightIndex,
|
|
302
|
+
projected.z
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
return visibility / max(f32(shadow.filterSampleCount), 1.0);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
fn shadow_getSpotFactor(lightIndex: i32, worldPosition: vec3f, worldNormal: vec3f) -> f32 {
|
|
309
|
+
if (lightIndex < 0 || lightIndex >= shadow.spotLightCount) { return 1.0; }
|
|
310
|
+
let light = shadow.spotLights[lightIndex];
|
|
311
|
+
let fromLight = worldPosition - light.position;
|
|
312
|
+
let distanceToLight = length(fromLight);
|
|
313
|
+
let coneCos = dot(normalize(fromLight), normalize(light.direction));
|
|
314
|
+
if (distanceToLight >= light.range || coneCos < light.outerConeCos) { return 1.0; }
|
|
315
|
+
let biasedPosition = worldPosition + normalize(worldNormal) * light.normalBias;
|
|
316
|
+
return mix(1.0, shadow_spotPCSS(lightIndex, biasedPosition), light.strength);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
fn shadow_pointReferenceDepth(majorDistance: f32, nearPlane: f32, farPlane: f32) -> f32 {
|
|
320
|
+
// Match the cube-face projection and stored WebGPU depth in [0, 1].
|
|
321
|
+
return farPlane / (farPlane - nearPlane) -
|
|
322
|
+
(farPlane * nearPlane) / ((farPlane - nearPlane) * majorDistance);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
fn shadow_pointBasis(direction: vec3f) -> mat2x3f {
|
|
326
|
+
let reference = select(vec3f(0.0, 1.0, 0.0), vec3f(0.0, 0.0, 1.0), abs(direction.y) > 0.99);
|
|
327
|
+
let tangent = normalize(cross(direction, reference));
|
|
328
|
+
return mat2x3f(tangent, normalize(cross(direction, tangent)));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
fn shadow_getPointFactor(lightIndex: i32, worldPosition: vec3f, worldNormal: vec3f) -> f32 {
|
|
332
|
+
if (lightIndex < 0 || lightIndex >= shadow.pointLightCount) { return 1.0; }
|
|
333
|
+
let light = shadow.pointLights[lightIndex];
|
|
334
|
+
let biasedPosition = worldPosition + normalize(worldNormal) * light.normalBias;
|
|
335
|
+
let fromLight = biasedPosition - light.position;
|
|
336
|
+
let distanceToLight = length(fromLight);
|
|
337
|
+
if (distanceToLight >= light.range || distanceToLight <= light.nearPlane) { return 1.0; }
|
|
338
|
+
let direction = normalize(fromLight);
|
|
339
|
+
let majorDistance = max(max(abs(fromLight.x), abs(fromLight.y)), abs(fromLight.z));
|
|
340
|
+
let referenceDepth = shadow_pointReferenceDepth(majorDistance, light.nearPlane, light.range);
|
|
341
|
+
let searchRadius = light.sourceRadius / max(distanceToLight, 0.001);
|
|
342
|
+
let rotation = shadow_hash(worldPosition) * 2.0 * SHADOW_PI;
|
|
343
|
+
let basis = shadow_pointBasis(direction);
|
|
344
|
+
var blockerDepth = 0.0;
|
|
345
|
+
var blockerCount = 0.0;
|
|
346
|
+
for (var index: i32 = 0; index < 24; index++) {
|
|
347
|
+
if (index >= shadow.blockerSampleCount) { break; }
|
|
348
|
+
let disk = shadow_diskSample(index, shadow.blockerSampleCount, rotation) * searchRadius;
|
|
349
|
+
let sampleDirection = normalize(direction + basis[0] * disk.x + basis[1] * disk.y);
|
|
350
|
+
let sampleDepth = textureSampleLevel(
|
|
351
|
+
pointShadowTexture,
|
|
352
|
+
directionalShadowTextureSampler,
|
|
353
|
+
sampleDirection,
|
|
354
|
+
lightIndex,
|
|
355
|
+
0
|
|
356
|
+
);
|
|
357
|
+
if (sampleDepth < referenceDepth) { blockerDepth += sampleDepth; blockerCount += 1.0; }
|
|
358
|
+
}
|
|
359
|
+
if (blockerCount == 0.0) { return 1.0; }
|
|
360
|
+
let averageBlocker = blockerDepth / blockerCount;
|
|
361
|
+
let penumbra = clamp((referenceDepth - averageBlocker) / max(abs(averageBlocker), 0.001), 0.0, 1.0);
|
|
362
|
+
let filterRadius = searchRadius * (1.0 + penumbra * 12.0);
|
|
363
|
+
var visibility = 0.0;
|
|
364
|
+
for (var index: i32 = 0; index < 48; index++) {
|
|
365
|
+
if (index >= shadow.filterSampleCount) { break; }
|
|
366
|
+
let disk = shadow_diskSample(index, shadow.filterSampleCount, rotation) * filterRadius;
|
|
367
|
+
let sampleDirection = normalize(direction + basis[0] * disk.x + basis[1] * disk.y);
|
|
368
|
+
visibility += textureSampleCompareLevel(
|
|
369
|
+
pointShadowTexture,
|
|
370
|
+
shadowComparisonSampler,
|
|
371
|
+
sampleDirection,
|
|
372
|
+
lightIndex,
|
|
373
|
+
referenceDepth
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
visibility /= max(f32(shadow.filterSampleCount), 1.0);
|
|
377
|
+
return mix(1.0, visibility, light.strength);
|
|
378
|
+
}
|
|
379
|
+
`;
|
|
380
|
+
|
|
381
|
+
/** Backend-aware shadow sampling module. Applications explicitly apply factors to direct-light terms. */
|
|
382
|
+
export const shadow = {
|
|
383
|
+
name: 'shadow',
|
|
384
|
+
source: SHADOW_WGSL,
|
|
385
|
+
fs: SHADOW_GLSL,
|
|
386
|
+
props: {} as ShadowProps,
|
|
387
|
+
uniforms: {} as ShadowUniforms,
|
|
388
|
+
bindings: {} as ShadowBindings,
|
|
389
|
+
uniformTypes: {
|
|
390
|
+
directionalLightCount: 'i32',
|
|
391
|
+
spotLightCount: 'i32',
|
|
392
|
+
pointLightCount: 'i32',
|
|
393
|
+
cascadeCount: 'i32',
|
|
394
|
+
blockerSampleCount: 'i32',
|
|
395
|
+
filterSampleCount: 'i32',
|
|
396
|
+
cascadeSplits: 'vec4<f32>',
|
|
397
|
+
directionalViewProjectionMatrices: ['mat4x4<f32>', MAX_CASCADES],
|
|
398
|
+
spotViewProjectionMatrices: ['mat4x4<f32>', MAX_SPOT_LIGHTS],
|
|
399
|
+
pointViewProjectionMatrices: ['mat4x4<f32>', MAX_POINT_MATRICES],
|
|
400
|
+
directionalLights: [DIRECTIONAL_LIGHT_UNIFORM_TYPE, 1],
|
|
401
|
+
spotLights: [SPOT_LIGHT_UNIFORM_TYPE, MAX_SPOT_LIGHTS],
|
|
402
|
+
pointLights: [POINT_LIGHT_UNIFORM_TYPE, MAX_POINT_LIGHTS]
|
|
403
|
+
},
|
|
404
|
+
bindingLayout: [
|
|
405
|
+
{name: 'shadow', group: 2},
|
|
406
|
+
{name: 'directionalShadowTexture', group: 2},
|
|
407
|
+
{name: 'spotShadowTexture', group: 2},
|
|
408
|
+
{name: 'pointShadowTexture', group: 2},
|
|
409
|
+
{name: 'shadowComparisonSampler', group: 2}
|
|
410
|
+
],
|
|
411
|
+
getUniforms(props: Partial<ShadowProps>): Partial<ShadowUniforms & ShadowBindings> {
|
|
412
|
+
if (
|
|
413
|
+
!props.directionalLights ||
|
|
414
|
+
!props.spotLights ||
|
|
415
|
+
!props.pointLights ||
|
|
416
|
+
!props.cascadeSplits ||
|
|
417
|
+
!props.directionalViewProjectionMatrices ||
|
|
418
|
+
!props.spotViewProjectionMatrices ||
|
|
419
|
+
!props.pointViewProjectionMatrices ||
|
|
420
|
+
!props.directionalShadowTexture ||
|
|
421
|
+
!props.spotShadowTexture ||
|
|
422
|
+
!props.pointShadowTexture ||
|
|
423
|
+
!props.nonFilteringSampler ||
|
|
424
|
+
!props.comparisonSampler
|
|
425
|
+
) {
|
|
426
|
+
return {};
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
directionalLightCount: props.directionalLights.length,
|
|
430
|
+
spotLightCount: props.spotLights.length,
|
|
431
|
+
pointLightCount: props.pointLights.length,
|
|
432
|
+
cascadeCount: props.cascadeCount,
|
|
433
|
+
blockerSampleCount: props.blockerSampleCount,
|
|
434
|
+
filterSampleCount: props.filterSampleCount,
|
|
435
|
+
cascadeSplits: props.cascadeSplits as Readonly<NumberArray4>,
|
|
436
|
+
directionalViewProjectionMatrices:
|
|
437
|
+
props.directionalViewProjectionMatrices as readonly Readonly<NumberArray16>[],
|
|
438
|
+
spotViewProjectionMatrices:
|
|
439
|
+
props.spotViewProjectionMatrices as readonly Readonly<NumberArray16>[],
|
|
440
|
+
pointViewProjectionMatrices:
|
|
441
|
+
props.pointViewProjectionMatrices as readonly Readonly<NumberArray16>[],
|
|
442
|
+
directionalLights: padDirectionalLights(props.directionalLights),
|
|
443
|
+
spotLights: padSpotLights(props.spotLights),
|
|
444
|
+
pointLights: padPointLights(props.pointLights),
|
|
445
|
+
directionalShadowTexture: props.directionalShadowTexture,
|
|
446
|
+
spotShadowTexture: props.spotShadowTexture,
|
|
447
|
+
pointShadowTexture: props.pointShadowTexture,
|
|
448
|
+
...(props.directionalShadowTexture.device.type === 'webgpu'
|
|
449
|
+
? {shadowComparisonSampler: props.comparisonSampler}
|
|
450
|
+
: {})
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
} as const satisfies ShaderModule<ShadowProps, ShadowUniforms, ShadowBindings>;
|
|
454
|
+
|
|
455
|
+
function padDirectionalLights(
|
|
456
|
+
lights: readonly DirectionalShadowLight[]
|
|
457
|
+
): DirectionalShadowUniform[] {
|
|
458
|
+
const light = lights[0];
|
|
459
|
+
return [
|
|
460
|
+
light
|
|
461
|
+
? {
|
|
462
|
+
direction: light.direction,
|
|
463
|
+
strength: light.strength ?? 1,
|
|
464
|
+
normalBias: light.normalBias ?? 0.04,
|
|
465
|
+
sourceAngularRadius: light.sourceAngularRadius ?? 0.00465,
|
|
466
|
+
cascadeBlendFraction: light.cascadeBlendFraction ?? 0.1,
|
|
467
|
+
farFadeFraction: light.farFadeFraction ?? 0.1,
|
|
468
|
+
shadowDistance: light.shadowDistance ?? 1
|
|
469
|
+
}
|
|
470
|
+
: {
|
|
471
|
+
direction: [0, 1, 0],
|
|
472
|
+
strength: 0,
|
|
473
|
+
normalBias: 0,
|
|
474
|
+
sourceAngularRadius: 0,
|
|
475
|
+
cascadeBlendFraction: 0,
|
|
476
|
+
farFadeFraction: 0,
|
|
477
|
+
shadowDistance: 1
|
|
478
|
+
}
|
|
479
|
+
];
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function padSpotLights(lights: readonly SpotShadowLight[]): SpotShadowUniform[] {
|
|
483
|
+
return padArray(
|
|
484
|
+
lights.map(light => ({
|
|
485
|
+
position: light.position,
|
|
486
|
+
range: light.range,
|
|
487
|
+
direction: light.direction,
|
|
488
|
+
outerConeCos: Math.cos(light.outerConeAngle),
|
|
489
|
+
sourceRadius: light.sourceRadius ?? 0.2,
|
|
490
|
+
normalBias: light.normalBias ?? 0.025,
|
|
491
|
+
strength: light.strength ?? 1,
|
|
492
|
+
nearPlane: light.nearPlane ?? 0.1
|
|
493
|
+
})),
|
|
494
|
+
MAX_SPOT_LIGHTS,
|
|
495
|
+
{
|
|
496
|
+
position: [0, 0, 0],
|
|
497
|
+
range: 1,
|
|
498
|
+
direction: [0, -1, 0],
|
|
499
|
+
outerConeCos: 1,
|
|
500
|
+
sourceRadius: 0,
|
|
501
|
+
normalBias: 0,
|
|
502
|
+
strength: 0,
|
|
503
|
+
nearPlane: 0.1
|
|
504
|
+
}
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function padPointLights(lights: readonly PointShadowLight[]): PointShadowUniform[] {
|
|
509
|
+
return padArray(
|
|
510
|
+
lights.map(light => ({
|
|
511
|
+
position: light.position,
|
|
512
|
+
range: light.range,
|
|
513
|
+
sourceRadius: light.sourceRadius ?? 0.2,
|
|
514
|
+
normalBias: light.normalBias ?? 0.025,
|
|
515
|
+
strength: light.strength ?? 1,
|
|
516
|
+
nearPlane: light.nearPlane ?? 0.1
|
|
517
|
+
})),
|
|
518
|
+
MAX_POINT_LIGHTS,
|
|
519
|
+
{
|
|
520
|
+
position: [0, 0, 0],
|
|
521
|
+
range: 1,
|
|
522
|
+
sourceRadius: 0,
|
|
523
|
+
normalBias: 0,
|
|
524
|
+
strength: 0,
|
|
525
|
+
nearPlane: 0.1
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function padArray<T>(values: readonly T[], length: number, fallback: T): T[] {
|
|
531
|
+
const result = values.slice(0, length);
|
|
532
|
+
while (result.length < length) {
|
|
533
|
+
result.push(fallback);
|
|
534
|
+
}
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// luma.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// SPDX-FileCopyrightText: Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {ShaderModule} from '../../../lib/shader-module/shader-module';
|
|
6
|
+
|
|
7
|
+
const SOURCE_WGSL = /* wgsl */ `\
|
|
8
|
+
fn unlitMaterial_getColor(surfaceColor: vec4<f32>) -> vec4<f32> {
|
|
9
|
+
return surfaceColor;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Compatibility with the aggregate Phong/Gouraud material evaluation function.
|
|
13
|
+
fn lighting_getLightColor2(surfaceColor: vec3<f32>, cameraPosition: vec3<f32>, positionWorldspace: vec3<f32>, normalWorldspace: vec3<f32>) -> vec3<f32> {
|
|
14
|
+
return surfaceColor;
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const SOURCE_GLSL = /* glsl */ `\
|
|
19
|
+
vec4 unlitMaterial_getColor(vec4 surfaceColor) {
|
|
20
|
+
return surfaceColor;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Compatibility with the aggregate Phong/Gouraud material evaluation function.
|
|
24
|
+
vec3 lighting_getLightColor(vec3 surfaceColor, vec3 cameraPosition, vec3 positionWorldspace, vec3 normalWorldspace) {
|
|
25
|
+
return surfaceColor;
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Preserves surface RGBA without lighting dependencies, uniforms, or bindings.
|
|
31
|
+
* Use instead of a lit material module when lighting is never needed. The caller
|
|
32
|
+
* supplies color/texture sampling and remains responsible for output color space
|
|
33
|
+
* and alpha conventions. Available in both vertex and fragment stages.
|
|
34
|
+
*/
|
|
35
|
+
export const unlitMaterial: ShaderModule<Record<string, never>> = {
|
|
36
|
+
name: 'unlitMaterial',
|
|
37
|
+
source: SOURCE_WGSL,
|
|
38
|
+
vs: SOURCE_GLSL,
|
|
39
|
+
fs: SOURCE_GLSL,
|
|
40
|
+
dependencies: [],
|
|
41
|
+
bindingLayout: [],
|
|
42
|
+
uniformTypes: {},
|
|
43
|
+
defaultUniforms: {}
|
|
44
|
+
};
|