@forgeax/engine-render 0.1.24 → 0.1.26
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 +118 -1
- package/dist/assembly/factory.d.ts +1 -1
- package/dist/assembly/factory.d.ts.map +1 -1
- package/dist/assembly/material/assembly.d.ts +4 -17
- package/dist/assembly/material/assembly.d.ts.map +1 -1
- package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -1
- package/dist/assembly/material/surface-projection.d.ts +24 -0
- package/dist/assembly/material/surface-projection.d.ts.map +1 -0
- package/dist/assembly/material-shader-policy.d.ts +20 -0
- package/dist/assembly/material-shader-policy.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts +1 -2
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/assembly/webgpu-vertex-layouts.d.ts +89 -0
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
- package/dist/assets/asset-decoders.d.ts.map +1 -1
- package/dist/authoring.mjs +1 -1
- package/dist/{chunk-Q752SHI5.mjs → chunk-ADHHVYLW.mjs} +3 -3
- package/dist/chunk-ADHHVYLW.mjs.map +1 -0
- package/dist/{chunk-TFH5I7JJ.mjs → chunk-E34VL5VI.mjs} +3 -3
- package/dist/{chunk-TFH5I7JJ.mjs.map → chunk-E34VL5VI.mjs.map} +1 -1
- package/dist/{chunk-YKB5DQV7.mjs → chunk-GF523LHF.mjs} +89 -21
- package/dist/chunk-GF523LHF.mjs.map +1 -0
- package/dist/{chunk-KZFTK6DO.mjs → chunk-HKXTW355.mjs} +7 -4
- package/dist/chunk-HKXTW355.mjs.map +1 -0
- package/dist/{chunk-UQYS7FRM.mjs → chunk-KM2NOX2I.mjs} +4 -4
- package/dist/{chunk-UQYS7FRM.mjs.map → chunk-KM2NOX2I.mjs.map} +1 -1
- package/dist/{chunk-YRUYGA4A.mjs → chunk-TMIASV2N.mjs} +944 -577
- package/dist/chunk-TMIASV2N.mjs.map +1 -0
- package/dist/{chunk-Z3YYH2MB.mjs → chunk-YUQXSEHG.mjs} +160 -73
- package/dist/chunk-YUQXSEHG.mjs.map +1 -0
- package/dist/{chunk-RR5L4SRS.mjs → chunk-ZZ4YQ474.mjs} +2 -2
- package/dist/{chunk-RR5L4SRS.mjs.map → chunk-ZZ4YQ474.mjs.map} +1 -1
- package/dist/components/instances.d.ts +13 -31
- package/dist/components/instances.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +1012 -149
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/extract/contracts.d.ts +6 -6
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +6 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +4 -0
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +27 -9
- package/dist/index.mjs.map +1 -1
- package/dist/instance-buffer-cache.d.ts +7 -1
- package/dist/instance-buffer-cache.d.ts.map +1 -1
- package/dist/instances.d.ts +101 -0
- package/dist/instances.d.ts.map +1 -0
- package/dist/internal.mjs +5 -5
- package/dist/materials.d.ts +63 -2
- package/dist/materials.d.ts.map +1 -1
- package/dist/pipeline-builder.d.ts.map +1 -1
- package/dist/pipeline-spec-types.d.ts +2 -0
- package/dist/pipeline-spec-types.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +13 -2
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +23 -4
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +20 -2
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +3 -3
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/material-uniforms.d.ts +4 -0
- package/dist/record/material-uniforms.d.ts.map +1 -0
- package/dist/record/render-context.d.ts +4 -3
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +10 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +1 -1
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/render-contract.d.ts +4 -0
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts +6 -0
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +44 -9
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +4 -1
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene-types.d.ts +2 -0
- package/dist/scene/render-scene-types.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +12 -1
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/systems/skin-palette-allocator.d.ts +6 -0
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- package/package.json +20 -20
- package/src/__tests__/fixtures/ai-weapon-spirit/README.md +20 -0
- package/src/__tests__/fixtures/ai-weapon-spirit/low-poly-toon.wgsl +178 -0
- package/src/__tests__/fixtures/ai-weapon-spirit/material.json +97 -0
- package/src/__tests__/fixtures/ai-weapon-spirit/painterly-surface.wgsl +55 -0
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +3 -3
- package/src/__tests__/gpu-driven-view.browser.test.ts +11 -3
- package/src/__tests__/gpu-scene.unit.test.ts +38 -2
- package/src/__tests__/instances-store.unit.test.ts +46 -0
- package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
- package/src/__tests__/material-contract-inventory.unit.test.ts +48 -13
- package/src/__tests__/{material-static-dynamic-route.unit.test.ts → material-layer-projection.unit.test.ts} +30 -47
- package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
- package/src/__tests__/material-program-dispatch.unit.test.ts +48 -0
- package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
- package/src/__tests__/material-surface-authoring.test-d.ts +62 -0
- package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
- package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +0 -4
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +44 -2
- package/src/__tests__/skin-motion-regression.unit.test.ts +33 -0
- package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
- package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
- package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
- package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
- package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
- package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
- package/src/assembly/factory.ts +2 -3
- package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
- package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
- package/src/assembly/material/assembly.ts +8 -40
- package/src/assembly/material/standard-layer-projection.ts +3 -1
- package/src/assembly/material/surface-projection.ts +91 -0
- package/src/assembly/material-shader-policy.ts +88 -10
- package/src/assembly/recovery/renderer-recover.ts +1 -1
- package/src/assembly/webgpu-ready.ts +3 -16
- package/src/assembly/webgpu-renderer.ts +106 -166
- package/src/assembly/webgpu-vertex-layouts.ts +132 -0
- package/src/assets/asset-decoders.ts +27 -4
- package/src/components/instances.ts +14 -197
- package/src/extract/contracts.ts +6 -6
- package/src/gpu-driven/production-raster.ts +34 -1
- package/src/gpu-scene.ts +84 -29
- package/src/index.ts +1 -0
- package/src/instance-buffer-cache.ts +14 -1
- package/src/instances.ts +288 -0
- package/src/materials.ts +109 -14
- package/src/pipeline-builder.ts +14 -10
- package/src/pipeline-spec-types.ts +2 -0
- package/src/pipeline-spec.ts +13 -17
- package/src/record/frame-snapshot.ts +19 -2
- package/src/record/frame.ts +38 -3
- package/src/record/main-pass-geometry.ts +837 -337
- package/src/record/main-pass-material.ts +87 -2
- package/src/record/main-pass-sprite-draws.ts +66 -169
- package/src/record/main-pass.ts +24 -246
- package/src/record/material-uniforms.ts +186 -0
- package/src/record/render-context.ts +8 -0
- package/src/record/shadow-pass.ts +120 -115
- package/src/recovery/render-system-candidate.ts +46 -5
- package/src/render-contract.ts +4 -0
- package/src/render-system-extract-tail.ts +153 -57
- package/src/render-system-extract.ts +110 -73
- package/src/render-system.ts +59 -5
- package/src/scene/render-scene-types.ts +2 -0
- package/src/scene/render-scene.ts +579 -36
- package/src/systems/skin-palette-allocator.ts +129 -7
- package/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
- package/dist/assembly/material/pipeline-projection.d.ts +0 -30
- package/dist/assembly/material/pipeline-projection.d.ts.map +0 -1
- package/dist/chunk-KZFTK6DO.mjs.map +0 -1
- package/dist/chunk-Q752SHI5.mjs.map +0 -1
- package/dist/chunk-YKB5DQV7.mjs.map +0 -1
- package/dist/chunk-YRUYGA4A.mjs.map +0 -1
- package/dist/chunk-Z3YYH2MB.mjs.map +0 -1
- package/src/assembly/material/pipeline-projection.ts +0 -63
|
@@ -45,6 +45,39 @@ describe('custom skinned mesh motion consumer contract', () => {
|
|
|
45
45
|
expect(writes[1]?.data[12]).toBe(0.5);
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
it('keeps stable uniform palette slices isolated across frame-pool resets', () => {
|
|
49
|
+
const device = {
|
|
50
|
+
limits: { maxUniformBufferBindingSize: 65536 },
|
|
51
|
+
createBuffer: () => ok({} as Buffer),
|
|
52
|
+
destroyBuffer: () => undefined,
|
|
53
|
+
queue: { writeBuffer: () => ok(undefined) },
|
|
54
|
+
} as unknown as RhiDevice;
|
|
55
|
+
const allocator = createSkinPaletteAllocator(device, 65536, false);
|
|
56
|
+
const first = allocator.allocateSliceFor('world:1', 1);
|
|
57
|
+
allocator.resetForFrame();
|
|
58
|
+
const second = allocator.allocateSliceFor('world:2', 1);
|
|
59
|
+
|
|
60
|
+
expect(second.buffer).not.toBe(first.buffer);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('does not reuse a split storage range beyond its binding window', () => {
|
|
64
|
+
let bufferId = 0;
|
|
65
|
+
const device = {
|
|
66
|
+
limits: { maxStorageBufferBindingSize: 65536 },
|
|
67
|
+
createBuffer: () => ok({ id: bufferId++ } as unknown as Buffer),
|
|
68
|
+
queue: { writeBuffer: () => ok(undefined) },
|
|
69
|
+
} as unknown as RhiDevice;
|
|
70
|
+
const allocator = createSkinPaletteAllocator(device, 65536, true);
|
|
71
|
+
|
|
72
|
+
const full = allocator.allocateSliceFor('world:full', 255);
|
|
73
|
+
allocator.releaseSliceFor('world:full');
|
|
74
|
+
const firstSmall = allocator.allocateSliceFor('world:first', 1);
|
|
75
|
+
const secondSmall = allocator.allocateSliceFor('world:second', 1);
|
|
76
|
+
|
|
77
|
+
expect(firstSmall.buffer).toBe(full.buffer);
|
|
78
|
+
expect(secondSmall.buffer).not.toBe(full.buffer);
|
|
79
|
+
});
|
|
80
|
+
|
|
48
81
|
it('prepares both URP and HDRP pbr-skin consumers with the skin-cluster contract', () => {
|
|
49
82
|
const variants = selectSkinPrewarmVariants(
|
|
50
83
|
{
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
SHADOW_CASTER_SHADER_ID,
|
|
10
10
|
shadowCasterVariantSet,
|
|
11
11
|
} from '../pbr-pipeline.js';
|
|
12
|
+
import { shadowShaderMap } from '../record/shadow-pass.js';
|
|
12
13
|
|
|
13
14
|
describe('skinned shadow caster', () => {
|
|
14
15
|
it('derives closed capability variants without changing the shader identity', () => {
|
|
@@ -52,8 +53,7 @@ describe('skinned shadow caster', () => {
|
|
|
52
53
|
expect(shader).toContain('let skinMatrix = palette[');
|
|
53
54
|
expect(record).toContain('entry.source.skin.byteOffset');
|
|
54
55
|
expect(record).toContain("'shadow-pbr-skin-mesh'");
|
|
55
|
-
expect(builder).toContain(
|
|
56
|
-
expect(builder).toContain('? [...ctx.vertexBuffers]');
|
|
56
|
+
expect(builder).toContain('buffers: [...ctx.vertexBuffers]');
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
it('keeps mixed static and skinned shadow draws on matching variants and bindings', () => {
|
|
@@ -129,4 +129,32 @@ describe('skinned shadow caster', () => {
|
|
|
129
129
|
);
|
|
130
130
|
expect(recordSource).toContain('binding: 12');
|
|
131
131
|
});
|
|
132
|
+
|
|
133
|
+
it('keeps each submesh material associated with its selected shadow program and entries', () => {
|
|
134
|
+
const context = {
|
|
135
|
+
dispatch: [
|
|
136
|
+
{
|
|
137
|
+
renderableIndex: 4,
|
|
138
|
+
materialHandle: 42,
|
|
139
|
+
materialShaderId: 'forgeax::default-standard-pbr',
|
|
140
|
+
tags: { LightMode: 'ShadowCaster' },
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
renderableIndex: 4,
|
|
144
|
+
materialHandle: 43,
|
|
145
|
+
materialShaderId: 'custom::cutout',
|
|
146
|
+
vertexEntry: 'vs_displaced',
|
|
147
|
+
fragmentEntry: 'fs_cutout',
|
|
148
|
+
tags: { LightMode: 'ShadowCaster' },
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
} as never;
|
|
152
|
+
const bindings = shadowShaderMap(context);
|
|
153
|
+
expect(bindings.get(4)?.get(42)?.materialShaderId).toBe(SHADOW_CASTER_SHADER_ID);
|
|
154
|
+
expect(bindings.get(4)?.get(43)).toMatchObject({
|
|
155
|
+
materialShaderId: 'custom::cutout',
|
|
156
|
+
vertexEntry: 'vs_displaced',
|
|
157
|
+
fragmentEntry: 'fs_cutout',
|
|
158
|
+
});
|
|
159
|
+
});
|
|
132
160
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import {
|
|
5
|
+
projectStandardSurfacePasses,
|
|
6
|
+
type StandardSurfaceProjectionOptions,
|
|
7
|
+
} from '../assembly/material/surface-projection';
|
|
8
|
+
import { GPU_DRIVEN_RIGID_UNLIT_WGSL } from '../gpu-driven/production-raster';
|
|
9
|
+
|
|
10
|
+
function shaderSource(name: string): string {
|
|
11
|
+
return readFileSync(
|
|
12
|
+
fileURLToPath(new URL(`../../../shader/src/${name}`, import.meta.url)),
|
|
13
|
+
'utf8',
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const standard: StandardSurfaceProjectionOptions = {
|
|
18
|
+
surfaceModule: 'game_3d::rusted_iron_surface',
|
|
19
|
+
values: {},
|
|
20
|
+
geometryVariant: 'rigid',
|
|
21
|
+
lightingLane: 'direct',
|
|
22
|
+
alphaClip: true,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe('Surface adjacent regressions', () => {
|
|
26
|
+
it('retains the default Standard pass family and provenance', () => {
|
|
27
|
+
const passes = projectStandardSurfacePasses(standard);
|
|
28
|
+
expect(passes.map((pass) => pass.name)).toEqual(['forward', 'deferred', 'shadow-caster']);
|
|
29
|
+
expect(
|
|
30
|
+
passes.every((pass) => pass.program.moduleSlots?.surface === standard.surfaceModule),
|
|
31
|
+
).toBe(true);
|
|
32
|
+
expect(
|
|
33
|
+
passes.every(
|
|
34
|
+
(pass) =>
|
|
35
|
+
(pass.renderState?.tags as Readonly<Record<string, string>> | undefined)?.SurfaceKind ===
|
|
36
|
+
'standard',
|
|
37
|
+
),
|
|
38
|
+
).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('keeps explicit full-custom pass ownership closed', () => {
|
|
42
|
+
const passes = projectStandardSurfacePasses({
|
|
43
|
+
...standard,
|
|
44
|
+
surfaceKind: 'full-custom',
|
|
45
|
+
declaredPasses: ['forward'],
|
|
46
|
+
});
|
|
47
|
+
expect(passes.map((pass) => pass.name)).toEqual(['forward']);
|
|
48
|
+
expect(
|
|
49
|
+
(passes[0]?.renderState?.tags as Readonly<Record<string, string>> | undefined)?.SurfaceKind,
|
|
50
|
+
).toBe('full-custom');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('keeps skinning and GPU-driven siblings on their own shader owners', () => {
|
|
54
|
+
const skinned = shaderSource('default-standard-pbr-skin.wgsl');
|
|
55
|
+
expect(skinned).toContain('skinIndex');
|
|
56
|
+
expect(skinned).toContain('skinWeight');
|
|
57
|
+
expect(GPU_DRIVEN_RIGID_UNLIT_WGSL).toContain('@builtin(instance_index)');
|
|
58
|
+
expect(GPU_DRIVEN_RIGID_UNLIT_WGSL).not.toContain('surface_v1');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { projectStandardSurfacePasses } from '../assembly/material/surface-projection';
|
|
3
|
+
import { layerPlanFor, passFor, SURFACE_CASES } from './surface-standard-pipeline.fixture';
|
|
4
|
+
|
|
5
|
+
describe('Standard Surface render projection contract', () => {
|
|
6
|
+
it('projects every published cell to its declared pass and Surface module', () => {
|
|
7
|
+
for (const surfaceCase of SURFACE_CASES) {
|
|
8
|
+
const passes = projectStandardSurfacePasses({
|
|
9
|
+
surfaceModule: surfaceCase.surfaceModule,
|
|
10
|
+
values: surfaceCase.material.values ?? {},
|
|
11
|
+
geometryVariant: 'rigid',
|
|
12
|
+
lightingLane: 'direct',
|
|
13
|
+
alphaClip: true,
|
|
14
|
+
layerPlan: layerPlanFor(surfaceCase),
|
|
15
|
+
});
|
|
16
|
+
expect(passes.some((entry) => entry.name === passFor(surfaceCase))).toBe(true);
|
|
17
|
+
expect(passes[0]?.program.moduleSlots?.surface).toBe(surfaceCase.surfaceModule);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { projectStandardSurfacePasses } from '../assembly/material/surface-projection';
|
|
3
|
+
import { layerPlanFor, passFor, SURFACE_CASES } from './surface-standard-pipeline.fixture';
|
|
4
|
+
|
|
5
|
+
describe('Standard Surface render projection contract', () => {
|
|
6
|
+
it('keeps physical roots Forward-only while base roots remain Deferred', () => {
|
|
7
|
+
for (const surfaceCase of SURFACE_CASES) {
|
|
8
|
+
const passes = projectStandardSurfacePasses({
|
|
9
|
+
surfaceModule: surfaceCase.surfaceModule,
|
|
10
|
+
values: surfaceCase.material.values ?? {},
|
|
11
|
+
geometryVariant: 'skinned',
|
|
12
|
+
lightingLane: 'clustered',
|
|
13
|
+
alphaClip: true,
|
|
14
|
+
layerPlan: layerPlanFor(surfaceCase),
|
|
15
|
+
});
|
|
16
|
+
expect(passes.some((entry) => entry.name === passFor(surfaceCase))).toBe(true);
|
|
17
|
+
expect(passes[0]?.program.moduleSlots?.surface).toBe(surfaceCase.surfaceModule);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { AssetGuid, type PackageId } from '@forgeax/engine-pack/guid';
|
|
2
|
+
import type { MaterialAsset } from '@forgeax/engine-types';
|
|
3
|
+
import { deriveStandardLayerPlan } from '@forgeax/engine-types';
|
|
4
|
+
|
|
5
|
+
type SurfacePack = {
|
|
6
|
+
packageId: PackageId;
|
|
7
|
+
build: () =>
|
|
8
|
+
| { readonly ok: true; readonly value: Readonly<Record<string, MaterialAsset>> }
|
|
9
|
+
| Promise<{ readonly ok: true; readonly value: Readonly<Record<string, MaterialAsset>> }>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type ViteImportMeta = ImportMeta & {
|
|
13
|
+
glob<T>(
|
|
14
|
+
patterns: readonly string[],
|
|
15
|
+
options: { readonly eager: true; readonly import: 'default' },
|
|
16
|
+
): Readonly<Record<string, T>>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const packModules = Object.values(
|
|
20
|
+
(import.meta as ViteImportMeta).glob<SurfacePack>(
|
|
21
|
+
[
|
|
22
|
+
'../../../../templates/game-3d/assets/materials.pack.ts',
|
|
23
|
+
'../../../../apps/preview/assets/surface-standard-evidence.pack.ts',
|
|
24
|
+
],
|
|
25
|
+
{
|
|
26
|
+
eager: true,
|
|
27
|
+
import: 'default',
|
|
28
|
+
},
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
if (packModules.length !== 2)
|
|
32
|
+
throw new Error('surface-evidence-fixture: required pack module missing');
|
|
33
|
+
|
|
34
|
+
const builtPacks = packModules.map((pack) => {
|
|
35
|
+
const built = pack.build();
|
|
36
|
+
if (built instanceof Promise || !built.ok) {
|
|
37
|
+
throw new Error('surface-evidence-fixture: materials pack did not produce outputs');
|
|
38
|
+
}
|
|
39
|
+
return { packageId: pack.packageId, values: built.value };
|
|
40
|
+
});
|
|
41
|
+
const materials = Object.assign({}, ...builtPacks.map((pack) => pack.values)) as Record<
|
|
42
|
+
string,
|
|
43
|
+
MaterialAsset | undefined
|
|
44
|
+
>;
|
|
45
|
+
|
|
46
|
+
function guidForSourceKey(sourceKey: string): AssetGuid {
|
|
47
|
+
const owner = builtPacks.find((pack) => Object.hasOwn(pack.values, sourceKey));
|
|
48
|
+
if (owner === undefined) {
|
|
49
|
+
throw new Error(`surface-evidence-fixture: source key '${sourceKey}' is not published`);
|
|
50
|
+
}
|
|
51
|
+
return AssetGuid.derive(owner.packageId, sourceKey);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const rustedIron = materials['material/rusted-iron'];
|
|
55
|
+
|
|
56
|
+
const defaultBase = materials['material/ground'];
|
|
57
|
+
const defaultPhysical = materials['material/painted'];
|
|
58
|
+
const customPhysical = materials['material/rusted-iron-custom-physical'];
|
|
59
|
+
if (
|
|
60
|
+
rustedIron === undefined ||
|
|
61
|
+
defaultBase === undefined ||
|
|
62
|
+
defaultPhysical === undefined ||
|
|
63
|
+
customPhysical === undefined
|
|
64
|
+
)
|
|
65
|
+
throw new Error('surface-evidence-fixture: required material row missing');
|
|
66
|
+
|
|
67
|
+
export const SURFACE_CLOSURE = 'forgeax_material::surface_v1';
|
|
68
|
+
export const SURFACE_SOURCE_CLOSURE = [
|
|
69
|
+
'templates/game-3d/assets/materials.pack.ts',
|
|
70
|
+
'templates/game-3d/assets/shaders/rusted-iron.wgsl',
|
|
71
|
+
] as const;
|
|
72
|
+
export const SURFACE_SOURCE_PATH = 'templates/game-3d/assets/shaders/rusted-iron.wgsl';
|
|
73
|
+
export const SURFACE_MATERIAL_SOURCE_KEY = 'material/rusted-iron';
|
|
74
|
+
export const EVIDENCE_SOURCE_SHA =
|
|
75
|
+
(import.meta as ImportMeta & { env?: Record<string, string | undefined> }).env
|
|
76
|
+
?.VITE_FORGEAX_EVIDENCE_SOURCE_SHA ?? 'unavailable';
|
|
77
|
+
|
|
78
|
+
export const SURFACE_CASES = [
|
|
79
|
+
{
|
|
80
|
+
id: 'default-base',
|
|
81
|
+
material: defaultBase,
|
|
82
|
+
materialGuid: AssetGuid.format(guidForSourceKey('material/ground')),
|
|
83
|
+
surfaceModule: defaultBase.passes?.[0]?.program.moduleSlots?.surface ?? '',
|
|
84
|
+
physical: false,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'custom-base',
|
|
88
|
+
material: rustedIron,
|
|
89
|
+
materialGuid: AssetGuid.format(guidForSourceKey('material/rusted-iron')),
|
|
90
|
+
surfaceModule: rustedIron.passes?.[0]?.program.moduleSlots?.surface ?? '',
|
|
91
|
+
physical: false,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'default-physical',
|
|
95
|
+
material: defaultPhysical,
|
|
96
|
+
materialGuid: '019fb7ce-3200-7000-8000-000000000002',
|
|
97
|
+
surfaceModule: defaultPhysical.passes?.[0]?.program.moduleSlots?.surface ?? '',
|
|
98
|
+
physical: true,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'custom-physical',
|
|
102
|
+
material: customPhysical,
|
|
103
|
+
materialGuid: '019fb7ce-3f00-7000-8000-000000000001',
|
|
104
|
+
surfaceModule: customPhysical.passes?.[0]?.program.moduleSlots?.surface ?? '',
|
|
105
|
+
physical: true,
|
|
106
|
+
},
|
|
107
|
+
] as const;
|
|
108
|
+
|
|
109
|
+
export function layerPlanFor(surfaceCase: (typeof SURFACE_CASES)[number]) {
|
|
110
|
+
return deriveStandardLayerPlan(
|
|
111
|
+
surfaceCase.physical ? (surfaceCase.material.parameters ?? []) : [],
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function baseColorFor(
|
|
116
|
+
surfaceCase: (typeof SURFACE_CASES)[number],
|
|
117
|
+
): readonly [number, number, number] {
|
|
118
|
+
const values = surfaceCase.material.values ?? {};
|
|
119
|
+
const value = values.baseColor ?? values.ironColor;
|
|
120
|
+
if (!Array.isArray(value) || value.length < 3) {
|
|
121
|
+
throw new Error(`surface-evidence-fixture: missing base color for ${surfaceCase.id}`);
|
|
122
|
+
}
|
|
123
|
+
return [Number(value[0]), Number(value[1]), Number(value[2])];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function passFor(surfaceCase: (typeof SURFACE_CASES)[number]): 'forward' | 'deferred' {
|
|
127
|
+
return layerPlanFor(surfaceCase).mode === 'physical' ? 'forward' : 'deferred';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function publicationGenerationFor(_surfaceCase: (typeof SURFACE_CASES)[number]): number {
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
projectStandardSurfacePasses,
|
|
4
|
+
type StandardSurfaceProjectionOptions,
|
|
5
|
+
} from '../assembly/material/surface-projection';
|
|
6
|
+
|
|
7
|
+
type FullCustomFixture = StandardSurfaceProjectionOptions & {
|
|
8
|
+
readonly surfaceKind: 'full-custom';
|
|
9
|
+
readonly declaredPasses: readonly ('forward' | 'deferred' | 'shadow-caster')[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe('full-custom Surface provenance', () => {
|
|
13
|
+
it('does not synthesize an undeclared Deferred pass or fallback identity', () => {
|
|
14
|
+
const options: FullCustomFixture = {
|
|
15
|
+
surfaceModule: 'game_3d::full_custom_surface',
|
|
16
|
+
values: { tint: [1, 0.1, 0.05, 1] },
|
|
17
|
+
surfaceKind: 'full-custom',
|
|
18
|
+
declaredPasses: ['forward', 'shadow-caster'],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const passes = projectStandardSurfacePasses(options);
|
|
22
|
+
|
|
23
|
+
expect(passes.map((pass) => pass.name)).toEqual(['forward', 'shadow-caster']);
|
|
24
|
+
expect(passes.map((pass) => pass.program.module)).toEqual([
|
|
25
|
+
'forgeax_material::standard',
|
|
26
|
+
'forgeax::default-shadow-caster',
|
|
27
|
+
]);
|
|
28
|
+
for (const pass of passes) {
|
|
29
|
+
expect(pass.program.moduleSlots?.surface).toBe(options.surfaceModule);
|
|
30
|
+
expect((pass.renderState?.tags as Record<string, string>).SurfaceKind).toBe('full-custom');
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
package/src/assembly/factory.ts
CHANGED
|
@@ -64,11 +64,11 @@ export {
|
|
|
64
64
|
exposeRenderer,
|
|
65
65
|
isSharedMaterialUserRegionCompatible,
|
|
66
66
|
normalizeMaterialShaderVariantSet,
|
|
67
|
-
|
|
67
|
+
resolveMaterialShaderBackendArtifactKey,
|
|
68
68
|
resolveMaterialShaderBindingContract,
|
|
69
|
+
resolveMaterialShaderUvSetCount,
|
|
69
70
|
resolveMaterialShaderVariantSet,
|
|
70
71
|
resolveMaterialShaderVertexInputContract,
|
|
71
|
-
routeMaterialPipeline,
|
|
72
72
|
selectHdrpPbrPrewarmVariants,
|
|
73
73
|
selectNoColorPbrVariant,
|
|
74
74
|
selectPipelineLayoutForVariant,
|
|
@@ -76,7 +76,6 @@ export {
|
|
|
76
76
|
selectSkinPrewarmVariants,
|
|
77
77
|
selectStandardPbrTransmissionPrewarmVariants,
|
|
78
78
|
shouldDeferMissingPreparedMaterialShader,
|
|
79
|
-
updateMaterialRuntimeValues,
|
|
80
79
|
} from './webgpu-renderer';
|
|
81
80
|
|
|
82
81
|
const GPU_ENVIRONMENT_ERROR_CODES = new Set([
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { deriveStandardLayerPlan } from '@forgeax/engine-types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { projectStandardSurfacePasses } from '../surface-projection.js';
|
|
4
|
+
|
|
5
|
+
describe('StandardLayerPlan pass projection', () => {
|
|
6
|
+
it('keeps a physical Standard material on Forward and shadow passes', () => {
|
|
7
|
+
const plan = deriveStandardLayerPlan([
|
|
8
|
+
{ name: 'clearcoat', type: 'f32' },
|
|
9
|
+
{ name: 'clearcoatRoughness', type: 'f32' },
|
|
10
|
+
]);
|
|
11
|
+
const passes = projectStandardSurfacePasses({
|
|
12
|
+
surfaceModule: 'game::surface',
|
|
13
|
+
values: {},
|
|
14
|
+
layerPlan: plan,
|
|
15
|
+
});
|
|
16
|
+
expect(passes.map((pass) => pass.name)).toEqual(['forward', 'shadow-caster']);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { deriveStandardLayerPlan } from '@forgeax/engine-types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import {
|
|
4
|
+
projectStandardSurfacePasses,
|
|
5
|
+
type StandardSurfaceProjectionOptions,
|
|
6
|
+
} from '../surface-projection';
|
|
7
|
+
|
|
8
|
+
type SurfaceFixture = StandardSurfaceProjectionOptions & {
|
|
9
|
+
readonly geometryVariant: 'rigid' | 'skinned' | 'instanced';
|
|
10
|
+
readonly lightingLane: 'direct' | 'clustered';
|
|
11
|
+
readonly alphaClip: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const fixture = (geometryVariant: SurfaceFixture['geometryVariant']): SurfaceFixture => ({
|
|
15
|
+
surfaceModule: 'game_3d::rusted_iron_surface',
|
|
16
|
+
values: { ironColor: [0.42, 0.47, 0.5, 1] },
|
|
17
|
+
geometryVariant,
|
|
18
|
+
lightingLane: geometryVariant === 'instanced' ? 'clustered' : 'direct',
|
|
19
|
+
alphaClip: geometryVariant === 'skinned',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function tagsFor(pass: ReturnType<typeof projectStandardSurfacePasses>[number]) {
|
|
23
|
+
return pass.renderState?.tags as Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('Standard Surface pass projection', () => {
|
|
27
|
+
it.each([
|
|
28
|
+
'rigid',
|
|
29
|
+
'skinned',
|
|
30
|
+
'instanced',
|
|
31
|
+
] as const)('projects one %s Surface closure into every Standard pass', (geometryVariant) => {
|
|
32
|
+
const options = fixture(geometryVariant);
|
|
33
|
+
const passes = projectStandardSurfacePasses(options);
|
|
34
|
+
|
|
35
|
+
expect(passes.map((pass) => pass.name)).toEqual(['forward', 'deferred', 'shadow-caster']);
|
|
36
|
+
for (const pass of passes) {
|
|
37
|
+
expect(pass.program.moduleSlots?.surface).toBe(options.surfaceModule);
|
|
38
|
+
expect(tagsFor(pass)).toMatchObject({
|
|
39
|
+
SurfaceModule: options.surfaceModule,
|
|
40
|
+
GeometryVariant: geometryVariant,
|
|
41
|
+
LightingLane: options.lightingLane,
|
|
42
|
+
AlphaClip: options.alphaClip ? 'enabled' : 'disabled',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('keeps base Standard on Deferred while physical Standard stays Forward-only', () => {
|
|
48
|
+
const base = projectStandardSurfacePasses({
|
|
49
|
+
...fixture('rigid'),
|
|
50
|
+
layerPlan: deriveStandardLayerPlan([]),
|
|
51
|
+
});
|
|
52
|
+
const physical = projectStandardSurfacePasses({
|
|
53
|
+
...fixture('rigid'),
|
|
54
|
+
layerPlan: deriveStandardLayerPlan([
|
|
55
|
+
{ name: 'clearcoat', type: 'f32' },
|
|
56
|
+
{ name: 'clearcoatRoughness', type: 'f32' },
|
|
57
|
+
]),
|
|
58
|
+
});
|
|
59
|
+
expect(base.map((pass) => pass.name)).toEqual(['forward', 'deferred', 'shadow-caster']);
|
|
60
|
+
expect(physical.map((pass) => pass.name)).toEqual(['forward', 'shadow-caster']);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -1,46 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type MaterialRenderProjection as AssetMaterialRenderProjection,
|
|
3
|
+
projectMaterialRecord,
|
|
4
|
+
} from '@forgeax/engine-assets-runtime';
|
|
1
5
|
import {
|
|
2
6
|
type CookedMaterialRecord,
|
|
3
7
|
materialLayerPlanIdentity,
|
|
4
8
|
} from '@forgeax/engine-pack/material-cook';
|
|
5
|
-
import type {
|
|
9
|
+
import type { StandardLayerPlan } from '@forgeax/engine-types';
|
|
6
10
|
import { projectStandardLayerPlan } from './standard-layer-projection.js';
|
|
7
11
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
readonly module: string;
|
|
11
|
-
readonly vertexEntry?: string;
|
|
12
|
-
readonly fragmentEntry?: string;
|
|
13
|
-
readonly moduleSlots?: Readonly<Record<string, string>>;
|
|
14
|
-
readonly renderState?: Readonly<Record<string, unknown>>;
|
|
15
|
-
readonly artifactHash: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface MaterialRenderProjection {
|
|
19
|
-
readonly materialGuid: string;
|
|
20
|
-
readonly specializationKey: string;
|
|
21
|
-
readonly artifactHash: string;
|
|
22
|
-
readonly passes: readonly MaterialRenderPassProjection[];
|
|
23
|
-
readonly runtimeValues: Readonly<Record<string, MaterialValue | null>>;
|
|
24
|
-
readonly staticSelection: readonly string[];
|
|
12
|
+
export type { MaterialRenderPassProjection } from '@forgeax/engine-assets-runtime';
|
|
13
|
+
export interface MaterialRenderProjection extends AssetMaterialRenderProjection {
|
|
25
14
|
readonly layerPlan: StandardLayerPlan;
|
|
26
15
|
}
|
|
27
16
|
|
|
28
|
-
function projectPass(
|
|
29
|
-
record: CookedMaterialRecord,
|
|
30
|
-
pass: CookedMaterialRecord['resolved']['passes'][number],
|
|
31
|
-
): MaterialRenderPassProjection {
|
|
32
|
-
const program = pass.program;
|
|
33
|
-
return {
|
|
34
|
-
name: pass.name,
|
|
35
|
-
module: program.module,
|
|
36
|
-
...(program.vertexEntry === undefined ? {} : { vertexEntry: program.vertexEntry }),
|
|
37
|
-
...(program.fragmentEntry === undefined ? {} : { fragmentEntry: program.fragmentEntry }),
|
|
38
|
-
...(program.moduleSlots === undefined ? {} : { moduleSlots: program.moduleSlots }),
|
|
39
|
-
...(pass.renderState === undefined ? {} : { renderState: pass.renderState }),
|
|
40
|
-
artifactHash: record.artifact.digest,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
17
|
export function assembleMaterialProjection(record: CookedMaterialRecord): MaterialRenderProjection {
|
|
45
18
|
const layerPlan = projectStandardLayerPlan(record.resolved.parameters, record.resolved.passes);
|
|
46
19
|
const expectedLayerPlanIdentity = materialLayerPlanIdentity(record);
|
|
@@ -53,12 +26,7 @@ export function assembleMaterialProjection(record: CookedMaterialRecord): Materi
|
|
|
53
26
|
);
|
|
54
27
|
}
|
|
55
28
|
return {
|
|
56
|
-
|
|
57
|
-
specializationKey: record.specializationKey ?? record.receipt.identity.programIdentity,
|
|
58
|
-
artifactHash: record.artifact.digest,
|
|
59
|
-
passes: record.resolved.passes.map((pass) => projectPass(record, pass)),
|
|
60
|
-
runtimeValues: record.resolved.values,
|
|
61
|
-
staticSelection: [],
|
|
29
|
+
...projectMaterialRecord(record),
|
|
62
30
|
layerPlan,
|
|
63
31
|
};
|
|
64
32
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isStandardRootModule } from '@forgeax/engine-pack';
|
|
1
2
|
import type { MaterialParameter, MaterialPass, StandardLayerPlan } from '@forgeax/engine-types';
|
|
2
3
|
import { deriveStandardLayerPlan } from '@forgeax/engine-types';
|
|
3
4
|
|
|
@@ -12,5 +13,6 @@ export function projectStandardLayerPlan(
|
|
|
12
13
|
parameters: readonly MaterialParameter[],
|
|
13
14
|
passes: readonly MaterialPass[],
|
|
14
15
|
): StandardLayerPlan {
|
|
15
|
-
|
|
16
|
+
const standard = passes.some((pass) => isStandardRootModule(pass.program.module));
|
|
17
|
+
return deriveStandardLayerPlan(standard ? parameters : [], standard ? passes : undefined);
|
|
16
18
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { DEFAULT_STANDARD_SURFACE_MODULE } from '@forgeax/engine-shader';
|
|
2
|
+
import type {
|
|
3
|
+
MaterialPass,
|
|
4
|
+
MaterialRenderState,
|
|
5
|
+
MaterialValue,
|
|
6
|
+
StandardLayerPlan,
|
|
7
|
+
} from '@forgeax/engine-types';
|
|
8
|
+
|
|
9
|
+
export const STANDARD_MATERIAL_MODULE = 'forgeax_material::standard';
|
|
10
|
+
export { DEFAULT_STANDARD_SURFACE_MODULE };
|
|
11
|
+
|
|
12
|
+
export type StandardSurfacePassName = 'forward' | 'deferred' | 'shadow-caster';
|
|
13
|
+
export type StandardSurfaceGeometryVariant = 'rigid' | 'skinned' | 'instanced';
|
|
14
|
+
export type StandardSurfaceLightingLane = 'direct' | 'clustered';
|
|
15
|
+
export type StandardSurfaceKind = 'standard' | 'full-custom';
|
|
16
|
+
|
|
17
|
+
export interface StandardSurfaceProjectionOptions {
|
|
18
|
+
readonly surfaceModule: string;
|
|
19
|
+
readonly values: Readonly<Record<string, MaterialValue | null>>;
|
|
20
|
+
readonly renderState?: MaterialRenderState;
|
|
21
|
+
readonly castShadow?: boolean;
|
|
22
|
+
readonly queue?: number;
|
|
23
|
+
readonly geometryVariant?: StandardSurfaceGeometryVariant;
|
|
24
|
+
readonly lightingLane?: StandardSurfaceLightingLane;
|
|
25
|
+
readonly alphaClip?: boolean;
|
|
26
|
+
readonly surfaceKind?: StandardSurfaceKind;
|
|
27
|
+
readonly declaredPasses?: readonly StandardSurfacePassName[];
|
|
28
|
+
readonly layerPlan?: StandardLayerPlan;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function pass(
|
|
32
|
+
name: StandardSurfacePassName,
|
|
33
|
+
options: StandardSurfaceProjectionOptions,
|
|
34
|
+
fragmentEntry: string,
|
|
35
|
+
): MaterialPass {
|
|
36
|
+
const authoredState = options.renderState ?? {};
|
|
37
|
+
const authoredRecord = authoredState as Readonly<Record<string, unknown>>;
|
|
38
|
+
const tags = (authoredRecord.tags ?? {}) as Readonly<Record<string, string>>;
|
|
39
|
+
const lightMode =
|
|
40
|
+
name === 'shadow-caster' ? 'ShadowCaster' : name === 'forward' ? 'Forward' : 'Deferred';
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
program: {
|
|
44
|
+
module:
|
|
45
|
+
name === 'shadow-caster' ? 'forgeax::default-shadow-caster' : STANDARD_MATERIAL_MODULE,
|
|
46
|
+
fragmentEntry,
|
|
47
|
+
moduleSlots: { surface: options.surfaceModule },
|
|
48
|
+
},
|
|
49
|
+
renderState: {
|
|
50
|
+
...authoredState,
|
|
51
|
+
tags:
|
|
52
|
+
options.surfaceModule === DEFAULT_STANDARD_SURFACE_MODULE
|
|
53
|
+
? { LightMode: lightMode, ...tags }
|
|
54
|
+
: {
|
|
55
|
+
...tags,
|
|
56
|
+
LightMode: lightMode,
|
|
57
|
+
SurfaceModule: options.surfaceModule,
|
|
58
|
+
SurfaceKind: options.surfaceKind ?? 'standard',
|
|
59
|
+
GeometryVariant: options.geometryVariant ?? 'rigid',
|
|
60
|
+
LightingLane: options.lightingLane ?? 'direct',
|
|
61
|
+
AlphaClip: options.alphaClip === true ? 'enabled' : 'disabled',
|
|
62
|
+
},
|
|
63
|
+
...(options.queue === undefined ? {} : { queue: options.queue }),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Project one custom or default Surface into the existing Standard pass family. */
|
|
69
|
+
export function projectStandardSurfacePasses(
|
|
70
|
+
options: StandardSurfaceProjectionOptions,
|
|
71
|
+
): [MaterialPass, ...MaterialPass[]] {
|
|
72
|
+
const authoredPasses = options.declaredPasses;
|
|
73
|
+
const passNames: StandardSurfacePassName[] =
|
|
74
|
+
options.surfaceKind === 'full-custom' && authoredPasses !== undefined
|
|
75
|
+
? [...authoredPasses]
|
|
76
|
+
: (['forward'] as StandardSurfacePassName[]);
|
|
77
|
+
if (authoredPasses === undefined || options.surfaceKind !== 'full-custom') {
|
|
78
|
+
const blended = options.renderState?.blend !== undefined;
|
|
79
|
+
if (!blended && options.layerPlan?.mode !== 'physical') passNames.push('deferred');
|
|
80
|
+
if (options.castShadow !== false) passNames.push('shadow-caster');
|
|
81
|
+
}
|
|
82
|
+
const entries = passNames.map((name) =>
|
|
83
|
+
pass(
|
|
84
|
+
name,
|
|
85
|
+
options,
|
|
86
|
+
name === 'forward' ? 'fs_main' : name === 'deferred' ? 'fs_gbuffer' : 'fs_shadow',
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
const passes = entries as [MaterialPass, ...MaterialPass[]];
|
|
90
|
+
return passes;
|
|
91
|
+
}
|