@forgeax/engine-shader 0.1.19 → 0.1.21
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/.tsbuildinfo +1 -1
- package/dist/ShaderRegistry.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +52 -1
- package/dist/index.mjs.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 +71 -22
- package/src/__tests__/fog-oracle.unit.test.ts +110 -114
- package/src/__tests__/fog-orthographic-ray.unit.test.ts +12 -37
- package/src/__tests__/fog-producer-matrix.integration.test.ts +16 -51
- package/src/__tests__/lighting-point-spot-volume.unit.test.ts +56 -0
- package/src/__tests__/lighting-punctual.unit.test.ts +32 -6
- package/src/__tests__/lighting-spot-volume-attenuation.unit.test.ts +26 -0
- package/src/__tests__/lighting-spot-volume.unit.test.ts +25 -0
- package/src/__tests__/material-derived-builtins.integration.test.ts +27 -0
- package/src/__tests__/reflection-probe-sampling.unit.test.ts +23 -0
- package/src/__tests__/scene-temporal.unit.test.ts +20 -0
- package/src/__tests__/shader.unit.test.ts +35 -13
- package/src/__tests__/shadow-pcss.unit.test.ts +131 -0
- package/src/__tests__/sprite-lit-shader.test.ts +24 -11
- package/src/__tests__/sprite-variants.unit.test.ts +2 -0
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +7 -1
- package/src/__tests__/transparent-pbr.unit.test.ts +7 -0
- package/src/common.wgsl +72 -73
- package/src/default-standard-pbr-skin.wgsl +31 -89
- package/src/default-standard-pbr.wgsl +94 -146
- package/src/fxaa.wgsl +27 -6
- package/src/hdrp-deferred-lighting.wgsl +3 -27
- package/src/ibl-sampling.wgsl +54 -0
- package/src/index.ts +6 -0
- package/src/lighting-attenuation.wgsl +58 -0
- package/src/lighting-directional.wgsl +188 -21
- package/src/lighting-punctual.wgsl +94 -19
- package/src/msdf-text.wgsl +4 -23
- package/src/scene-temporal.wgsl +14 -8
- package/src/shadow-pcf.wgsl +75 -11
- package/src/skybox.wgsl +2 -7
- package/src/sprite-lit.wgsl +38 -73
- package/src/sprite.wgsl +3 -22
- package/src/{hdrp-cluster-forward.wgsl → standard-cluster.wgsl} +74 -23
- package/src/tonemap.wgsl +8 -3
- package/src/unlit.wgsl +2 -24
- package/src/volume/volume-composite.wgsl +113 -0
- package/src/volume/volume-inject.wgsl +261 -0
- package/src/volume/volume-integrate.wgsl +337 -0
- package/src/volume/volume-temporal.wgsl +70 -0
package/src/common.wgsl
CHANGED
|
@@ -36,6 +36,36 @@ fn linearToSrgbOetf(color : vec3<f32>) -> vec3<f32> {
|
|
|
36
36
|
return select(high, low, safe <= vec3<f32>(0.0031308));
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Shared final-surface dither. The output transform and FXAA are both
|
|
40
|
+
// display-encoded fullscreen writers, so they must use one owner-level
|
|
41
|
+
// algorithm rather than carrying subtly different copies in each pass.
|
|
42
|
+
// The bounded shift is ±0.5/255 per channel, matching Three.js's optional
|
|
43
|
+
// material dithering amplitude while the screen-space hash remains stable for
|
|
44
|
+
// a still frame. Callers decide whether this writer is the final 8-bit
|
|
45
|
+
// boundary; intermediate float targets must leave it disabled.
|
|
46
|
+
fn hash32(value : u32) -> u32 {
|
|
47
|
+
var hash = value;
|
|
48
|
+
hash = (hash ^ 61u) ^ (hash >> 16u);
|
|
49
|
+
hash = hash + (hash << 3u);
|
|
50
|
+
hash = hash ^ (hash >> 4u);
|
|
51
|
+
hash = hash * 668265261u;
|
|
52
|
+
hash = hash ^ (hash >> 15u);
|
|
53
|
+
return hash;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fn ditherNoise(pixelPosition : vec2<f32>) -> f32 {
|
|
57
|
+
let pixel = vec2<u32>(pixelPosition);
|
|
58
|
+
let seed = (pixel.x * 1973u) ^ (pixel.y * 9277u) ^ 89173u;
|
|
59
|
+
return f32(hash32(seed) & 1023u) / 1023.0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fn ditherUnorm8(value : vec3<f32>, pixelPosition : vec2<f32>) -> vec3<f32> {
|
|
63
|
+
let noise = ditherNoise(pixelPosition);
|
|
64
|
+
var shift = vec3<f32>(0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0);
|
|
65
|
+
shift = mix(2.0 * shift, -2.0 * shift, noise);
|
|
66
|
+
return clamp(value + shift, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
67
|
+
}
|
|
68
|
+
|
|
39
69
|
// @forgeax/engine-shader - common.wgsl (M5 T-18 feat-20260512-naga-oil-composition-hmr).
|
|
40
70
|
//
|
|
41
71
|
// Shared view + mesh structs extracted from pbr.wgsl + unlit.wgsl via
|
|
@@ -50,7 +80,7 @@ fn linearToSrgbOetf(color : vec3<f32>) -> vec3<f32> {
|
|
|
50
80
|
// aligned with Bevy's bevy_view::common and matches charter proposition 5
|
|
51
81
|
// (consistent abstraction - one View struct everywhere).
|
|
52
82
|
|
|
53
|
-
// View UBO byte layout (
|
|
83
|
+
// View UBO byte layout (960 B payload, 1024 B slot):
|
|
54
84
|
// [ 0.. 64) worldViewProj mat4x4<f32> (align 16, size 64)
|
|
55
85
|
// [ 64.. 80) lightDir vec3<f32> (align 16, 12+4 pad)
|
|
56
86
|
// [ 80.. 96) lightColor vec3<f32> (align 16, 12+4 pad)
|
|
@@ -68,16 +98,14 @@ fn linearToSrgbOetf(color : vec3<f32>) -> vec3<f32> {
|
|
|
68
98
|
// [500..504) cascadeBlend f32 (align 4, size 4)
|
|
69
99
|
// [504..508) depthBias f32 (align 4, size 4)
|
|
70
100
|
// [508..512) normalBias f32 (align 4, size 4)
|
|
71
|
-
// [512..
|
|
72
|
-
// [516..528) _align_pad — (mat4 array align=16, 12 B)
|
|
101
|
+
// [512..528) directional shadow carrier f32[4] (profile, radius, penumbra, reserved)
|
|
73
102
|
// [528..784) spotLightViewProj array<mat4x4<f32>,4> (align 16, size 256)
|
|
74
103
|
// WGSL prefix = 784 B. Host UBO = VIEW_UBO_BYTES = 960 B (renderer-factory.ts);
|
|
75
104
|
// view-ubo.ts writes the full 240 f32 payload. The spot matrix
|
|
76
105
|
// array fold (feat-20260625 w25) removes the standalone @group(0) binding 9
|
|
77
106
|
// uniform buffer so the WebGL2 fallback fragment uniform-buffer count returns
|
|
78
107
|
// to 11 (GLES 3.0 max).
|
|
79
|
-
// temporal projection facts
|
|
80
|
-
// total = 960 B.
|
|
108
|
+
// temporal projection facts append after the spot matrix; total = 960 B.
|
|
81
109
|
//
|
|
82
110
|
// Field order must stay byte-for-byte identical to every prior release
|
|
83
111
|
// (charter P4 consistent abstraction); new fields append at the tail.
|
|
@@ -100,14 +128,15 @@ fn linearToSrgbOetf(color : vec3<f32>) -> vec3<f32> {
|
|
|
100
128
|
// shadow_caster.wgsl indexes the 4 fields via `shadowCasterCascade.index`
|
|
101
129
|
// (binding 5, written per shadow pass).
|
|
102
130
|
//
|
|
103
|
-
// feat-
|
|
104
|
-
// the merged DirectionalLight's shadow bias +
|
|
105
|
-
// tail (depthBias / normalBias
|
|
106
|
-
// 126/127/128). Tail
|
|
131
|
+
// feat-20260827-directional-csm-pcss-quality M1:
|
|
132
|
+
// the merged DirectionalLight's shadow bias + accepted filter carrier occupy
|
|
133
|
+
// the existing tail (depthBias / normalBias at bytes 504/508, carrier at
|
|
134
|
+
// bytes 512..528, floats 126/127/128..131). Tail reuse only -- field order is
|
|
135
|
+
// byte-for-byte stable
|
|
107
136
|
// (charter P4); the prior 88 B host tail pad shrinks to 64 B, total stays
|
|
108
137
|
// View slot remains within the existing 1024 B stride. lighting-directional.wgsl drives the
|
|
109
|
-
// directional shadow bias (D-1: bias = max(normalBias*(1-N.L), depthBias))
|
|
110
|
-
//
|
|
138
|
+
// directional shadow bias (D-1: bias = max(normalBias*(1-N.L), depthBias));
|
|
139
|
+
// directional shadow carrier is the receiver-facing profile/radius/penumbra POD.
|
|
111
140
|
//
|
|
112
141
|
// feat-20260625-spot-light-shadow-mapping w25 (scope-amend webkit-fallback):
|
|
113
142
|
// the per-spot perspective `spotLightViewProj` matrices (4 lanes, fragment-read)
|
|
@@ -123,24 +152,11 @@ fn linearToSrgbOetf(color : vec3<f32>) -> vec3<f32> {
|
|
|
123
152
|
// matrix in the same frame (same-frame write contention); the FRAGMENT read
|
|
124
153
|
// channel has no such contention — the host writes all <=4 spot matrices once
|
|
125
154
|
// per frame before the forward pass, so the fold is safe. mat4 array align=16:
|
|
126
|
-
// the array lands at byte 528 (next 16 B-aligned offset after
|
|
155
|
+
// the array lands at byte 528 (next 16 B-aligned offset after the filter carrier at
|
|
127
156
|
// byte 512..516), spanning bytes 528..784. The host allocates VIEW_UBO_BYTES =
|
|
128
157
|
// 960 (renderer-factory.ts) and writes the full 240 f32
|
|
129
158
|
// payload (render-system-record.ts). Lane N = the spot with `shadowAtlasTile
|
|
130
159
|
// === N` (cap = 4); `evalSpotShadowed` reads `view.spotLightViewProj[tile]`.
|
|
131
|
-
struct FogViewParams {
|
|
132
|
-
color : vec3<f32>,
|
|
133
|
-
density : f32,
|
|
134
|
-
heightFalloff : f32,
|
|
135
|
-
maxOpacity : f32,
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
struct FogRay {
|
|
139
|
-
origin : vec3<f32>,
|
|
140
|
-
direction : vec3<f32>,
|
|
141
|
-
distance : f32,
|
|
142
|
-
};
|
|
143
|
-
|
|
144
160
|
struct View {
|
|
145
161
|
worldViewProj : mat4x4<f32>,
|
|
146
162
|
lightDir : vec3<f32>,
|
|
@@ -156,10 +172,10 @@ struct View {
|
|
|
156
172
|
cascadeBlend : f32,
|
|
157
173
|
depthBias : f32,
|
|
158
174
|
normalBias : f32,
|
|
159
|
-
|
|
175
|
+
directionalShadowFilter : vec4<f32>,
|
|
160
176
|
// feat-20260625-spot-light-shadow-mapping w25: per-spot perspective
|
|
161
177
|
// light-view-projection matrices, fragment-read. Auto-aligned to byte 528
|
|
162
|
-
// (mat4 array align=16, after
|
|
178
|
+
// (mat4 array align=16, after the four directional carrier lanes at byte 512); spans bytes
|
|
163
179
|
// 528..784. Lane N = spot with shadowAtlasTile === N (cap = 4). Zeroed lanes
|
|
164
180
|
// are safe (sample gated on shadowAtlasTile >= 0 in default-standard-pbr.wgsl).
|
|
165
181
|
spotLightViewProj : array<mat4x4<f32>, 4>,
|
|
@@ -167,7 +183,9 @@ struct View {
|
|
|
167
183
|
temporalPreviousViewProj : mat4x4<f32>,
|
|
168
184
|
// x = near, y = far, z = 1 for orthographic and 0 for perspective.
|
|
169
185
|
temporalProjection : vec4<f32>,
|
|
170
|
-
|
|
186
|
+
// Accepted camera origin at the previous submitted frame. This occupies
|
|
187
|
+
// the existing tail padding and keeps temporal reprojection on one SSOT.
|
|
188
|
+
temporalPreviousCameraPos : vec4<f32>,
|
|
171
189
|
};
|
|
172
190
|
|
|
173
191
|
// Per-instance mesh slot (feat-20260518-pbr-direct-lighting-mvp M2 / w8.5,
|
|
@@ -202,7 +220,7 @@ struct Mesh {
|
|
|
202
220
|
// color * intensity so the shader avoids the per-fragment mul)
|
|
203
221
|
// [ 7 ] pad f32 = 0
|
|
204
222
|
//
|
|
205
|
-
// SpotLight (
|
|
223
|
+
// SpotLight (80 B / 20 floats):
|
|
206
224
|
// [ 0..2 ] position vec3<f32>
|
|
207
225
|
// [ 3 ] invRangeSquared f32
|
|
208
226
|
// [ 4..6 ] colorTimesIntensity vec3<f32>
|
|
@@ -211,9 +229,11 @@ struct Mesh {
|
|
|
211
229
|
// [ 8..10] direction vec3<f32> (raw outgoing vector; shader reads
|
|
212
230
|
// via dot(L, -direction) for cone angle test)
|
|
213
231
|
// [ 11 ] cosOuter f32
|
|
214
|
-
// [12
|
|
232
|
+
// [12] depthBias f32, [13] normalBias f32, [14] pcfKernelSize f32
|
|
215
233
|
// [ 15 ] shadowAtlasTile i32 (sentinel -1 = unassigned/clipped,
|
|
216
234
|
// 0..3 = spot atlas tile index; feat-20260625 M2/M3 D-4)
|
|
235
|
+
// [ 16 ] shadowIntensity f32 (fraction of sampled shadow to apply)
|
|
236
|
+
// [17..19] alignment tail padding
|
|
217
237
|
//
|
|
218
238
|
// WGSL std430 alignment audit: vec3<f32> alignof=16 / sizeof=12. The
|
|
219
239
|
// f32 lane wedged immediately after each vec3 fills the 4 B remainder
|
|
@@ -221,10 +241,10 @@ struct Mesh {
|
|
|
221
241
|
// prev.size, this.alignof) - for f32 alignof=4 the round-up is a
|
|
222
242
|
// no-op, so position[3] / color[3] / direction[3] sit at byte
|
|
223
243
|
// offsets 12 / 28 / 44 with zero internal padding). PointLight stays
|
|
224
|
-
// 32 B; SpotLight grows
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
244
|
+
// 32 B; SpotLight grows from 64 B (feat-20260625 M2/M3 D-4) for the three
|
|
245
|
+
// receiver controls plus `shadowAtlasTile: i32`, then to 80 B for the
|
|
246
|
+
// parity-only `shadowIntensity` lane at byte 64. These struct sizes match the
|
|
247
|
+
// host packers (packPointLight 32 B / packSpotLight 80 B) exactly.
|
|
228
248
|
//
|
|
229
249
|
// Header `count: u32` lives in a wrapper struct with the trailing
|
|
230
250
|
// runtime-sized array. Array-of-vec4-aligned-struct alignof = 16, so
|
|
@@ -255,19 +275,14 @@ struct SpotLight {
|
|
|
255
275
|
direction : vec3<f32>,
|
|
256
276
|
cosOuter : f32,
|
|
257
277
|
// feat-20260625-spot-light-shadow-mapping M3 / w13 (plan-strategy D-4):
|
|
258
|
-
// The
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
// `shadowAtlasTile >= 0` (evalSpotShadowed vs evalSpot). Field names are
|
|
265
|
-
// unique across the composed module surface so naga_oil writeback
|
|
266
|
-
// substitution does not collide with prior `pad0` members. (naga_oil
|
|
267
|
-
// reserves the `__` identifier prefix, so the pad lane is `spotPad`, not
|
|
268
|
-
// `__pad`.)
|
|
269
|
-
spotPad : vec3<f32>,
|
|
278
|
+
// The trailing vec4 transports receiver controls and the tile identity.
|
|
279
|
+
// Host packSpotLight writes slots 12..14 from the same snapshot; slot 15 is
|
|
280
|
+
// the i32 shadow tile sentinel and slot 16 carries shadowIntensity.
|
|
281
|
+
depthBias : f32,
|
|
282
|
+
normalBias : f32,
|
|
283
|
+
pcfKernelSize : f32,
|
|
270
284
|
shadowAtlasTile : i32,
|
|
285
|
+
shadowIntensity : f32,
|
|
271
286
|
};
|
|
272
287
|
|
|
273
288
|
struct PointLightsArray {
|
|
@@ -282,32 +297,6 @@ struct SpotLightsArray {
|
|
|
282
297
|
|
|
283
298
|
@group(0) @binding(0) var<uniform> view : View;
|
|
284
299
|
|
|
285
|
-
// Shared screen-space ray reconstruction for scene Fog producers. Keeping the
|
|
286
|
-
// inverse-view and projection branch here prevents each producer from growing
|
|
287
|
-
// a second Fog formula or drifting from the View ABI.
|
|
288
|
-
fn fogWorldPoint(ndc : vec3<f32>) -> vec3<f32> {
|
|
289
|
-
let homogeneous = view.inverseViewProj * vec4<f32>(ndc, 1.0);
|
|
290
|
-
let divisor = select(1.0, homogeneous.w, abs(homogeneous.w) > 0.000001);
|
|
291
|
-
return homogeneous.xyz / divisor;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
fn fogRayFromNdc(ndc : vec3<f32>) -> FogRay {
|
|
295
|
-
let worldPosition = fogWorldPoint(ndc);
|
|
296
|
-
let nearPosition = fogWorldPoint(vec3<f32>(ndc.xy, 0.0));
|
|
297
|
-
let farPosition = fogWorldPoint(vec3<f32>(ndc.xy, 1.0));
|
|
298
|
-
let perspective = view.temporalProjection.z < 0.5;
|
|
299
|
-
let perspectiveVector = worldPosition - view.cameraPos;
|
|
300
|
-
let orthographicVector = farPosition - nearPosition;
|
|
301
|
-
let direction = normalize(select(orthographicVector, perspectiveVector, perspective));
|
|
302
|
-
let origin = select(nearPosition, view.cameraPos, perspective);
|
|
303
|
-
let rayDistance = select(
|
|
304
|
-
max(dot(worldPosition - nearPosition, direction), 0.0),
|
|
305
|
-
length(perspectiveVector),
|
|
306
|
-
perspective,
|
|
307
|
-
);
|
|
308
|
-
return FogRay(origin, direction, rayDistance);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
300
|
#if STORAGE_BUFFER_AVAILABLE == true
|
|
312
301
|
@group(0) @binding(1) var<storage, read> pointLightsBuffer : PointLightsArray;
|
|
313
302
|
@group(0) @binding(2) var<storage, read> spotLightsBuffer : SpotLightsArray;
|
|
@@ -421,7 +410,17 @@ struct ShadowCasterCascade {
|
|
|
421
410
|
// — folded there in feat-20260625 w25 (scope-amend) to keep the WebGL2 fallback
|
|
422
411
|
// fragment uniform-buffer count <= 11; binding 8 is the last view-BG binding.
|
|
423
412
|
@group(0) @binding(8) var spotShadowMap : texture_depth_2d;
|
|
424
|
-
|
|
413
|
+
// Optional authored SpotLight projector. Surface and volume bind the same
|
|
414
|
+
// accepted TextureAsset view and linear-clamp sampler; an unprojected spot
|
|
415
|
+
// receives the renderer-owned white fallback. The declaration is capability
|
|
416
|
+
// gated because the complete HDRP PBR view/material ABI reaches the WebGPU
|
|
417
|
+
// minimum of 16 sampled textures without this optional cookie. Devices that
|
|
418
|
+
// cannot expose the 17th sampled texture select the matching white-projector
|
|
419
|
+
// shader variant and omit these resources from the view BGL.
|
|
420
|
+
#ifdef PROJECTOR_AVAILABLE
|
|
421
|
+
@group(0) @binding(11) var projectorTexture : texture_2d<f32>;
|
|
422
|
+
@group(0) @binding(12) var projectorSampler : sampler;
|
|
423
|
+
#endif
|
|
425
424
|
#if STORAGE_BUFFER_AVAILABLE == true
|
|
426
425
|
@group(2) @binding(0) var<storage, read> meshes : array<Mesh>;
|
|
427
426
|
#else
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
#import forgeax_view::common::{View,
|
|
1
|
+
#import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, sampleMaterialTexture}
|
|
2
|
+
#import forgeax_scene_temporal::{sceneViewZ}
|
|
2
3
|
#import forgeax_pbr::temporal::{projectPbrSceneTemporal}
|
|
3
4
|
#import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx}
|
|
4
5
|
#import forgeax_pbr::ibl_sampling::{sampleIblDiffuse, sampleIblSpecular}
|
|
5
6
|
#import forgeax_pbr::tbn::{decodeTangentSpaceNormalRg, scaleTangentSpaceNormal, applyTBN}
|
|
6
7
|
#import forgeax_pbr::lighting_directional::{evalDirectionalNoShadow, evalDirectionalShadowFactor}
|
|
7
|
-
#
|
|
8
|
-
#import
|
|
9
|
-
#ifdef POINT_SHADOW_AVAILABLE
|
|
10
|
-
#import forgeax_pbr::lighting_punctual::{evalPointShadowed}
|
|
11
|
-
#import forgeax_view::common::{shadowParams}
|
|
8
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
9
|
+
#import forgeax_standard::cluster::{evaluateStandardClusterLights}
|
|
12
10
|
#endif
|
|
13
11
|
|
|
14
12
|
#define_import_path forgeax_material::pbr-skin
|
|
15
13
|
#pragma variant_axis STORAGE_BUFFER_AVAILABLE
|
|
14
|
+
#pragma variant_axis CLUSTER_FORWARD_AVAILABLE
|
|
16
15
|
#pragma variant_axis VERTEX_COLOR_AVAILABLE
|
|
16
|
+
#pragma variant_axis DIRECTIONAL_PCSS_AVAILABLE
|
|
17
17
|
|
|
18
18
|
// @forgeax/engine-shader - default-standard-pbr-skin.wgsl
|
|
19
19
|
// (feat-20260523-skin-skeleton-animation M3 / T-29).
|
|
@@ -205,7 +205,6 @@ struct VsOut {
|
|
|
205
205
|
@location(1) worldNormal : vec3<f32>,
|
|
206
206
|
@location(2) uv : vec2<f32>,
|
|
207
207
|
@location(3) worldTangent : vec4<f32>,
|
|
208
|
-
@location(4) @interpolate(flat) instanceIdx : u32,
|
|
209
208
|
// feat-city-glb multi-UV tiling: second UV set varying at location 5
|
|
210
209
|
// (parity with default-standard-pbr.wgsl).
|
|
211
210
|
@location(5) uv1 : vec2<f32>,
|
|
@@ -218,6 +217,9 @@ struct VsOut {
|
|
|
218
217
|
#ifdef VERTEX_COLOR_AVAILABLE
|
|
219
218
|
@location(14) color : vec4<f32>,
|
|
220
219
|
#endif
|
|
220
|
+
// Vertex-produced Standard cluster coordinates. Fragment builtin position
|
|
221
|
+
// is framebuffer-space and cannot recover the original clip-space NDC.
|
|
222
|
+
@location(6) ndc : vec3<f32>,
|
|
221
223
|
@location(7) viewZ : f32,
|
|
222
224
|
};
|
|
223
225
|
|
|
@@ -230,22 +232,6 @@ fn pick_channel(rgba : vec4<f32>, channelIndex : u32) -> f32 {
|
|
|
230
232
|
}
|
|
231
233
|
}
|
|
232
234
|
|
|
233
|
-
fn applySceneFog(viewParams : View, color : vec3<f32>, alpha : f32, worldPos : vec3<f32>) -> vec4<f32> {
|
|
234
|
-
var origin = viewParams.cameraPos;
|
|
235
|
-
var direction = normalize(worldPos - origin);
|
|
236
|
-
var rayDistance = length(worldPos - origin);
|
|
237
|
-
if (viewParams.temporalProjection.z >= 0.5) {
|
|
238
|
-
let nearH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
|
239
|
-
let farH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
|
240
|
-
let nearPoint = nearH.xyz / nearH.w;
|
|
241
|
-
let farPoint = farH.xyz / farH.w;
|
|
242
|
-
direction = normalize(farPoint - nearPoint);
|
|
243
|
-
origin = worldPos - direction * dot(worldPos - viewParams.cameraPos, direction);
|
|
244
|
-
rayDistance = max(dot(worldPos - origin, direction), 0.0);
|
|
245
|
-
}
|
|
246
|
-
return apply_fog(viewParams.fog, FogRay(origin, direction, rayDistance), vec4<f32>(color, alpha));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
235
|
@vertex
|
|
250
236
|
fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
251
237
|
// 4-bone weighted skinning (plan-strategy D-3 / D-3a).
|
|
@@ -315,11 +301,14 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
|
315
301
|
#ifdef VERTEX_COLOR_AVAILABLE
|
|
316
302
|
out.color = in.color;
|
|
317
303
|
#endif
|
|
318
|
-
out.instanceIdx = idx;
|
|
319
304
|
// feat-20260613-csm-cascaded-shadow-maps M5 / w19: viewZ replaces the
|
|
320
305
|
// prior light-space-position varying; evalDirectional picks the cascade
|
|
321
306
|
// matrix per fragment from viewZ + worldPos.
|
|
322
|
-
|
|
307
|
+
let clipPos = out.clip;
|
|
308
|
+
out.ndc = vec3(clipPos.xy / clipPos.w, clipPos.z / clipPos.w);
|
|
309
|
+
// Keep the cluster depth exactly aligned with the CPU binner for both
|
|
310
|
+
// perspective and off-axis orthographic projections.
|
|
311
|
+
out.viewZ = sceneViewZ(out.clip, view.temporalProjection);
|
|
323
312
|
return out;
|
|
324
313
|
}
|
|
325
314
|
|
|
@@ -414,66 +403,18 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
414
403
|
);
|
|
415
404
|
color = color + directionalShadow * material.clearcoat * directionalClearcoat;
|
|
416
405
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
in.worldPos, n, v, albedo, metallic, a, f0,
|
|
426
|
-
p.shadowAtlasLayer, lane.x, lane.y, 0.005, 0.05,
|
|
427
|
-
);
|
|
428
|
-
if (material.clearcoat != 0.0) {
|
|
429
|
-
color = color + material.clearcoat * evalPointShadowed(
|
|
430
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
431
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
432
|
-
p.shadowAtlasLayer, lane.x, lane.y, 0.005, 0.05,
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
} else {
|
|
436
|
-
color = color + evalPoint(
|
|
437
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
438
|
-
in.worldPos, n, v, albedo, metallic, a, f0,
|
|
439
|
-
);
|
|
440
|
-
if (material.clearcoat != 0.0) {
|
|
441
|
-
color = color + material.clearcoat * evalPoint(
|
|
442
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
443
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
#else
|
|
448
|
-
color = color + evalPoint(
|
|
449
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
450
|
-
in.worldPos, n, v, albedo, metallic, a, f0,
|
|
451
|
-
);
|
|
452
|
-
if (material.clearcoat != 0.0) {
|
|
453
|
-
color = color + material.clearcoat * evalPoint(
|
|
454
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
455
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
#endif
|
|
459
|
-
}
|
|
460
|
-
let spotCount = spotLightsBuffer.count;
|
|
461
|
-
for (var i: u32 = 0u; i < spotCount; i = i + 1u) {
|
|
462
|
-
let s = spotLightsBuffer.slots[i];
|
|
463
|
-
color = color + evalSpot(
|
|
464
|
-
s.position, s.direction, s.colorTimesIntensity,
|
|
465
|
-
s.cosInner, s.cosOuter, s.invRangeSquared,
|
|
466
|
-
in.worldPos, n, v, albedo, metallic, a, f0,
|
|
406
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
407
|
+
color = color + evaluateStandardClusterLights(
|
|
408
|
+
in.ndc, in.viewZ, in.worldPos, n, v, albedo, metallic, a, f0, false,
|
|
409
|
+
);
|
|
410
|
+
if (material.clearcoat != 0.0) {
|
|
411
|
+
color = color + material.clearcoat * evaluateStandardClusterLights(
|
|
412
|
+
in.ndc, in.viewZ, in.worldPos, n, v,
|
|
413
|
+
vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04), false,
|
|
467
414
|
);
|
|
468
|
-
if (material.clearcoat != 0.0) {
|
|
469
|
-
color = color + material.clearcoat * evalSpot(
|
|
470
|
-
s.position, s.direction, s.colorTimesIntensity,
|
|
471
|
-
s.cosInner, s.cosOuter, s.invRangeSquared,
|
|
472
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
415
|
}
|
|
476
|
-
|
|
416
|
+
#endif
|
|
417
|
+
return vec4<f32>(color, alpha);
|
|
477
418
|
}
|
|
478
419
|
|
|
479
420
|
struct TemporalVsOut {
|
|
@@ -488,6 +429,7 @@ struct TemporalVsOut {
|
|
|
488
429
|
@location(7) uv7 : vec2<f32>,
|
|
489
430
|
@location(8) @interpolate(perspective) currentClip : vec4<f32>,
|
|
490
431
|
@location(9) @interpolate(perspective) previousClip : vec4<f32>,
|
|
432
|
+
@location(10) @interpolate(flat) reactive : f32,
|
|
491
433
|
#ifdef VERTEX_COLOR_AVAILABLE
|
|
492
434
|
@location(14) color : vec4<f32>,
|
|
493
435
|
#endif
|
|
@@ -509,6 +451,11 @@ fn vs_temporal(in : VsIn, @builtin(instance_index) idx : u32) -> TemporalVsOut {
|
|
|
509
451
|
out.currentClip = view.temporalCurrentViewProj * currentWorld;
|
|
510
452
|
out.clip = out.currentClip;
|
|
511
453
|
out.previousClip = view.temporalPreviousViewProj * previousWorld;
|
|
454
|
+
#if STORAGE_BUFFER_AVAILABLE == true
|
|
455
|
+
out.reactive = meshes[0].temporal.x;
|
|
456
|
+
#else
|
|
457
|
+
out.reactive = 1.0;
|
|
458
|
+
#endif
|
|
512
459
|
out.uv = in.uv;
|
|
513
460
|
out.uv1 = in.uv1;
|
|
514
461
|
out.uv2 = in.uv2;
|
|
@@ -534,11 +481,6 @@ fn temporalVertexAlpha(in : TemporalVsOut) -> f32 {
|
|
|
534
481
|
|
|
535
482
|
@fragment
|
|
536
483
|
fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
|
|
537
|
-
#if STORAGE_BUFFER_AVAILABLE == true
|
|
538
|
-
let reactive = meshes[0].temporal.x;
|
|
539
|
-
#else
|
|
540
|
-
let reactive = 1.0;
|
|
541
|
-
#endif
|
|
542
484
|
return projectPbrSceneTemporal(
|
|
543
485
|
material.baseColor.a * temporalVertexAlpha(in),
|
|
544
486
|
material.alphaCutoff,
|
|
@@ -549,7 +491,7 @@ fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
|
|
|
549
491
|
in.currentClip,
|
|
550
492
|
in.previousClip,
|
|
551
493
|
view.temporalProjection,
|
|
552
|
-
reactive,
|
|
494
|
+
in.reactive,
|
|
553
495
|
in.uv, in.uv1, in.uv2, in.uv3,
|
|
554
496
|
in.uv4, in.uv5, in.uv6, in.uv7,
|
|
555
497
|
);
|