@forgeax/engine-shader 0.1.20 → 0.1.23
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/README.md +10 -0
- package/dist/ShaderRegistry.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +33070 -8
- package/dist/index.mjs.map +1 -1
- package/dist/ltc/provenance.d.ts +17 -0
- package/dist/ltc/provenance.d.ts.map +1 -0
- package/dist/ltc/tables.d.ts +22 -0
- package/dist/ltc/tables.d.ts.map +1 -0
- package/dist/material/artifact-types.d.ts +52 -0
- package/dist/material/artifact-types.d.ts.map +1 -1
- package/dist/material-schemas.d.ts +5 -0
- package/dist/material-schemas.d.ts.map +1 -1
- package/dist/register-default-sprite-lit.d.ts +3 -6
- package/dist/register-default-sprite-lit.d.ts.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ShaderRegistry.ts +65 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +30 -4
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +7 -0
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +138 -30
- package/src/__tests__/direct-light-layout.unit.test.ts +40 -0
- package/src/__tests__/ibl-irradiance.unit.test.ts +7 -4
- package/src/__tests__/ibl-sampling.unit.test.ts +3 -4
- package/src/__tests__/lighting-point-spot-volume.unit.test.ts +16 -2
- package/src/__tests__/lighting-punctual.unit.test.ts +61 -8
- package/src/__tests__/lighting-spot-volume-attenuation.unit.test.ts +1 -1
- package/src/__tests__/ltc-provenance.unit.test.ts +33 -0
- package/src/__tests__/manifest.fixture.json +33 -0
- package/src/__tests__/material-contract.unit.test.ts +61 -1
- package/src/__tests__/material-derived-builtins.integration.test.ts +27 -0
- package/src/__tests__/probe-lighting-composition.unit.test.ts +38 -0
- package/src/__tests__/rect-area-brdf.unit.test.ts +36 -0
- package/src/__tests__/reflection-probe-sampling.unit.test.ts +45 -0
- package/src/__tests__/scene-temporal.unit.test.ts +20 -0
- package/src/__tests__/shader.unit.test.ts +40 -15
- package/src/__tests__/shadow-pcss.unit.test.ts +131 -0
- package/src/__tests__/spot-cookie-composition.unit.test.ts +57 -0
- package/src/__tests__/spot-modifier-composition.unit.test.ts +19 -0
- package/src/__tests__/sprite-lit-shader.test.ts +24 -11
- package/src/__tests__/sprite-variants.unit.test.ts +5 -1
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +7 -1
- package/src/__tests__/transparent-pbr.unit.test.ts +7 -0
- package/src/brdf.wgsl +115 -1
- package/src/common.wgsl +95 -91
- package/src/default-standard-pbr-skin.wgsl +108 -92
- package/src/default-standard-pbr.wgsl +189 -191
- package/src/fxaa.wgsl +27 -6
- package/src/ibl-irradiance.wgsl +4 -1
- package/src/ibl-sampling.wgsl +56 -2
- package/src/index.ts +23 -0
- package/src/lighting-directional.wgsl +161 -132
- package/src/lighting-probe.wgsl +51 -0
- package/src/lighting-punctual.wgsl +69 -29
- package/src/lighting-rect-area.wgsl +162 -0
- package/src/lighting-spot-modifiers.wgsl +93 -0
- package/src/ltc/provenance.ts +19 -0
- package/src/ltc/tables.ts +2831 -0
- package/src/material/artifact-types.ts +137 -0
- package/src/material-schemas.ts +12 -0
- package/src/register-default-sprite-lit.ts +3 -6
- package/src/scene-temporal.wgsl +14 -8
- package/src/shadow-pcf.wgsl +44 -1
- package/src/sprite-lit.wgsl +37 -53
- package/src/sprite.wgsl +1 -1
- package/src/standard-cluster.wgsl +274 -0
- package/src/tonemap.wgsl +8 -3
- package/src/types.ts +2 -1
- package/src/volume/volume-inject.wgsl +48 -38
- package/src/volume/volume-integrate.wgsl +47 -42
- package/dist/.tsbuildinfo +0 -1
- package/src/hdrp-cluster-forward.wgsl +0 -224
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// standard-cluster.wgsl — Standard clustered punctual light evaluation.
|
|
2
|
+
// feat-20260608-cluster-lighting M4 / w17.
|
|
3
|
+
//
|
|
4
|
+
// The cluster module owns membership and raw payload decoding only. Punctual
|
|
5
|
+
// BRDF, range, cone, PCF, and fail-open behavior live in lighting-punctual.
|
|
6
|
+
// ProbeBlendRecord is intentionally absent: probes are not DirectLightSlot
|
|
7
|
+
// members and this cluster owner never performs an all-probe loop.
|
|
8
|
+
|
|
9
|
+
#define_import_path forgeax_standard::cluster
|
|
10
|
+
|
|
11
|
+
#import forgeax_pbr::lighting_punctual::{evalPoint, evalPointFlat, evalSpot, evalSpotFlat, evalSpotShadowed}
|
|
12
|
+
#import forgeax_pbr::lighting_attenuation::{projectSpotUv}
|
|
13
|
+
#import forgeax_pbr::lighting_spot_modifiers::{spotModifierFactors}
|
|
14
|
+
#import forgeax_pbr::lighting_rect_area::{evalRectAreaLtcGgx}
|
|
15
|
+
#import forgeax_view::common::{view}
|
|
16
|
+
#ifdef PROJECTOR_AVAILABLE
|
|
17
|
+
#ifdef EXTENDED_LIGHTING_AVAILABLE
|
|
18
|
+
#import forgeax_view::common::{spotModifierSampler, cookieTexture}
|
|
19
|
+
#else
|
|
20
|
+
#ifndef EXTENDED_LIGHTING_AVAILABLE
|
|
21
|
+
#import forgeax_view::common::{projectorTexture, projectorSampler}
|
|
22
|
+
#endif
|
|
23
|
+
#endif
|
|
24
|
+
#endif
|
|
25
|
+
#ifdef POINT_SHADOW_AVAILABLE
|
|
26
|
+
#import forgeax_pbr::lighting_punctual::{evalPointShadowed}
|
|
27
|
+
#import forgeax_view::common::{shadowParams}
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
31
|
+
|
|
32
|
+
const KIND_POINT: u32 = 0u;
|
|
33
|
+
const KIND_SPOT: u32 = 1u;
|
|
34
|
+
const KIND_RECT_AREA: u32 = 2u;
|
|
35
|
+
const PROJECTOR_FLAG: i32 = 0x40000000;
|
|
36
|
+
const TILE_MASK: i32 = 0x3fffffff;
|
|
37
|
+
|
|
38
|
+
// DirectLightSlot is the byte-frozen 80B std430/std140 transport contract.
|
|
39
|
+
// The named rows match the host packer; metadata carries kind and identities.
|
|
40
|
+
struct DirectLightSlot {
|
|
41
|
+
position : vec4<f32>,
|
|
42
|
+
colorTimesIntensity : vec4<f32>,
|
|
43
|
+
direction : vec4<f32>,
|
|
44
|
+
auxiliary : vec4<f32>,
|
|
45
|
+
metadata : vec4<u32>,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const DIRECT_LIGHT_SLOT_BYTE_SIZE: u32 = 80u;
|
|
49
|
+
|
|
50
|
+
struct ClusterUniform {
|
|
51
|
+
grid : vec4<u32>,
|
|
52
|
+
near_far_log : vec4<f32>,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
@group(2) @binding(3) var<storage, read> light_data : array<DirectLightSlot, 256>;
|
|
56
|
+
@group(2) @binding(4) var<storage, read> cluster_grid : array<u32>;
|
|
57
|
+
@group(2) @binding(5) var<storage, read> light_index_list : array<u32>;
|
|
58
|
+
@group(2) @binding(6) var<uniform> cluster_uniform : ClusterUniform;
|
|
59
|
+
|
|
60
|
+
fn get_ssao_intensity() -> f32 {
|
|
61
|
+
return cluster_uniform.near_far_log.w;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn view_z_to_z_slice(
|
|
65
|
+
view_z : f32,
|
|
66
|
+
grid_z : u32,
|
|
67
|
+
near : f32,
|
|
68
|
+
far : f32,
|
|
69
|
+
log_far_over_near : f32,
|
|
70
|
+
) -> u32 {
|
|
71
|
+
if (view_z >= -near) {
|
|
72
|
+
return 0u;
|
|
73
|
+
}
|
|
74
|
+
let slice = floor(log(-view_z / near) / log_far_over_near * f32(grid_z));
|
|
75
|
+
let u_slice = u32(slice);
|
|
76
|
+
if (u_slice >= grid_z) {
|
|
77
|
+
return grid_z - 1u;
|
|
78
|
+
}
|
|
79
|
+
return u_slice;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn ndc_position_to_cluster(
|
|
83
|
+
ndc : vec3<f32>,
|
|
84
|
+
view_z : f32,
|
|
85
|
+
grid_x : u32,
|
|
86
|
+
grid_y : u32,
|
|
87
|
+
grid_z : u32,
|
|
88
|
+
near : f32,
|
|
89
|
+
far : f32,
|
|
90
|
+
log_far : f32,
|
|
91
|
+
) -> vec3<u32> {
|
|
92
|
+
let cx = clamp(u32(floor((ndc.x * 0.5 + 0.5) * f32(grid_x))), 0u, grid_x - 1u);
|
|
93
|
+
let cy = clamp(u32(floor((ndc.y * 0.5 + 0.5) * f32(grid_y))), 0u, grid_y - 1u);
|
|
94
|
+
let cz = view_z_to_z_slice(view_z, grid_z, near, far, log_far);
|
|
95
|
+
return vec3(cx, cy, cz);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Surface SpotLight projector sampling is owned by the same clustered light
|
|
99
|
+
// evaluator as the analytic cone/range contribution. The host marks the
|
|
100
|
+
// selected projector in the DirectLightSlot flag and writes its matrix to the
|
|
101
|
+
// corresponding View lane (lane 0 for projector-only); no second
|
|
102
|
+
// material-local sampling loop or resource owner is allowed.
|
|
103
|
+
fn sampleStandardSpotProjector(
|
|
104
|
+
lightViewProj : mat4x4<f32>,
|
|
105
|
+
world_pos : vec3<f32>,
|
|
106
|
+
metadata : vec4<u32>,
|
|
107
|
+
) -> vec3<f32> {
|
|
108
|
+
#ifdef PROJECTOR_AVAILABLE
|
|
109
|
+
let uv = projectSpotUv(lightViewProj, world_pos);
|
|
110
|
+
if (!(uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0)) {
|
|
111
|
+
return vec3<f32>(1.0);
|
|
112
|
+
}
|
|
113
|
+
#ifdef EXTENDED_LIGHTING_AVAILABLE
|
|
114
|
+
if (metadata.w == 0xffffffffu) {
|
|
115
|
+
return vec3<f32>(1.0);
|
|
116
|
+
}
|
|
117
|
+
return textureSampleLevel(cookieTexture, spotModifierSampler, uv, metadata.w, 0.0).rgb;
|
|
118
|
+
#else
|
|
119
|
+
return textureSampleLevel(projectorTexture, projectorSampler, uv, 0.0).rgb;
|
|
120
|
+
#endif
|
|
121
|
+
#else
|
|
122
|
+
return vec3<f32>(1.0);
|
|
123
|
+
#endif
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Decode one kind first, then delegate the complete evaluation to the shared
|
|
127
|
+
// punctual owner. Unknown kinds are explicitly zero contribution.
|
|
128
|
+
fn evaluate_cluster_light(
|
|
129
|
+
light : DirectLightSlot,
|
|
130
|
+
world_pos : vec3<f32>,
|
|
131
|
+
normal : vec3<f32>,
|
|
132
|
+
view_dir : vec3<f32>,
|
|
133
|
+
base_color : vec3<f32>,
|
|
134
|
+
metallic : f32,
|
|
135
|
+
alpha_sq : f32,
|
|
136
|
+
f0 : vec3<f32>,
|
|
137
|
+
flat_2d : bool,
|
|
138
|
+
) -> vec3<f32> {
|
|
139
|
+
let kind = light.metadata.x;
|
|
140
|
+
if (kind == KIND_POINT) {
|
|
141
|
+
if (flat_2d) {
|
|
142
|
+
return evalPointFlat(
|
|
143
|
+
light.position.xyz, light.colorTimesIntensity.xyz, light.position.w,
|
|
144
|
+
world_pos, base_color,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
#ifdef POINT_SHADOW_AVAILABLE
|
|
148
|
+
let layer = bitcast<i32>(light.metadata.y);
|
|
149
|
+
if (layer >= 0) {
|
|
150
|
+
let shadow = shadowParams[layer];
|
|
151
|
+
return evalPointShadowed(
|
|
152
|
+
light.position.xyz, light.colorTimesIntensity.xyz, light.position.w,
|
|
153
|
+
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
154
|
+
layer,
|
|
155
|
+
shadow.x,
|
|
156
|
+
shadow.y,
|
|
157
|
+
0.005, 0.05,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
#endif
|
|
161
|
+
return evalPoint(
|
|
162
|
+
light.position.xyz, light.colorTimesIntensity.xyz, light.position.w,
|
|
163
|
+
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (kind == KIND_SPOT) {
|
|
167
|
+
if (flat_2d) {
|
|
168
|
+
return evalSpotFlat(
|
|
169
|
+
light.position.xyz, light.direction.xyz, light.colorTimesIntensity.xyz,
|
|
170
|
+
light.colorTimesIntensity.w, light.direction.w, light.position.w,
|
|
171
|
+
world_pos, base_color,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
let modifierFactors = spotModifierFactors(
|
|
175
|
+
light.position.xyz,
|
|
176
|
+
light.direction.xyz,
|
|
177
|
+
light.direction.w,
|
|
178
|
+
world_pos,
|
|
179
|
+
light.auxiliary.w,
|
|
180
|
+
light.metadata,
|
|
181
|
+
);
|
|
182
|
+
let modifier = vec3<f32>(modifierFactors.x) * modifierFactors.yzw;
|
|
183
|
+
let encoded_tile = bitcast<i32>(light.metadata.y);
|
|
184
|
+
// The optional projector marker shares the non-negative shadow identity
|
|
185
|
+
// lane. The direct-light packer keeps the lower tile bits intact.
|
|
186
|
+
var projector_selected = false;
|
|
187
|
+
var tile : i32 = encoded_tile;
|
|
188
|
+
if (encoded_tile >= 0 && (encoded_tile & PROJECTOR_FLAG) != 0) {
|
|
189
|
+
projector_selected = true;
|
|
190
|
+
tile = encoded_tile & TILE_MASK;
|
|
191
|
+
}
|
|
192
|
+
if (tile >= 0 && tile < 4) {
|
|
193
|
+
return evalSpotShadowed(
|
|
194
|
+
light.position.xyz, light.direction.xyz, light.colorTimesIntensity.xyz,
|
|
195
|
+
// DirectLightSlot packs cosInner in color.w and cosOuter in direction.w;
|
|
196
|
+
// keep the evaluator's named cone order intact at the decode boundary.
|
|
197
|
+
light.colorTimesIntensity.w, light.direction.w, light.position.w,
|
|
198
|
+
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
199
|
+
view.spotLightViewProj[tile], tile, 0.005, 0.05,
|
|
200
|
+
3.0,
|
|
201
|
+
1.0,
|
|
202
|
+
) * modifier
|
|
203
|
+
#ifdef PROJECTOR_AVAILABLE
|
|
204
|
+
* select(
|
|
205
|
+
vec3<f32>(1.0),
|
|
206
|
+
sampleStandardSpotProjector(view.spotLightViewProj[tile], world_pos, light.metadata),
|
|
207
|
+
projector_selected,
|
|
208
|
+
)
|
|
209
|
+
#endif
|
|
210
|
+
;
|
|
211
|
+
}
|
|
212
|
+
return evalSpot(
|
|
213
|
+
light.position.xyz, light.direction.xyz, light.colorTimesIntensity.xyz,
|
|
214
|
+
light.colorTimesIntensity.w, light.direction.w, light.position.w,
|
|
215
|
+
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
216
|
+
) * modifier;
|
|
217
|
+
}
|
|
218
|
+
if (kind == KIND_RECT_AREA) {
|
|
219
|
+
return evalRectAreaLtcGgx(
|
|
220
|
+
light.position.xyz,
|
|
221
|
+
light.colorTimesIntensity.xyz,
|
|
222
|
+
light.auxiliary.xyz,
|
|
223
|
+
light.direction.xyz,
|
|
224
|
+
light.colorTimesIntensity.w,
|
|
225
|
+
light.direction.w,
|
|
226
|
+
light.position.w,
|
|
227
|
+
world_pos,
|
|
228
|
+
normal,
|
|
229
|
+
view_dir,
|
|
230
|
+
base_color,
|
|
231
|
+
metallic,
|
|
232
|
+
alpha_sq,
|
|
233
|
+
f0,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return vec3<f32>(0.0);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fn evaluateStandardClusterLights(
|
|
240
|
+
ndc : vec3<f32>,
|
|
241
|
+
view_z : f32,
|
|
242
|
+
world_pos : vec3<f32>,
|
|
243
|
+
normal : vec3<f32>,
|
|
244
|
+
view_dir : vec3<f32>,
|
|
245
|
+
base_color : vec3<f32>,
|
|
246
|
+
metallic : f32,
|
|
247
|
+
alpha_sq : f32,
|
|
248
|
+
f0 : vec3<f32>,
|
|
249
|
+
flat_2d : bool,
|
|
250
|
+
) -> vec3<f32> {
|
|
251
|
+
let gx = cluster_uniform.grid.x;
|
|
252
|
+
let gy = cluster_uniform.grid.y;
|
|
253
|
+
let gz = cluster_uniform.grid.z;
|
|
254
|
+
let near = cluster_uniform.near_far_log.x;
|
|
255
|
+
let far = cluster_uniform.near_far_log.y;
|
|
256
|
+
let log_far = cluster_uniform.near_far_log.z;
|
|
257
|
+
var total_radiance = vec3<f32>(0.0);
|
|
258
|
+
|
|
259
|
+
let cluster_idx = ndc_position_to_cluster(ndc, view_z, gx, gy, gz, near, far, log_far);
|
|
260
|
+
let cluster_linear = cluster_idx.z * gy * gx + cluster_idx.y * gx + cluster_idx.x;
|
|
261
|
+
let grid_offset = cluster_linear * 2u;
|
|
262
|
+
let list_offset = cluster_grid[grid_offset];
|
|
263
|
+
let list_count = cluster_grid[grid_offset + 1u];
|
|
264
|
+
for (var i = 0u; i < list_count; i = i + 1u) {
|
|
265
|
+
let light = light_data[light_index_list[list_offset + i]];
|
|
266
|
+
total_radiance += evaluate_cluster_light(
|
|
267
|
+
light, world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
268
|
+
flat_2d,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return total_radiance;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#endif // CLUSTER_FORWARD_AVAILABLE
|
package/src/tonemap.wgsl
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
#import forgeax_view::common::FullscreenOutput
|
|
11
11
|
#import forgeax_view::common::fullscreen_triangle
|
|
12
|
-
#import forgeax_view::common::linearToSrgbOetf
|
|
12
|
+
#import forgeax_view::common::{linearToSrgbOetf, ditherUnorm8}
|
|
13
13
|
|
|
14
14
|
const TONEMAP_LUMINANCE_EPSILON : f32 = 1e-5;
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ struct TonemapParams {
|
|
|
17
17
|
exposure : f32,
|
|
18
18
|
whitePoint : f32,
|
|
19
19
|
mode : u32,
|
|
20
|
-
|
|
20
|
+
ditherEnabled : f32,
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
@group(1) @binding(0) var hdr : texture_2d<f32>;
|
|
@@ -171,5 +171,10 @@ fn fs_main(in : FullscreenOutput) -> @location(0) vec4<f32> {
|
|
|
171
171
|
}
|
|
172
172
|
// Every public mode reaches exactly one shared sRGB OETF.
|
|
173
173
|
let encoded = linearToSrgbOetf(mapped);
|
|
174
|
-
|
|
174
|
+
let output = select(
|
|
175
|
+
encoded,
|
|
176
|
+
ditherUnorm8(encoded, in.position.xy),
|
|
177
|
+
params.ditherEnabled > 0.5,
|
|
178
|
+
);
|
|
179
|
+
return vec4<f32>(output, source.a);
|
|
175
180
|
}
|
package/src/types.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* no longer carry a bindingLayout sidecar (§Change stance: no v1/v2
|
|
18
18
|
* dual-path, no incremental drop window).
|
|
19
19
|
*/
|
|
20
|
+
import type { MaterialShaderArtifactReceipt } from './material/artifact-types.js';
|
|
20
21
|
export interface MaterialShaderManifestVariant {
|
|
21
22
|
readonly definesKey: string;
|
|
22
23
|
readonly defines: Record<string, boolean>;
|
|
@@ -33,7 +34,7 @@ export interface MaterialShaderManifestVariant {
|
|
|
33
34
|
*
|
|
34
35
|
* For Engine.create() internal use only; AI users never see this type.
|
|
35
36
|
*/
|
|
36
|
-
export interface MaterialShaderManifestEntry {
|
|
37
|
+
export interface MaterialShaderManifestEntry extends Partial<MaterialShaderArtifactReceipt> {
|
|
37
38
|
readonly identifier: string;
|
|
38
39
|
readonly sourcePath: string;
|
|
39
40
|
readonly composedWgsl: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#define_import_path forgeax_view::volume_inject
|
|
2
2
|
#import forgeax_pbr::shadow_pcf::{sample_shadow_2d_kernel}
|
|
3
3
|
#import forgeax_pbr::lighting_attenuation::{projectSpotUv}
|
|
4
|
+
#import forgeax_view::common::{DirectLightSlot}
|
|
4
5
|
// forgeax_pbr::lighting_punctual exports the surface-equivalent
|
|
5
6
|
// evalVolumePoint and evalVolumeSpot contract; this stage consumes their
|
|
6
7
|
// shared projector and shadow visibility facts without a second light owner.
|
|
@@ -9,7 +10,8 @@ struct View {
|
|
|
9
10
|
worldViewProj : mat4x4<f32>, lightDir : vec3<f32>, lightColor : vec3<f32>, cameraPos : vec3<f32>,
|
|
10
11
|
lightViewProj_A : mat4x4<f32>, inverseViewProj : mat4x4<f32>, lightViewProj_B : mat4x4<f32>,
|
|
11
12
|
lightViewProj_C : mat4x4<f32>, lightViewProj_D : mat4x4<f32>, splitPlanes : array<vec4<f32>,4>,
|
|
12
|
-
cascadeCount : f32, cascadeBlend : f32, depthBias : f32, normalBias : f32,
|
|
13
|
+
cascadeCount : f32, cascadeBlend : f32, depthBias : f32, normalBias : f32,
|
|
14
|
+
directionalShadowFilter : vec4<f32>,
|
|
13
15
|
spotLightViewProj : array<mat4x4<f32>,4>, temporalCurrentViewProj : mat4x4<f32>,
|
|
14
16
|
temporalPreviousViewProj : mat4x4<f32>, temporalProjection : vec4<f32>, temporalPreviousCameraPos : vec4<f32>,
|
|
15
17
|
};
|
|
@@ -28,41 +30,18 @@ struct VolumeParams {
|
|
|
28
30
|
optics : vec4<f32>,
|
|
29
31
|
};
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
invRangeSquared : f32,
|
|
34
|
-
colorTimesIntensity : vec3<f32>,
|
|
35
|
-
cosInner : f32,
|
|
36
|
-
direction : vec3<f32>,
|
|
37
|
-
cosOuter : f32,
|
|
38
|
-
depthBias : f32,
|
|
39
|
-
normalBias : f32,
|
|
40
|
-
pcfKernelSize : f32,
|
|
41
|
-
shadowAtlasTile : i32,
|
|
42
|
-
shadowIntensity : f32,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
struct SpotLightsArray {
|
|
46
|
-
count : u32,
|
|
47
|
-
slots : array<SpotLight, 4>,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
struct PointLight {
|
|
51
|
-
position : vec3<f32>,
|
|
52
|
-
invRangeSquared : f32,
|
|
53
|
-
colorTimesIntensity : vec3<f32>,
|
|
54
|
-
shadowAtlasLayer : i32,
|
|
55
|
-
};
|
|
33
|
+
const DIRECT_LIGHT_METADATA_SENTINEL : u32 = 0xffffffffu;
|
|
34
|
+
const DIRECT_LIGHT_TILE_MASK : u32 = 0x3fffffffu;
|
|
56
35
|
|
|
57
|
-
struct
|
|
58
|
-
|
|
59
|
-
|
|
36
|
+
struct ClusterUniform {
|
|
37
|
+
grid : vec4<u32>,
|
|
38
|
+
near_far_log : vec4<f32>,
|
|
60
39
|
};
|
|
61
40
|
|
|
62
41
|
@group(0) @binding(5) var<uniform> volume_params : VolumeParams;
|
|
63
42
|
@group(0) @binding(6) var volume_froxel : texture_storage_2d_array<rgba8unorm, write>;
|
|
64
|
-
@group(0) @binding(7) var<storage, read>
|
|
65
|
-
@group(0) @binding(8) var<
|
|
43
|
+
@group(0) @binding(7) var<storage, read> light_data : array<DirectLightSlot, 256>;
|
|
44
|
+
@group(0) @binding(8) var<uniform> cluster_uniform : ClusterUniform;
|
|
66
45
|
@group(0) @binding(0) var<uniform> view : View;
|
|
67
46
|
|
|
68
47
|
fn reconstruct_world(uv : vec2<f32>, depth : f32) -> vec3<f32> {
|
|
@@ -143,18 +122,49 @@ fn volume_cascade(world_position : vec3<f32>, view_depth : f32) -> f32 {
|
|
|
143
122
|
if (!(tile_uv.x >= 0.0 && tile_uv.x <= 1.0 && tile_uv.y >= 0.0 && tile_uv.y <= 1.0 && projected.z <= 1.0)) { return 1.0; }
|
|
144
123
|
let uv = tile_uv * tile_scale + vec2<f32>(tile) * tile_scale;
|
|
145
124
|
let shadow_size = vec2<f32>(textureDimensions(shadowMap));
|
|
125
|
+
let filter_profile = clamp(u32(round(view.directionalShadowFilter.x)), 1u, 5u);
|
|
126
|
+
// Volumetric injection currently owns a depth-only PCF receiver. Directional
|
|
127
|
+
// PCSS profiles remain an explicit capability fallback here: the surface
|
|
128
|
+
// directional owner performs blocker search, while volume keeps the same
|
|
129
|
+
// accepted filter carrier and uses the stable PCF3 receiver instead of
|
|
130
|
+
// misreading profiles 4/5 as a kernel width.
|
|
131
|
+
let volume_kernel = select(
|
|
132
|
+
select(select(3.0, 5.0, filter_profile == 3u), 1.0, filter_profile == 1u),
|
|
133
|
+
3.0,
|
|
134
|
+
filter_profile >= 4u,
|
|
135
|
+
);
|
|
146
136
|
return sample_shadow_2d_kernel(
|
|
147
137
|
shadowMap, shadowSampler, uv, 1.0 / shadow_size, projected.z,
|
|
148
|
-
0.0, view.depthBias, 1.0,
|
|
138
|
+
0.0, view.depthBias, 1.0, volume_kernel,
|
|
149
139
|
);
|
|
150
140
|
}
|
|
151
141
|
|
|
142
|
+
fn shadowAtlasTile(light : DirectLightSlot) -> i32 {
|
|
143
|
+
let encoded = light.metadata.y;
|
|
144
|
+
if (encoded == DIRECT_LIGHT_METADATA_SENTINEL) { return -1; }
|
|
145
|
+
// Projector-selected spots carry the marker in bit 30 and retain the
|
|
146
|
+
// selected shadow tile in the lower 30 bits. A projector-only spot uses
|
|
147
|
+
// tile zero, matching the surface projector contract.
|
|
148
|
+
let tile = encoded & DIRECT_LIGHT_TILE_MASK;
|
|
149
|
+
if (tile >= 4u) { return -1; }
|
|
150
|
+
return i32(tile);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fn selected_cluster_light_slot(value : f32) -> i32 {
|
|
154
|
+
let slot = i32(round(value));
|
|
155
|
+
if (slot < 0) { return -1; }
|
|
156
|
+
let index = u32(slot);
|
|
157
|
+
if (index >= cluster_uniform.grid.w || index >= 256u) { return -1; }
|
|
158
|
+
return slot;
|
|
159
|
+
}
|
|
160
|
+
|
|
152
161
|
fn spot_shadow_visibility_at(world_position : vec3<f32>) -> f32 {
|
|
153
|
-
let slot =
|
|
154
|
-
if (slot
|
|
155
|
-
let light =
|
|
156
|
-
|
|
157
|
-
|
|
162
|
+
let slot = selected_cluster_light_slot(volume_params.light_color.w);
|
|
163
|
+
if (slot < 0) { return 0.0; }
|
|
164
|
+
let light = light_data[u32(slot)];
|
|
165
|
+
let encodedTile = shadowAtlasTile(light);
|
|
166
|
+
if (encodedTile < 0) { return 1.0; }
|
|
167
|
+
let tile = u32(encodedTile);
|
|
158
168
|
let projectorUv = projectSpotUv(view.spotLightViewProj[tile], world_position);
|
|
159
169
|
// The accepted projector tuple is sampled with textureSampleLevel by the
|
|
160
170
|
// surface and volume owners when a projector texture is present.
|
|
@@ -174,7 +184,7 @@ fn spot_shadow_visibility_at(world_position : vec3<f32>) -> f32 {
|
|
|
174
184
|
let shadow_size = vec2<f32>(textureDimensions(shadowMap));
|
|
175
185
|
return sample_shadow_2d_kernel(
|
|
176
186
|
shadowMap, shadowSampler, atlas_uv, 1.0 / shadow_size, projected.z,
|
|
177
|
-
light.
|
|
187
|
+
light.auxiliary.y, light.auxiliary.x, 1.0, 3.0,
|
|
178
188
|
);
|
|
179
189
|
}
|
|
180
190
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#define_import_path forgeax_view::volume_integrate
|
|
2
2
|
// forgeax_pbr::lighting_punctual remains the surface contract; the isolated
|
|
3
3
|
// attenuation module below provides its resource-free volume equivalents.
|
|
4
|
+
#import forgeax_pbr::lighting_attenuation::{projectSpotUv}
|
|
5
|
+
#import forgeax_view::common::{DirectLightSlot}
|
|
4
6
|
|
|
5
7
|
struct VolumeView {
|
|
6
8
|
_prefix : array<vec4<f32>, 6>,
|
|
@@ -10,20 +12,12 @@ struct VolumeView {
|
|
|
10
12
|
_viewTail : array<vec4<f32>, 18>,
|
|
11
13
|
spotLightViewProj : array<mat4x4<f32>, 4>,
|
|
12
14
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
shadowAtlasTile : i32,
|
|
19
|
-
shadowIntensity : f32,
|
|
15
|
+
// Volume reads the same full light_data payload as Standard surface Cluster;
|
|
16
|
+
// it has no secondary light mirror.
|
|
17
|
+
struct ClusterUniform {
|
|
18
|
+
grid : vec4<u32>,
|
|
19
|
+
near_far_log : vec4<f32>,
|
|
20
20
|
};
|
|
21
|
-
struct SpotLightsArray { count : u32, slots : array<SpotLight, 4>, };
|
|
22
|
-
struct PointLight {
|
|
23
|
-
position : vec3<f32>, invRangeSquared : f32,
|
|
24
|
-
colorTimesIntensity : vec3<f32>, shadowAtlasLayer : i32,
|
|
25
|
-
};
|
|
26
|
-
struct PointLightsArray { count : u32, slots : array<PointLight, 4>, };
|
|
27
21
|
struct VolumeParams {
|
|
28
22
|
bounds_min : vec4<f32>, bounds_max : vec4<f32>, extinction : vec4<f32>, albedo : vec4<f32>,
|
|
29
23
|
emission : vec4<f32>, light_direction : vec4<f32>, light_color : vec4<f32>, optics : vec4<f32>,
|
|
@@ -68,8 +62,8 @@ fn stratified32(seed : u32, frame_index : u32, odd_stride : u32) -> f32 {
|
|
|
68
62
|
@group(0) @binding(7) var density_sampler : sampler;
|
|
69
63
|
@group(0) @binding(8) var history_seed : texture_storage_2d<rgba16float, write>;
|
|
70
64
|
@group(0) @binding(9) var temporal_seed : texture_storage_2d<rgba16float, write>;
|
|
71
|
-
@group(0) @binding(10) var<storage, read>
|
|
72
|
-
@group(0) @binding(11) var<
|
|
65
|
+
@group(0) @binding(10) var<storage, read> light_data : array<DirectLightSlot, 256>;
|
|
66
|
+
@group(0) @binding(11) var<uniform> cluster_uniform : ClusterUniform;
|
|
73
67
|
@group(0) @binding(12) var projectorTexture : texture_2d<f32>;
|
|
74
68
|
@group(0) @binding(13) var projectorSampler : sampler;
|
|
75
69
|
|
|
@@ -106,32 +100,43 @@ fn evalSpotAttenuation(
|
|
|
106
100
|
return distance * smoothstep(cosOuter, cosInner, dot(direction, -normalize(lightDir)));
|
|
107
101
|
}
|
|
108
102
|
|
|
109
|
-
fn
|
|
110
|
-
let
|
|
111
|
-
|
|
112
|
-
|
|
103
|
+
fn shadowAtlasTile(light : DirectLightSlot) -> i32 {
|
|
104
|
+
let encoded = light.metadata.y;
|
|
105
|
+
if (encoded == 0xffffffffu) { return -1; }
|
|
106
|
+
let tile = encoded & 0x3fffffffu;
|
|
107
|
+
if (tile >= 4u) { return -1; }
|
|
108
|
+
return i32(tile);
|
|
113
109
|
}
|
|
114
110
|
|
|
115
|
-
fn spotProjectorMatrix(light :
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
fn spotProjectorMatrix(light : DirectLightSlot) -> mat4x4<f32> {
|
|
112
|
+
let tile = shadowAtlasTile(light);
|
|
113
|
+
if (tile >= 0) {
|
|
114
|
+
return volume_view.spotLightViewProj[tile];
|
|
118
115
|
}
|
|
119
116
|
return volume_view.spotLightViewProj[0];
|
|
120
117
|
}
|
|
121
118
|
|
|
119
|
+
fn selected_cluster_light_slot(value : f32) -> i32 {
|
|
120
|
+
let slot = i32(round(value));
|
|
121
|
+
if (slot < 0) { return -1; }
|
|
122
|
+
let index = u32(slot);
|
|
123
|
+
if (index >= cluster_uniform.grid.w || index >= 256u) { return -1; }
|
|
124
|
+
return slot;
|
|
125
|
+
}
|
|
126
|
+
|
|
122
127
|
fn spot_attenuation_at(world_position : vec3<f32>) -> f32 {
|
|
123
|
-
let slot =
|
|
124
|
-
if (slot
|
|
125
|
-
let light =
|
|
128
|
+
let slot = selected_cluster_light_slot(volume_params.light_color.w);
|
|
129
|
+
if (slot < 0) { return 0.0; }
|
|
130
|
+
let light = light_data[u32(slot)];
|
|
126
131
|
let projectorUv = projectSpotUv(spotProjectorMatrix(light), world_position);
|
|
127
132
|
if (any(projectorUv < vec2<f32>(0.0)) || any(projectorUv > vec2<f32>(1.0))) { return 0.0; }
|
|
128
133
|
return evalSpotAttenuation(
|
|
129
|
-
light.position,
|
|
130
|
-
light.direction,
|
|
134
|
+
light.position.xyz,
|
|
135
|
+
light.direction.xyz,
|
|
131
136
|
world_position,
|
|
132
|
-
light.
|
|
133
|
-
light.
|
|
134
|
-
light.
|
|
137
|
+
light.colorTimesIntensity.w,
|
|
138
|
+
light.direction.w,
|
|
139
|
+
light.position.w,
|
|
135
140
|
);
|
|
136
141
|
}
|
|
137
142
|
|
|
@@ -156,21 +161,21 @@ fn evalVolumeSpot(
|
|
|
156
161
|
}
|
|
157
162
|
|
|
158
163
|
fn volume_point_radiance(world_position : vec3<f32>) -> vec3<f32> {
|
|
159
|
-
let pointSlot =
|
|
160
|
-
if (pointSlot
|
|
161
|
-
let point =
|
|
164
|
+
let pointSlot = selected_cluster_light_slot(volume_params.emission.w);
|
|
165
|
+
if (pointSlot < 0) { return vec3<f32>(0.0); }
|
|
166
|
+
let point = light_data[u32(pointSlot)];
|
|
162
167
|
return evalVolumePoint(
|
|
163
|
-
point.position, point.colorTimesIntensity, point.
|
|
168
|
+
point.position.xyz, point.colorTimesIntensity.xyz, point.position.w, world_position,
|
|
164
169
|
);
|
|
165
170
|
}
|
|
166
171
|
|
|
167
172
|
fn volume_spot_radiance(world_position : vec3<f32>) -> vec3<f32> {
|
|
168
|
-
let spotSlot =
|
|
169
|
-
if (spotSlot
|
|
170
|
-
let spot =
|
|
173
|
+
let spotSlot = selected_cluster_light_slot(volume_params.light_color.w);
|
|
174
|
+
if (spotSlot < 0) { return vec3<f32>(0.0); }
|
|
175
|
+
let spot = light_data[u32(spotSlot)];
|
|
171
176
|
let radiance = evalVolumeSpot(
|
|
172
|
-
spot.position, spot.direction, spot.colorTimesIntensity,
|
|
173
|
-
spot.
|
|
177
|
+
spot.position.xyz, spot.direction.xyz, spot.colorTimesIntensity.xyz,
|
|
178
|
+
spot.colorTimesIntensity.w, spot.direction.w, spot.position.w, world_position,
|
|
174
179
|
);
|
|
175
180
|
let projectorUv = projectSpotUv(spotProjectorMatrix(spot), world_position);
|
|
176
181
|
if (!(projectorUv.x >= 0.0 && projectorUv.x <= 1.0 && projectorUv.y >= 0.0 && projectorUv.y <= 1.0)) {
|
|
@@ -183,9 +188,9 @@ fn volume_spot_radiance(world_position : vec3<f32>) -> vec3<f32> {
|
|
|
183
188
|
}
|
|
184
189
|
|
|
185
190
|
fn volume_spot_shadow_intensity() -> f32 {
|
|
186
|
-
let spotSlot =
|
|
187
|
-
if (spotSlot
|
|
188
|
-
return clamp(
|
|
191
|
+
let spotSlot = selected_cluster_light_slot(volume_params.light_color.w);
|
|
192
|
+
if (spotSlot < 0) { return 1.0; }
|
|
193
|
+
return clamp(light_data[u32(spotSlot)].auxiliary.z, 0.0, 1.0);
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
fn volume_light_radiance(world_position : vec3<f32>) -> vec3<f32> {
|