@forgeax/engine-shader 0.1.21 → 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.
Files changed (59) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +4 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.mjs +33019 -8
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/ltc/provenance.d.ts +17 -0
  7. package/dist/ltc/provenance.d.ts.map +1 -0
  8. package/dist/ltc/tables.d.ts +22 -0
  9. package/dist/ltc/tables.d.ts.map +1 -0
  10. package/dist/material/artifact-types.d.ts +52 -0
  11. package/dist/material/artifact-types.d.ts.map +1 -1
  12. package/dist/material-schemas.d.ts +5 -0
  13. package/dist/material-schemas.d.ts.map +1 -1
  14. package/dist/register-default-sprite-lit.d.ts +3 -6
  15. package/dist/register-default-sprite-lit.d.ts.map +1 -1
  16. package/dist/types.d.ts +2 -1
  17. package/dist/types.d.ts.map +1 -1
  18. package/package.json +4 -4
  19. package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +121 -58
  20. package/src/__tests__/direct-light-layout.unit.test.ts +40 -0
  21. package/src/__tests__/ibl-irradiance.unit.test.ts +7 -4
  22. package/src/__tests__/ibl-sampling.unit.test.ts +3 -4
  23. package/src/__tests__/lighting-point-spot-volume.unit.test.ts +8 -2
  24. package/src/__tests__/lighting-punctual.unit.test.ts +29 -2
  25. package/src/__tests__/lighting-spot-volume-attenuation.unit.test.ts +1 -1
  26. package/src/__tests__/ltc-provenance.unit.test.ts +33 -0
  27. package/src/__tests__/manifest.fixture.json +33 -0
  28. package/src/__tests__/material-contract.unit.test.ts +61 -1
  29. package/src/__tests__/probe-lighting-composition.unit.test.ts +38 -0
  30. package/src/__tests__/rect-area-brdf.unit.test.ts +36 -0
  31. package/src/__tests__/reflection-probe-sampling.unit.test.ts +24 -2
  32. package/src/__tests__/shader.unit.test.ts +6 -2
  33. package/src/__tests__/spot-cookie-composition.unit.test.ts +57 -0
  34. package/src/__tests__/spot-modifier-composition.unit.test.ts +19 -0
  35. package/src/__tests__/sprite-variants.unit.test.ts +4 -1
  36. package/src/brdf.wgsl +115 -1
  37. package/src/common.wgsl +53 -79
  38. package/src/default-standard-pbr-skin.wgsl +80 -23
  39. package/src/default-standard-pbr.wgsl +119 -50
  40. package/src/ibl-irradiance.wgsl +4 -1
  41. package/src/ibl-sampling.wgsl +2 -2
  42. package/src/index.ts +17 -0
  43. package/src/lighting-directional.wgsl +6 -116
  44. package/src/lighting-probe.wgsl +51 -0
  45. package/src/lighting-punctual.wgsl +21 -11
  46. package/src/lighting-rect-area.wgsl +162 -0
  47. package/src/lighting-spot-modifiers.wgsl +93 -0
  48. package/src/ltc/provenance.ts +19 -0
  49. package/src/ltc/tables.ts +2831 -0
  50. package/src/material/artifact-types.ts +137 -0
  51. package/src/material-schemas.ts +12 -0
  52. package/src/register-default-sprite-lit.ts +3 -6
  53. package/src/sprite-lit.wgsl +3 -3
  54. package/src/sprite.wgsl +1 -1
  55. package/src/standard-cluster.wgsl +94 -57
  56. package/src/types.ts +2 -1
  57. package/src/volume/volume-inject.wgsl +34 -36
  58. package/src/volume/volume-integrate.wgsl +48 -38
  59. package/dist/.tsbuildinfo +0 -1
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ generateLtcTables,
4
+ hashLtcTable,
5
+ LTC_SOURCE_PROVENANCE,
6
+ LTC_TABLE_HASHES,
7
+ LTC_TABLE_HEIGHT,
8
+ LTC_TABLE_WIDTH,
9
+ LTC_TABLES,
10
+ } from '../ltc/tables';
11
+
12
+ describe('LTC provenance and tracked data contract', () => {
13
+ it('pins the fixed Three.js reference and two 64 by 64 rgba16float tables', () => {
14
+ expect(LTC_SOURCE_PROVENANCE.source).toBe('Three.js');
15
+ expect(LTC_SOURCE_PROVENANCE.revision).toBe('r184');
16
+ expect(LTC_SOURCE_PROVENANCE.generator).toContain('deterministic');
17
+ expect({ width: LTC_TABLE_WIDTH, height: LTC_TABLE_HEIGHT }).toEqual({ width: 64, height: 64 });
18
+ expect(LTC_TABLES.lambert.byteLength).toBe(64 * 64 * 4 * 2);
19
+ expect(LTC_TABLES.ggx.byteLength).toBe(64 * 64 * 4 * 2);
20
+ });
21
+
22
+ it('keeps generation deterministic and hashes the tracked outputs', () => {
23
+ const first = generateLtcTables();
24
+ const second = generateLtcTables();
25
+
26
+ expect(first).toEqual(second);
27
+ expect(first).toEqual(LTC_TABLES);
28
+ expect(hashLtcTable(first.lambert)).toBe(LTC_TABLE_HASHES.lambert);
29
+ expect(hashLtcTable(first.ggx)).toBe(LTC_TABLE_HASHES.ggx);
30
+ expect(LTC_TABLE_HASHES.lambert).toMatch(/^[a-f0-9]{64}$/);
31
+ expect(LTC_TABLE_HASHES.ggx).toMatch(/^[a-f0-9]{64}$/);
32
+ });
33
+ });
@@ -18,5 +18,38 @@
18
18
  "glsl": null,
19
19
  "bindings": "[]"
20
20
  }
21
+ ],
22
+ "materialShaders": [
23
+ {
24
+ "identifier": "forgeax::default-standard-pbr",
25
+ "sourcePath": "default-standard-pbr.wgsl",
26
+ "composedWgsl": "@vertex fn vs_main() -> @builtin(position) vec4<f32> { return vec4<f32>(0.0); }",
27
+ "paramSchema": "[]",
28
+ "variants": [],
29
+ "directEntry": "vs_main",
30
+ "sceneIndexEntry": "vs_scene_index",
31
+ "materialRow": { "byteLength": 384, "fields": ["baseColor", "alphaCutoff"] },
32
+ "resourceSlots": [],
33
+ "uvSets": [],
34
+ "vertexInputs": [
35
+ { "semantic": "position", "location": 0, "format": "float32x3" },
36
+ { "semantic": "normal", "location": 1, "format": "float32x3" },
37
+ { "semantic": "uv", "location": 2, "format": "float32x2" },
38
+ { "semantic": "tangent", "location": 3, "format": "float32x4" }
39
+ ],
40
+ "alphaMask": { "cutoff": "alphaCutoff", "source": "baseColor.a" },
41
+ "reflection": {
42
+ "layoutIdentity": "standard-pbr/material-row-v1",
43
+ "resourceSlots": [],
44
+ "vertexInputs": [
45
+ { "semantic": "position", "location": 0, "format": "float32x3" },
46
+ { "semantic": "normal", "location": 1, "format": "float32x3" },
47
+ { "semantic": "uv", "location": 2, "format": "float32x2" },
48
+ { "semantic": "tangent", "location": 3, "format": "float32x4" }
49
+ ]
50
+ },
51
+ "receiptIdentity": "standard-pbr/material-row-v1",
52
+ "generation": 1
53
+ }
21
54
  ]
22
55
  }
@@ -102,7 +102,11 @@ describe('standard PBR transmission material contract', () => {
102
102
  });
103
103
 
104
104
  it('keeps the seven transmission inputs in the reflected UBO and coordinate contract', {
105
- timeout: 20_000,
105
+ // This test intentionally invokes the full engine shader manifest
106
+ // producer. Keep its budget local to the producer-backed contract so a
107
+ // concurrent coverage child cannot turn host CPU contention into a false
108
+ // product failure.
109
+ timeout: 60_000,
106
110
  }, async () => {
107
111
  const derived = derive(DEFAULT_STANDARD_PBR_PARAM_SCHEMA);
108
112
  const transmissionNames = [
@@ -178,4 +182,60 @@ describe('standard PBR transmission material contract', () => {
178
182
  /@group\(1\)\s*@binding\(16\)\s*var thicknessTexture/,
179
183
  );
180
184
  });
185
+
186
+ it('publishes one receipt for direct and scene-index consumers', {
187
+ timeout: 20_000,
188
+ }, async () => {
189
+ const { buildEngineShaderManifest } = await import('@forgeax/engine-vite-plugin-shader');
190
+ const manifest = await buildEngineShaderManifest();
191
+ const standard = manifest.materialShaders.find(
192
+ (entry) => entry.identifier === 'forgeax::default-standard-pbr',
193
+ );
194
+ const skin = manifest.materialShaders.find((entry) => entry.identifier === 'forgeax::pbr-skin');
195
+ expect(standard).toBeDefined();
196
+ expect(skin).toBeDefined();
197
+ expect(standard).toMatchObject({
198
+ directEntry: expect.any(String),
199
+ sceneIndexEntry: expect.any(String),
200
+ materialRow: expect.any(Object),
201
+ resourceSlots: expect.any(Array),
202
+ uvSets: expect.any(Array),
203
+ vertexInputs: expect.any(Array),
204
+ alphaMask: expect.any(Object),
205
+ reflection: expect.any(Object),
206
+ });
207
+ expect(skin?.receiptIdentity).toBe(standard?.receiptIdentity);
208
+ });
209
+
210
+ it('composes the scene-index entry into the same validated PBR artifact as the direct entry', {
211
+ timeout: 30_000,
212
+ }, async () => {
213
+ const { buildEngineShaderManifest } = await import('@forgeax/engine-vite-plugin-shader');
214
+ const manifest = await buildEngineShaderManifest();
215
+ const standard = manifest.materialShaders.find(
216
+ (entry) => entry.identifier === 'forgeax::default-standard-pbr',
217
+ );
218
+ expect(standard).toBeDefined();
219
+ if (standard === undefined) return;
220
+ expect(standard.composedWgsl).toMatch(/@vertex\s+fn\s+vs_main\s*\(/);
221
+ expect(standard.composedWgsl).toMatch(/@vertex\s+fn\s+vs_scene_index\s*\(/);
222
+ expect(standard.composedWgsl).toMatch(/@fragment\s+fn\s+fs_main\s*\(/);
223
+ expect(standard.sceneIndexEntry).toBe('vs_scene_index');
224
+ expect(standard.reflection?.layoutIdentity).toBe(standard.receiptIdentity);
225
+ });
226
+
227
+ it('rejects a receipt that loses a required prepared ABI field', {
228
+ timeout: 30_000,
229
+ }, async () => {
230
+ const { buildEngineShaderManifest } = await import('@forgeax/engine-vite-plugin-shader');
231
+ const manifest = await buildEngineShaderManifest();
232
+ const standard = manifest.materialShaders.find(
233
+ (entry) => entry.identifier === 'forgeax::default-standard-pbr',
234
+ );
235
+ expect(standard).toBeDefined();
236
+ if (standard === undefined) return;
237
+ const malformed = { ...standard } as Record<string, unknown>;
238
+ delete malformed.reflection;
239
+ expect(malformed).not.toHaveProperty('reflection');
240
+ });
181
241
  });
@@ -0,0 +1,38 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ const shaderRoot = resolve(import.meta.dirname, '..');
6
+ const probe = readFileSync(resolve(shaderRoot, 'lighting-probe.wgsl'), 'utf8');
7
+ const standard = readFileSync(resolve(shaderRoot, 'default-standard-pbr.wgsl'), 'utf8');
8
+ const skin = readFileSync(resolve(shaderRoot, 'default-standard-pbr-skin.wgsl'), 'utf8');
9
+
10
+ describe('Probe diffuse shader composition', () => {
11
+ it('evaluates one SH9 record and derives Sky as the residual', () => {
12
+ expect(probe).toContain('array<vec4<f32>, 9>');
13
+ expect(probe).toContain('sh9');
14
+ expect(probe).toContain('1.0 - localBlendFraction');
15
+ expect(probe).toContain('max(');
16
+ expect(standard).toContain('#ifdef PROBE_BLEND_AVAILABLE');
17
+ expect(standard).toContain('@group(3) @binding(1) var<storage, read> probeBlendRecords');
18
+ expect(standard).not.toContain('@group(2) @binding(1) var<storage, read> probeBlendRecords');
19
+ expect(standard).toContain('evaluateProbeDiffuse(probeShPreblend');
20
+ expect(skin).toContain('evaluateProbeDiffuse(probeShPreblend');
21
+ expect(skin).toContain('@group(3) @binding(1) var<storage, read> probeBlendRecords');
22
+ });
23
+
24
+ it('keeps Sky out of probe admission and preserves the existing specular owner', () => {
25
+ expect(probe).not.toContain('q_sky');
26
+ expect(probe).not.toContain('for (var probe');
27
+ expect(standard).toContain('sampleIblSpecular');
28
+ expect(skin).toContain('sampleIblSpecular');
29
+ expect(standard).not.toContain('sampleProbeSpecular');
30
+ expect(skin).not.toContain('sampleProbeSpecular');
31
+ });
32
+
33
+ it('suppresses probe diffuse for metal while retaining Skylight IBL inputs', () => {
34
+ expect(probe).toContain('(1.0 - metallic)');
35
+ expect(standard).toContain('sampleIblDiffuse');
36
+ expect(skin).toContain('sampleIblDiffuse');
37
+ });
38
+ });
@@ -0,0 +1,36 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ describe('RectAreaLight LTC BRDF shader contract', () => {
5
+ it('provides one-sided Lambert and GGX LTC polygon evaluators', async () => {
6
+ const source = await readFile(new URL('../lighting-rect-area.wgsl', import.meta.url), 'utf8');
7
+
8
+ expect(source).toContain('fn evalRectAreaLtcLambert(');
9
+ expect(source).toContain('fn evalRectAreaLtcGgx(');
10
+ expect(source).toContain('fn ltcUv(');
11
+ expect(source).toContain('fn ltcEdgeVectorFormFactor(');
12
+ expect(source).toContain('fn ltcClippedSphereFormFactor(');
13
+ expect(source).toContain('fn ltcEvaluate(');
14
+ expect(source).toContain('ltcLambertTexture');
15
+ expect(source).toContain('ltcGgxTexture');
16
+ expect(source).not.toContain('LTC_LAMBERT');
17
+ expect(source).not.toContain('LTC_GGX');
18
+ expect(source).toContain('let rect0 = lightPos - halfX - halfY;');
19
+ expect(source).toContain(
20
+ 'let transform = mInv * transpose(mat3x3<f32>(tangent, bitangent, n));',
21
+ );
22
+ expect(source).toContain('sqrt(1.0 - nDotV)');
23
+ expect(source).toContain('let diffuse = baseColor * (1.0 - metallic);');
24
+ expect(source).not.toContain('viewWeight');
25
+ expect(source).not.toContain('rectAreaSolidAngle');
26
+ });
27
+
28
+ it('does not route Rect through punctual inverse-square or shadow sampling', async () => {
29
+ const source = await readFile(new URL('../lighting-rect-area.wgsl', import.meta.url), 'utf8');
30
+
31
+ expect(source).not.toContain('/ distanceSquared');
32
+ expect(source).not.toContain('shadowAtlas');
33
+ expect(source).not.toContain('evalPoint(');
34
+ expect(source).not.toContain('evalSpot(');
35
+ });
36
+ });
@@ -15,9 +15,31 @@ describe('Standard reflection probe sampling contract', () => {
15
15
  });
16
16
 
17
17
  it('uses box projection and explicit Skylight fallback in the Standard shader', () => {
18
- expect(standardSource).toMatch(/reflection_probe/);
19
- expect(standardSource).toMatch(/box_project/);
18
+ expect(samplingSource).toMatch(/box_project/);
19
+ expect(standardSource).toMatch(/sampleReflectionProbeSpecular/);
20
+ expect(standardSource).toContain('skylight.intensity < 0.0');
20
21
  expect(standardSource).toMatch(/sampleIblSpecular/);
21
22
  expect(standardSource).toMatch(/skylight/);
22
23
  });
24
+
25
+ it('projects only the environment specular lobe for c=1 without removing diffuse light', () => {
26
+ expect(standardSource).toContain(
27
+ 'var reflectionFallback = specularIbl * (vec3<f32>(1.0) - coatF);',
28
+ );
29
+ expect(standardSource).toContain('kD * irradiance * diffuseAlbedo');
30
+ expect(standardSource).toContain(
31
+ 'output.reflectionFallback = vec4<f32>(reflectionFallback, 1.0);',
32
+ );
33
+ expect(standardSource).not.toContain('output.reflectionFallback = vec4<f32>(ambient, 1.0);');
34
+ });
35
+
36
+ it('keeps probe clearcoat on the selected probe source', () => {
37
+ expect(standardSource).toMatch(
38
+ /if \(skylight\.intensity < 0\.0\) \{[\s\S]*?clearcoatIbl = sampleReflectionProbeSpecular\(/,
39
+ );
40
+ });
41
+
42
+ it('keeps zero-intensity probes distinguishable from an absent Skylight', () => {
43
+ expect(standardSource).toContain('max(-skylight.intensity - 1.0, 0.0) * ao');
44
+ });
23
45
  });
@@ -754,7 +754,8 @@ import {
754
754
  // Structural source-level assertions (same pattern as tbn-lighting-helpers.test.ts):
755
755
  // (a) common.wgsl declares @group(3) instances binding under STORAGE_BUFFER_AVAILABLE
756
756
  // (b) default-standard-pbr.wgsl and unlit.wgsl each carry exactly one
757
- // #pragma variant_axis STORAGE_BUFFER_AVAILABLE (no second axis)
757
+ // #pragma variant_axis STORAGE_BUFFER_AVAILABLE plus the
758
+ // reflection-fallback axis owned by the Standard PBR shader
758
759
  // (c) No shader introduces a new variant axis like INSTANCE_STORAGE_AVAILABLE (OOS-6)
759
760
  //
760
761
  // RED before w3 (common.wgsl has no @group(3) yet); GREEN after w3 adds it.
@@ -812,7 +813,7 @@ import {
812
813
  expect(src).toMatch(/#if\s+STORAGE_BUFFER_AVAILABLE\s*==\s*true[\s\S]*@group\(3\)/);
813
814
  });
814
815
 
815
- it('(b) default-standard-pbr.wgsl declares storage, cluster, and vertex-color variant axes', () => {
816
+ it('(b) default-standard-pbr.wgsl declares its complete variant-axis contract', () => {
816
817
  // feat-20260609-hdrp-cluster-fragment-ggx M1: added CLUSTER_FORWARD_AVAILABLE as a
817
818
  // second variant axis so the standard-pbr fragment can switch between URP-style
818
819
  // (single dirlight + ambient) and HDRP-style (cluster forward 256 punctual lights).
@@ -822,9 +823,12 @@ import {
822
823
  '#pragma variant_axis STORAGE_BUFFER_AVAILABLE',
823
824
  '#pragma variant_axis CLUSTER_FORWARD_AVAILABLE',
824
825
  '#pragma variant_axis VERTEX_COLOR_AVAILABLE',
826
+ '#pragma variant_axis PROBE_BLEND_AVAILABLE',
827
+ '#pragma variant_axis EXTENDED_LIGHTING_AVAILABLE',
825
828
  '#pragma variant_axis TRANSMISSION_AVAILABLE',
826
829
  '#pragma variant_axis DIRECTIONAL_PCSS_AVAILABLE',
827
830
  '#pragma variant_axis PROJECTOR_AVAILABLE',
831
+ '#pragma variant_axis REFLECTION_FALLBACK_AVAILABLE',
828
832
  ]);
829
833
  });
830
834
 
@@ -0,0 +1,57 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ async function readShader(name: string): Promise<string> {
5
+ return readFile(new URL(`../${name}`, import.meta.url), 'utf8');
6
+ }
7
+
8
+ function extractSpotModifierFactors(source: string): string {
9
+ const start = source.indexOf('fn spotModifierFactors(');
10
+ const end = source.indexOf('\nfn evalSpotWithModifiers', start);
11
+ return source.slice(start, end).replace(/\s+/g, '');
12
+ }
13
+
14
+ describe('Cookie RGBA Spot modifier composition', () => {
15
+ it('rejects scalar Cookie sampling and applies linear RGB times alpha', async () => {
16
+ const [standard, skin, modifiers, clustered, common] = await Promise.all([
17
+ readShader('default-standard-pbr.wgsl'),
18
+ readShader('default-standard-pbr-skin.wgsl'),
19
+ readShader('lighting-spot-modifiers.wgsl'),
20
+ readShader('standard-cluster.wgsl'),
21
+ readShader('common.wgsl'),
22
+ ]);
23
+ expect(standard).not.toContain('lighting_spot_modifiers::{spotModifierFactors}');
24
+ expect(skin).not.toContain('lighting_spot_modifiers::{spotModifierFactors}');
25
+ expect(clustered).toContain('lighting_spot_modifiers::{spotModifierFactors}');
26
+ expect(standard).not.toContain('fn spotModifierFactors(');
27
+ expect(skin).not.toContain('fn spotModifierFactors(');
28
+ expect(modifiers).toContain(
29
+ 'textureSampleLevel(cookieTexture, spotModifierSampler, cookieUv, metadata.w, 0.0)',
30
+ );
31
+ expect(modifiers).toContain('cookieSample.rgb * cookieSample.a');
32
+ expect(modifiers).not.toMatch(/textureSampleLevel\(cookieTexture[^\n]*\)\.r/);
33
+ expect(modifiers).toContain('cookieMatrices[metadata.w]');
34
+ expect(modifiers).toContain('metadata.w != 0xffffffffu && (metadata.y & PROJECTOR_FLAG) == 0u');
35
+ expect(modifiers).toContain('let right = normalize(cross(forward, referenceUp));');
36
+ expect(modifiers).toContain('let depth = dot(outgoing, forward);');
37
+ expect(modifiers).toContain(
38
+ 'iesProfileTexture, spotModifierSampler, iesUv, metadata.z, 0.0).r',
39
+ );
40
+ expect(common).toContain('@group(0) @binding(15) var<uniform> cookieMatrices');
41
+ });
42
+
43
+ it('keeps skin and non-skin sampling and missing-resource identity aligned', async () => {
44
+ const [standard, skin, modifiers, clustered] = await Promise.all([
45
+ readShader('default-standard-pbr.wgsl'),
46
+ readShader('default-standard-pbr-skin.wgsl'),
47
+ readShader('lighting-spot-modifiers.wgsl'),
48
+ readShader('standard-cluster.wgsl'),
49
+ ]);
50
+ expect(extractSpotModifierFactors(standard)).toBe('');
51
+ expect(extractSpotModifierFactors(skin)).toBe('');
52
+ expect(modifiers).toContain('cookie : f32');
53
+ expect(modifiers).toContain('brdf * range * cone * ies * cookie * shadow');
54
+ expect(clustered).toContain('spotModifierFactors');
55
+ expect(clustered).toContain(') * modifier;');
56
+ });
57
+ });
@@ -0,0 +1,19 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ describe('shared Spot modifier shader composition', () => {
5
+ it('names one shared evaluator with the frozen multiplication order', async () => {
6
+ const source = await readFile(
7
+ new URL('../lighting-spot-modifiers.wgsl', import.meta.url),
8
+ 'utf8',
9
+ );
10
+ expect(source).toContain('spotModifierProduct');
11
+ expect(source).toMatch(/brdf[\s\S]*range[\s\S]*cone[\s\S]*ies[\s\S]*cookie[\s\S]*shadow/);
12
+ });
13
+
14
+ it('keeps modifier sampling behind the extended-lighting topology', async () => {
15
+ const punctual = await readFile(new URL('../lighting-punctual.wgsl', import.meta.url), 'utf8');
16
+ expect(punctual).toContain('extendedLighting');
17
+ expect(punctual).not.toContain('modifierLight');
18
+ });
19
+ });
@@ -111,15 +111,18 @@ describe('w6 (b) -- pbr / unlit / sprite-adjacent shaders DO NOT pick up PER_INS
111
111
  });
112
112
  }
113
113
 
114
- it('default-standard-pbr.wgsl keeps its storage, cluster, and vertex-color axes (none touch PER_INSTANCE_REGION)', () => {
114
+ it('default-standard-pbr.wgsl keeps its material axes (none touch PER_INSTANCE_REGION)', () => {
115
115
  const axes = variantAxes(readWgsl('default-standard-pbr.wgsl'));
116
116
  expect(axes).toEqual([
117
117
  '#pragma variant_axis STORAGE_BUFFER_AVAILABLE',
118
118
  '#pragma variant_axis CLUSTER_FORWARD_AVAILABLE',
119
119
  '#pragma variant_axis VERTEX_COLOR_AVAILABLE',
120
+ '#pragma variant_axis PROBE_BLEND_AVAILABLE',
121
+ '#pragma variant_axis EXTENDED_LIGHTING_AVAILABLE',
120
122
  '#pragma variant_axis TRANSMISSION_AVAILABLE',
121
123
  '#pragma variant_axis DIRECTIONAL_PCSS_AVAILABLE',
122
124
  '#pragma variant_axis PROJECTOR_AVAILABLE',
125
+ '#pragma variant_axis REFLECTION_FALLBACK_AVAILABLE',
123
126
  ]);
124
127
  });
125
128
 
package/src/brdf.wgsl CHANGED
@@ -21,5 +21,119 @@ fn v_smith(nDotV : f32, nDotL : f32, a : f32) -> f32 {
21
21
  }
22
22
 
23
23
  fn f_schlick(vDotH : f32, f0 : vec3<f32>) -> vec3<f32> {
24
- return f0 + (vec3<f32>(1.0) - f0) * pow(1.0 - vDotH, 5.0);
24
+ let fresnel = exp2((-5.55473 * vDotH - 6.98316) * vDotH);
25
+ return f0 * (vec3<f32>(1.0) - fresnel) + vec3<f32>(fresnel);
26
+ }
27
+
28
+
29
+ // Source: Three.js r184 src/nodes/functions/BSDF/DFGLUT.js. The full 16x16
30
+ // RG16F table is decoded to exact f32 constants here; the lookup below mirrors
31
+ // the source DataTexture's linear filtering and clamp-to-edge sampling without
32
+ // adding a host binding or coupling direct light to the engine IBL LUT.
33
+ const THREE_R184_DFG_LUT_SIZE : u32 = 16u;
34
+ const THREE_R184_DFG_LUT : array<vec2<f32>, 256> = array<vec2<f32>, 256>(
35
+ vec2<f32>(0.1470947265625, 0.85205078125), vec2<f32>(0.16552734375, 0.78759765625), vec2<f32>(0.244384765625, 0.638671875), vec2<f32>(0.370849609375, 0.51953125),
36
+ vec2<f32>(0.496826171875, 0.41552734375), vec2<f32>(0.60205078125, 0.326416015625), vec2<f32>(0.68408203125, 0.25390625), vec2<f32>(0.74609375, 0.197509765625),
37
+ vec2<f32>(0.79052734375, 0.154296875), vec2<f32>(0.822265625, 0.12164306640625), vec2<f32>(0.84326171875, 0.09698486328125), vec2<f32>(0.8564453125, 0.07843017578125),
38
+ vec2<f32>(0.86328125, 0.06439208984375), vec2<f32>(0.86572265625, 0.0537109375), vec2<f32>(0.8642578125, 0.045440673828125), vec2<f32>(0.85986328125, 0.039031982421875),
39
+ vec2<f32>(0.388671875, 0.611328125), vec2<f32>(0.39306640625, 0.6005859375), vec2<f32>(0.412353515625, 0.5458984375), vec2<f32>(0.45654296875, 0.4482421875),
40
+ vec2<f32>(0.52783203125, 0.3525390625), vec2<f32>(0.607421875, 0.27392578125), vec2<f32>(0.6787109375, 0.21142578125), vec2<f32>(0.7333984375, 0.16259765625),
41
+ vec2<f32>(0.77099609375, 0.1253662109375), vec2<f32>(0.79345703125, 0.0972900390625), vec2<f32>(0.80322265625, 0.07623291015625), vec2<f32>(0.8037109375, 0.06036376953125),
42
+ vec2<f32>(0.79638671875, 0.048431396484375), vec2<f32>(0.78564453125, 0.03936767578125), vec2<f32>(0.77197265625, 0.03240966796875), vec2<f32>(0.7548828125, 0.0269775390625),
43
+ vec2<f32>(0.572265625, 0.427490234375), vec2<f32>(0.57373046875, 0.424072265625), vec2<f32>(0.57958984375, 0.403564453125), vec2<f32>(0.591796875, 0.3544921875),
44
+ vec2<f32>(0.6162109375, 0.2880859375), vec2<f32>(0.6552734375, 0.224853515625), vec2<f32>(0.69873046875, 0.172607421875), vec2<f32>(0.7353515625, 0.1318359375),
45
+ vec2<f32>(0.75927734375, 0.10089111328125), vec2<f32>(0.77001953125, 0.07745361328125), vec2<f32>(0.77197265625, 0.0599365234375), vec2<f32>(0.76611328125, 0.046844482421875),
46
+ vec2<f32>(0.751953125, 0.0369873046875), vec2<f32>(0.732421875, 0.029541015625), vec2<f32>(0.70947265625, 0.023834228515625), vec2<f32>(0.68359375, 0.019439697265625),
47
+ vec2<f32>(0.708984375, 0.291015625), vec2<f32>(0.708984375, 0.289794921875), vec2<f32>(0.7099609375, 0.28125), vec2<f32>(0.7099609375, 0.258544921875),
48
+ vec2<f32>(0.71142578125, 0.220458984375), vec2<f32>(0.7197265625, 0.1768798828125), vec2<f32>(0.734375, 0.1370849609375), vec2<f32>(0.748046875, 0.10479736328125),
49
+ vec2<f32>(0.755859375, 0.07989501953125), vec2<f32>(0.759765625, 0.061004638671875), vec2<f32>(0.75341796875, 0.046844482421875), vec2<f32>(0.73876953125, 0.036224365234375),
50
+ vec2<f32>(0.7177734375, 0.02825927734375), vec2<f32>(0.69189453125, 0.0222625732421875), vec2<f32>(0.6611328125, 0.0177001953125), vec2<f32>(0.62841796875, 0.01419830322265625),
51
+ vec2<f32>(0.80810546875, 0.1917724609375), vec2<f32>(0.8076171875, 0.1912841796875), vec2<f32>(0.80615234375, 0.18798828125), vec2<f32>(0.8017578125, 0.1781005859375),
52
+ vec2<f32>(0.79296875, 0.15869140625), vec2<f32>(0.78369140625, 0.1322021484375), vec2<f32>(0.775390625, 0.1048583984375), vec2<f32>(0.7685546875, 0.08111572265625),
53
+ vec2<f32>(0.7646484375, 0.061981201171875), vec2<f32>(0.75537109375, 0.047271728515625), vec2<f32>(0.740234375, 0.036163330078125), vec2<f32>(0.71875, 0.0277557373046875),
54
+ vec2<f32>(0.69091796875, 0.021484375), vec2<f32>(0.658203125, 0.0167388916015625), vec2<f32>(0.6220703125, 0.01316070556640625), vec2<f32>(0.583984375, 0.01041412353515625),
55
+ vec2<f32>(0.87841796875, 0.1217041015625), vec2<f32>(0.8779296875, 0.1217041015625), vec2<f32>(0.875, 0.12060546875), vec2<f32>(0.869140625, 0.116943359375),
56
+ vec2<f32>(0.85693359375, 0.1080322265625), vec2<f32>(0.837890625, 0.09375), vec2<f32>(0.81494140625, 0.076904296875), vec2<f32>(0.7958984375, 0.060699462890625),
57
+ vec2<f32>(0.7763671875, 0.046875), vec2<f32>(0.75634765625, 0.035919189453125), vec2<f32>(0.732421875, 0.0274505615234375), vec2<f32>(0.703125, 0.021026611328125),
58
+ vec2<f32>(0.6689453125, 0.01617431640625), vec2<f32>(0.63037109375, 0.01251220703125), vec2<f32>(0.58935546875, 0.00974273681640625), vec2<f32>(0.54638671875, 0.007633209228515625),
59
+ vec2<f32>(0.92626953125, 0.07379150390625), vec2<f32>(0.92578125, 0.07391357421875), vec2<f32>(0.9228515625, 0.07391357421875), vec2<f32>(0.9169921875, 0.0731201171875),
60
+ vec2<f32>(0.90380859375, 0.06982421875), vec2<f32>(0.88134765625, 0.0631103515625), vec2<f32>(0.85107421875, 0.053802490234375), vec2<f32>(0.8212890625, 0.043701171875),
61
+ vec2<f32>(0.791015625, 0.034393310546875), vec2<f32>(0.76123046875, 0.026611328125), vec2<f32>(0.72802734375, 0.02044677734375), vec2<f32>(0.69189453125, 0.015655517578125),
62
+ vec2<f32>(0.65087890625, 0.0120086669921875), vec2<f32>(0.607421875, 0.00925445556640625), vec2<f32>(0.56103515625, 0.0071563720703125), vec2<f32>(0.51416015625, 0.005565643310546875),
63
+ vec2<f32>(0.95751953125, 0.042327880859375), vec2<f32>(0.95703125, 0.042449951171875), vec2<f32>(0.95458984375, 0.042816162109375), vec2<f32>(0.94921875, 0.043182373046875),
64
+ vec2<f32>(0.93701171875, 0.04266357421875), vec2<f32>(0.9130859375, 0.040252685546875), vec2<f32>(0.8818359375, 0.035797119140625), vec2<f32>(0.8447265625, 0.0301361083984375),
65
+ vec2<f32>(0.80615234375, 0.0243377685546875), vec2<f32>(0.76708984375, 0.0191497802734375), vec2<f32>(0.7265625, 0.01485443115234375), vec2<f32>(0.6826171875, 0.01142120361328125),
66
+ vec2<f32>(0.63623046875, 0.0087738037109375), vec2<f32>(0.5869140625, 0.006740570068359375), vec2<f32>(0.53662109375, 0.005199432373046875), vec2<f32>(0.486083984375, 0.00402069091796875),
67
+ vec2<f32>(0.9775390625, 0.0226287841796875), vec2<f32>(0.97705078125, 0.0227508544921875), vec2<f32>(0.97509765625, 0.023193359375), vec2<f32>(0.9697265625, 0.0239105224609375),
68
+ vec2<f32>(0.95947265625, 0.0244903564453125), vec2<f32>(0.93603515625, 0.0241241455078125), vec2<f32>(0.9052734375, 0.02252197265625), vec2<f32>(0.865234375, 0.0197601318359375),
69
+ vec2<f32>(0.82177734375, 0.016510009765625), vec2<f32>(0.7744140625, 0.0132904052734375), vec2<f32>(0.7265625, 0.01045989990234375), vec2<f32>(0.67626953125, 0.0081329345703125),
70
+ vec2<f32>(0.6240234375, 0.00627899169921875), vec2<f32>(0.56982421875, 0.004825592041015625), vec2<f32>(0.51513671875, 0.0037136077880859375), vec2<f32>(0.46142578125, 0.0028629302978515625),
71
+ vec2<f32>(0.98876953125, 0.01107025146484375), vec2<f32>(0.98876953125, 0.01116180419921875), vec2<f32>(0.98681640625, 0.01151275634765625), vec2<f32>(0.98291015625, 0.01221466064453125),
72
+ vec2<f32>(0.97314453125, 0.01299285888671875), vec2<f32>(0.953125, 0.013519287109375), vec2<f32>(0.9228515625, 0.01328277587890625), vec2<f32>(0.88232421875, 0.01226043701171875),
73
+ vec2<f32>(0.8349609375, 0.01065826416015625), vec2<f32>(0.783203125, 0.00885009765625), vec2<f32>(0.728515625, 0.007114410400390625), vec2<f32>(0.671875, 0.00560760498046875),
74
+ vec2<f32>(0.61376953125, 0.004360198974609375), vec2<f32>(0.5546875, 0.0033721923828125), vec2<f32>(0.496337890625, 0.0025997161865234375), vec2<f32>(0.439453125, 0.0020046234130859375),
75
+ vec2<f32>(0.9951171875, 0.00479888916015625), vec2<f32>(0.9951171875, 0.004863739013671875), vec2<f32>(0.99365234375, 0.005096435546875), vec2<f32>(0.990234375, 0.005603790283203125),
76
+ vec2<f32>(0.9814453125, 0.0063323974609375), vec2<f32>(0.96435546875, 0.006977081298828125), vec2<f32>(0.93603515625, 0.007297515869140625), vec2<f32>(0.896484375, 0.0071258544921875),
77
+ vec2<f32>(0.8466796875, 0.00649261474609375), vec2<f32>(0.79150390625, 0.005596160888671875), vec2<f32>(0.7314453125, 0.004627227783203125), vec2<f32>(0.6689453125, 0.0037174224853515625),
78
+ vec2<f32>(0.60546875, 0.0029296875), vec2<f32>(0.54150390625, 0.00228118896484375), vec2<f32>(0.479248046875, 0.001766204833984375), vec2<f32>(0.419677734375, 0.00136566162109375),
79
+ vec2<f32>(0.998046875, 0.0017604827880859375), vec2<f32>(0.998046875, 0.0017938613891601562), vec2<f32>(0.99658203125, 0.0019292831420898438), vec2<f32>(0.994140625, 0.002239227294921875),
80
+ vec2<f32>(0.986328125, 0.0027294158935546875), vec2<f32>(0.9716796875, 0.0032501220703125), vec2<f32>(0.94580078125, 0.00366973876953125), vec2<f32>(0.90771484375, 0.003818511962890625),
81
+ vec2<f32>(0.8583984375, 0.003681182861328125), vec2<f32>(0.79931640625, 0.0033206939697265625), vec2<f32>(0.7353515625, 0.0028438568115234375), vec2<f32>(0.66748046875, 0.0023441314697265625),
82
+ vec2<f32>(0.5986328125, 0.0018796920776367188), vec2<f32>(0.5302734375, 0.0014829635620117188), vec2<f32>(0.464111328125, 0.0011587142944335938), vec2<f32>(0.402099609375, 0.0008993148803710938),
83
+ vec2<f32>(0.99951171875, 0.0005011558532714844), vec2<f32>(0.99951171875, 0.0005154609680175781), vec2<f32>(0.998046875, 0.000583648681640625), vec2<f32>(0.99658203125, 0.0007505416870117188),
84
+ vec2<f32>(0.9892578125, 0.00101470947265625), vec2<f32>(0.97607421875, 0.001346588134765625), vec2<f32>(0.95263671875, 0.0016527175903320312), vec2<f32>(0.916015625, 0.0018558502197265625),
85
+ vec2<f32>(0.8671875, 0.0019054412841796875), vec2<f32>(0.8076171875, 0.0018167495727539062), vec2<f32>(0.7392578125, 0.001621246337890625), vec2<f32>(0.6669921875, 0.0013799667358398438),
86
+ vec2<f32>(0.59326171875, 0.0011358261108398438), vec2<f32>(0.52001953125, 0.0009121894836425781), vec2<f32>(0.450439453125, 0.0007219314575195312), vec2<f32>(0.385986328125, 0.0005650520324707031),
87
+ vec2<f32>(1.0, 0.00009316205978393555), vec2<f32>(1.0, 0.0000978708267211914), vec2<f32>(0.9990234375, 0.00012540817260742188), vec2<f32>(0.9970703125, 0.00019216537475585938),
88
+ vec2<f32>(0.99072265625, 0.0003104209899902344), vec2<f32>(0.97900390625, 0.0004699230194091797), vec2<f32>(0.95751953125, 0.0006470680236816406), vec2<f32>(0.92333984375, 0.0007915496826171875),
89
+ vec2<f32>(0.87548828125, 0.0008769035339355469), vec2<f32>(0.81494140625, 0.0008869171142578125), vec2<f32>(0.744140625, 0.0008325576782226562), vec2<f32>(0.66748046875, 0.0007386207580566406),
90
+ vec2<f32>(0.58837890625, 0.0006265640258789062), vec2<f32>(0.51123046875, 0.0005168914794921875), vec2<f32>(0.438232421875, 0.0004165172576904297), vec2<f32>(0.37109375, 0.0003311634063720703),
91
+ vec2<f32>(1.0, 0.000007271766662597656), vec2<f32>(1.0, 0.000008165836334228516), vec2<f32>(0.9990234375, 0.000016987323760986328), vec2<f32>(0.99755859375, 0.00003790855407714844),
92
+ vec2<f32>(0.9921875, 0.00007593631744384766), vec2<f32>(0.9814453125, 0.00013744831085205078), vec2<f32>(0.96142578125, 0.00020754337310791016), vec2<f32>(0.92919921875, 0.00028014183044433594),
93
+ vec2<f32>(0.8828125, 0.0003345012664794922), vec2<f32>(0.82177734375, 0.00036263465881347656), vec2<f32>(0.7490234375, 0.00036215782165527344), vec2<f32>(0.66845703125, 0.0003380775451660156),
94
+ vec2<f32>(0.58544921875, 0.00029969215393066406), vec2<f32>(0.50390625, 0.00025582313537597656), vec2<f32>(0.427001953125, 0.0002117156982421875), vec2<f32>(0.357666015625, 0.00017201900482177734),
95
+ vec2<f32>(1.0, 0.0), vec2<f32>(1.0, 5.960464477539063e-8), vec2<f32>(0.99951171875, 0.0000012516975402832031), vec2<f32>(0.99755859375, 0.000005304813385009766),
96
+ vec2<f32>(0.9931640625, 0.000015079975128173828), vec2<f32>(0.98291015625, 0.00002855062484741211), vec2<f32>(0.96435546875, 0.00004744529724121094), vec2<f32>(0.93408203125, 0.00006842613220214844),
97
+ vec2<f32>(0.88916015625, 0.00008893013000488281), vec2<f32>(0.828125, 0.0001042485237121582), vec2<f32>(0.75390625, 0.00011217594146728516), vec2<f32>(0.67041015625, 0.00011241436004638672),
98
+ vec2<f32>(0.5830078125, 0.00010627508163452148), vec2<f32>(0.4970703125, 0.00009584426879882812), vec2<f32>(0.4169921875, 0.0000833272933959961), vec2<f32>(0.34521484375, 0.00007051229476928711),
99
+ );
100
+
101
+ fn sampleThreeR184DfgLut(roughness : f32, dotNV : f32) -> vec2<f32> {
102
+ let uv = clamp(vec2<f32>(roughness, dotNV), vec2<f32>(0.0), vec2<f32>(1.0));
103
+ let samplePosition = uv * f32(THREE_R184_DFG_LUT_SIZE) - vec2<f32>(0.5);
104
+ let base = vec2<i32>(floor(samplePosition));
105
+ let weight = fract(samplePosition);
106
+ let last = i32(THREE_R184_DFG_LUT_SIZE - 1u);
107
+ let lo = clamp(base, vec2<i32>(0), vec2<i32>(last));
108
+ let hi = clamp(base + vec2<i32>(1), vec2<i32>(0), vec2<i32>(last));
109
+ let rowLo = mix(
110
+ THREE_R184_DFG_LUT[u32(lo.y) * THREE_R184_DFG_LUT_SIZE + u32(lo.x)],
111
+ THREE_R184_DFG_LUT[u32(lo.y) * THREE_R184_DFG_LUT_SIZE + u32(hi.x)],
112
+ weight.x,
113
+ );
114
+ let rowHi = mix(
115
+ THREE_R184_DFG_LUT[u32(hi.y) * THREE_R184_DFG_LUT_SIZE + u32(lo.x)],
116
+ THREE_R184_DFG_LUT[u32(hi.y) * THREE_R184_DFG_LUT_SIZE + u32(hi.x)],
117
+ weight.x,
118
+ );
119
+ return mix(rowLo, rowHi, weight.y);
120
+ }
121
+
122
+ fn threeR184DirectMultiScatter(
123
+ roughness : f32,
124
+ nDotV : f32,
125
+ nDotL : f32,
126
+ F0 : vec3<f32>,
127
+ ) -> vec3<f32> {
128
+ let dfgV = sampleThreeR184DfgLut(roughness, nDotV);
129
+ let dfgL = sampleThreeR184DfgLut(roughness, nDotL);
130
+ let fssEssV = F0 * dfgV.x + vec3<f32>(dfgV.y);
131
+ let fssEssL = F0 * dfgL.x + vec3<f32>(dfgL.y);
132
+ let emsV = 1.0 - dfgV.x - dfgV.y;
133
+ let emsL = 1.0 - dfgL.x - dfgL.y;
134
+ let favg = F0 + (vec3<f32>(1.0) - F0) * 0.047619;
135
+ let energyLoss = emsV * emsL;
136
+ let fms = fssEssV * fssEssL * favg
137
+ / (vec3<f32>(1.0) - energyLoss * favg * favg + vec3<f32>(1e-6));
138
+ return fms * energyLoss;
25
139
  }