@forgeax/engine-shader 0.1.23 → 0.1.24
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 +13 -4
- package/dist/ShaderRegistry.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +28 -52
- package/dist/index.mjs.map +1 -1
- package/dist/material-schemas.d.ts +31 -0
- package/dist/material-schemas.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ShaderRegistry.ts +0 -17
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +9 -3
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +48 -166
- package/src/__tests__/deferred-lighting-ssao.test.ts +3 -3
- package/src/__tests__/ibl-irradiance.unit.test.ts +9 -0
- package/src/__tests__/lighting-directional.unit.test.ts +36 -0
- package/src/__tests__/lighting-punctual.unit.test.ts +12 -4
- package/src/__tests__/material-builtins.unit.test.ts +21 -3
- package/src/__tests__/material-contract.unit.test.ts +131 -17
- package/src/__tests__/physical-clearcoat.unit.test.ts +65 -0
- package/src/__tests__/probe-lighting-composition.unit.test.ts +3 -2
- package/src/__tests__/shader-manifest-transmission.unit.test.ts +8 -10
- package/src/__tests__/shader.unit.test.ts +52 -87
- package/src/__tests__/spot-cookie-composition.unit.test.ts +2 -1
- package/src/__tests__/standard-webgl2-varying-budget.unit.test.ts +17 -0
- package/src/__tests__/surface-v1-negative.unit.test.ts +37 -0
- package/src/__tests__/surface-v1.unit.test.ts +83 -0
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +2 -6
- package/src/__tests__/vertex-color-variant.unit.test.ts +16 -5
- package/src/default-standard-pbr-skin.wgsl +678 -192
- package/src/default-standard-pbr.wgsl +563 -279
- package/src/default_standard_surface.wgsl +88 -0
- package/src/hdrp-cluster-forward.wgsl +225 -0
- package/src/ibl-irradiance.wgsl +43 -8
- package/src/ibl-prefilter.wgsl +13 -3
- package/src/ibl-shared.wgsl +9 -2
- package/src/index.ts +6 -0
- package/src/lighting-directional.wgsl +3 -1
- package/src/lighting-spot-modifiers.wgsl +8 -1
- package/src/lighting-spot-projector.wgsl +31 -0
- package/src/material/artifact-types.ts +2 -2
- package/src/material/physical/anisotropy.wgsl +26 -0
- package/src/material/physical/clearcoat.wgsl +24 -0
- package/src/material/physical/iridescence.wgsl +23 -0
- package/src/material/physical/sheen.wgsl +17 -0
- package/src/material-schemas.ts +65 -30
- package/src/register-default-standard-pbr-skin.ts +2 -2
- package/src/standard-cluster.wgsl +1 -36
- package/src/surface_v1.wgsl +26 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
const surfaceSource = readFileSync(
|
|
6
|
+
fileURLToPath(new URL('../surface_v1.wgsl', import.meta.url)),
|
|
7
|
+
'utf8',
|
|
8
|
+
);
|
|
9
|
+
const defaultSource = readFileSync(
|
|
10
|
+
fileURLToPath(new URL('../default_standard_surface.wgsl', import.meta.url)),
|
|
11
|
+
'utf8',
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const inputFields = [
|
|
15
|
+
['positionOS', 'vec3<f32>'],
|
|
16
|
+
['positionWS', 'vec3<f32>'],
|
|
17
|
+
['geometricNormalWS', 'vec3<f32>'],
|
|
18
|
+
['tangentWS', 'vec4<f32>'],
|
|
19
|
+
['viewDirectionWS', 'vec3<f32>'],
|
|
20
|
+
['uv0', 'vec2<f32>'],
|
|
21
|
+
['uv1', 'vec2<f32>'],
|
|
22
|
+
['vertexColor', 'vec4<f32>'],
|
|
23
|
+
['frontFacing', 'bool'],
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
const dataFields = [
|
|
27
|
+
['baseColor', 'vec3<f32>'],
|
|
28
|
+
['normalWS', 'vec3<f32>'],
|
|
29
|
+
['metallic', 'f32'],
|
|
30
|
+
['roughness', 'f32'],
|
|
31
|
+
['emissive', 'vec3<f32>'],
|
|
32
|
+
['occlusion', 'f32'],
|
|
33
|
+
['opacity', 'f32'],
|
|
34
|
+
['alphaClipThreshold', 'f32'],
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
function assertOrderedFields(source: string, fields: readonly (readonly [string, string])[]) {
|
|
38
|
+
let previous = -1;
|
|
39
|
+
for (const [name, type] of fields) {
|
|
40
|
+
const index = new RegExp(`${name}\\s*:\\s*${type.replace(/[<>]/g, '\\$&')}`).exec(
|
|
41
|
+
source,
|
|
42
|
+
)?.index;
|
|
43
|
+
expect(index, `Surface ABI must contain ${name}: ${type}`).toBeGreaterThan(previous);
|
|
44
|
+
previous = index ?? previous;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('surface_v1 ABI', () => {
|
|
49
|
+
it('publishes one versioned base-only ABI in stable field order', () => {
|
|
50
|
+
expect(surfaceSource).toContain('#define_import_path forgeax_material::surface_v1');
|
|
51
|
+
expect(surfaceSource).toContain('struct SurfaceInput');
|
|
52
|
+
expect(surfaceSource).toContain('struct SurfaceData');
|
|
53
|
+
assertOrderedFields(surfaceSource, inputFields);
|
|
54
|
+
assertOrderedFields(surfaceSource, dataFields);
|
|
55
|
+
expect(surfaceSource).not.toMatch(/clearcoat|anisotropy|sheen|iridescence|specular/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('keeps authored Surface entry points free of stage and resource ownership', () => {
|
|
59
|
+
const authoredSource = `
|
|
60
|
+
#import forgeax_material::surface_v1::{SurfaceInput, SurfaceData}
|
|
61
|
+
fn evaluate_surface(input : SurfaceInput) -> SurfaceData {
|
|
62
|
+
return SurfaceData(vec3<f32>(1.0), input.geometricNormalWS, 0.0, 0.5, vec3<f32>(0.0), 1.0, 1.0, 0.0);
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
expect(authoredSource).toMatch(
|
|
66
|
+
/fn\s+evaluate_surface\s*\(input\s*:\s*SurfaceInput\s*\)\s*->\s*SurfaceData/,
|
|
67
|
+
);
|
|
68
|
+
expect(authoredSource.match(/fn\s+evaluate_surface\s*\(/g)).toHaveLength(1);
|
|
69
|
+
expect(authoredSource).not.toMatch(/@(fragment|vertex|compute)|@(group|binding)\s*\(/);
|
|
70
|
+
expect(surfaceSource).not.toMatch(/@(fragment|vertex|compute)|@(group|binding)\s*\(/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('keeps the default evaluator in the same Surface ABI family', () => {
|
|
74
|
+
expect(defaultSource).toContain(
|
|
75
|
+
'#define_import_path forgeax_material::default_standard_surface',
|
|
76
|
+
);
|
|
77
|
+
expect(defaultSource).toMatch(
|
|
78
|
+
/fn\s+evaluate_surface\s*\(input\s*:\s*SurfaceInput\s*\)\s*->\s*SurfaceData/,
|
|
79
|
+
);
|
|
80
|
+
expect(defaultSource).toContain('input.geometricNormalWS');
|
|
81
|
+
expect(defaultSource).not.toMatch(/@(fragment|vertex|compute)|@(group|binding)\s*\(/);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -6,16 +6,12 @@ const shader = readFileSync(resolve(import.meta.dirname, '../default-standard-pb
|
|
|
6
6
|
|
|
7
7
|
describe('standard transmission thickness scale contract', () => {
|
|
8
8
|
it('converts local authored thickness through the combined local-to-world basis', () => {
|
|
9
|
-
expect(shader).toContain('let localToWorld = entityWorld * instanceLocal;');
|
|
10
9
|
expect(shader).toContain('@location(4) @interpolate(flat) transmissionBasis0 : vec4<f32>');
|
|
11
|
-
expect(shader).toContain('@location(
|
|
10
|
+
expect(shader).toContain('@location(13) @interpolate(flat) transmissionBasis1 : vec4<f32>');
|
|
11
|
+
expect(shader).not.toContain('@location(15)');
|
|
12
12
|
expect(shader).toContain('let localToWorld0 = in.transmissionBasis0.xyz;');
|
|
13
13
|
expect(shader).toContain('let localToWorld1 = vec3<f32>(');
|
|
14
14
|
expect(shader).toContain('let localToWorld2 = vec3<f32>(');
|
|
15
|
-
expect(shader).toContain('in.ndc.w,');
|
|
16
|
-
expect(shader).not.toContain(
|
|
17
|
-
'let localToWorld = meshes[0].worldFromLocal * instances[in.instanceIdx].localFromInstance;',
|
|
18
|
-
);
|
|
19
15
|
expect(shader).toContain('let worldToLocal0 = vec3<f32>(');
|
|
20
16
|
expect(shader).toContain('let worldRefractedDirection =');
|
|
21
17
|
expect(shader).toMatch(
|
|
@@ -23,7 +23,7 @@ describe('M4 vertex-color shader contract', () => {
|
|
|
23
23
|
/#ifdef\s+VERTEX_COLOR_AVAILABLE[\s\S]*?@location\(14\)\s+color\s*:\s*vec4<f32>/,
|
|
24
24
|
);
|
|
25
25
|
if (file !== 'unlit.wgsl') {
|
|
26
|
-
expect(text).toMatch(/@location\(
|
|
26
|
+
expect(text).toMatch(/@location\(12\)\s+uv6And7\s*:\s*vec4<f32>/);
|
|
27
27
|
}
|
|
28
28
|
});
|
|
29
29
|
|
|
@@ -38,15 +38,26 @@ describe('M4 vertex-color shader contract', () => {
|
|
|
38
38
|
|
|
39
39
|
it.each(SOURCES)('%s applies vertex color to linear RGB and all alpha consumers', (file) => {
|
|
40
40
|
const text = source(file);
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
if (file === 'unlit.wgsl') {
|
|
42
|
+
expect(text).toMatch(/baseColor\.rgb\s*\*\s*texSample\.rgb\s*\*\s*vertexColor\.rgb/);
|
|
43
|
+
expect(text).toMatch(/baseColor\.a\s*\*\s*texSample\.a\s*\*\s*vertexColor\.a/);
|
|
44
|
+
} else {
|
|
45
|
+
expect(text).toContain('#import forgeax_material::slot::surface::{evaluate_surface}');
|
|
46
|
+
}
|
|
43
47
|
expect(text).toMatch(/alphaCutoff/);
|
|
44
48
|
expect(text).toMatch(/fs_temporal/);
|
|
45
49
|
});
|
|
46
50
|
|
|
47
|
-
it('
|
|
51
|
+
it('canonical Standard surface applies vertex color to linear RGB and alpha', () => {
|
|
52
|
+
const text = readFileSync(new URL('../default_standard_surface.wgsl', import.meta.url), 'utf8');
|
|
53
|
+
expect(text).toMatch(/baseColor\.rgb\s*\*\s*baseSample\.rgb\s*\*\s*vertexColor\.rgb/);
|
|
54
|
+
expect(text).toMatch(/baseColor\.a\s*\*\s*baseSample\.a\s*\*\s*vertexColor\.a/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('packs the high UV pair while reserving color at 14', () => {
|
|
48
58
|
const text = source('default-standard-pbr.wgsl');
|
|
49
|
-
expect(text).toMatch(/@location\(
|
|
59
|
+
expect(text).toMatch(/@location\(12\)\s+uv6And7\s*:\s*vec4<f32>/);
|
|
60
|
+
expect(text).toContain('in.uv6And7.zw');
|
|
50
61
|
expect(text).toMatch(/@location\(14\)\s+color\s*:\s*vec4<f32>/);
|
|
51
62
|
});
|
|
52
63
|
});
|