@forgeax/engine-shader 0.1.2
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/LICENSE +202 -0
- package/README.md +99 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/ShaderRegistry.d.ts +208 -0
- package/dist/ShaderRegistry.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-background.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-background.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts.map +1 -0
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts +2 -0
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts.map +1 -0
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts +2 -0
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts.map +1 -0
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts +2 -0
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts.map +1 -0
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts +2 -0
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts.map +1 -0
- package/dist/__tests__/fog-oracle.unit.test.d.ts +2 -0
- package/dist/__tests__/fog-oracle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts +2 -0
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts +2 -0
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts.map +1 -0
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ibl-sampling.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl-sampling.unit.test.d.ts.map +1 -0
- package/dist/__tests__/lighting-punctual.unit.test.d.ts +2 -0
- package/dist/__tests__/lighting-punctual.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts +2 -0
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-builtins.unit.test.d.ts +2 -0
- package/dist/__tests__/material-builtins.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/material-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts +2 -0
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts.map +1 -0
- package/dist/__tests__/public-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/public-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-temporal.unit.test.d.ts +2 -0
- package/dist/__tests__/scene-temporal.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts +2 -0
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shader.unit.test.d.ts +2 -0
- package/dist/__tests__/shader.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-lit-shader.test.d.ts +2 -0
- package/dist/__tests__/sprite-lit-shader.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-variants.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-variants.unit.test.d.ts.map +1 -0
- package/dist/__tests__/taa-resolve.unit.test.d.ts +2 -0
- package/dist/__tests__/taa-resolve.unit.test.d.ts.map +1 -0
- package/dist/__tests__/transparent-pbr.unit.test.d.ts +2 -0
- package/dist/__tests__/transparent-pbr.unit.test.d.ts.map +1 -0
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts +2 -0
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts.map +1 -0
- package/dist/errors.d.ts +96 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +560 -0
- package/dist/index.mjs.map +1 -0
- package/dist/material/artifact-registry.d.ts +31 -0
- package/dist/material/artifact-registry.d.ts.map +1 -0
- package/dist/material/artifact-types.d.ts +13 -0
- package/dist/material/artifact-types.d.ts.map +1 -0
- package/dist/material-schemas.d.ts +8 -0
- package/dist/material-schemas.d.ts.map +1 -0
- package/dist/register-default-sprite-lit.d.ts +40 -0
- package/dist/register-default-sprite-lit.d.ts.map +1 -0
- package/dist/register-default-standard-pbr-skin.d.ts +34 -0
- package/dist/register-default-standard-pbr-skin.d.ts.map +1 -0
- package/dist/tonemap.d.ts +13 -0
- package/dist/tonemap.d.ts.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/ShaderRegistry.ts +519 -0
- package/src/__tests__/atmosphere-background.test.ts +33 -0
- package/src/__tests__/atmosphere-cubemap-orientation.test.ts +68 -0
- package/src/__tests__/atmosphere-preetham-numeric.test.ts +122 -0
- package/src/__tests__/atmosphere-preetham-red.test.ts +52 -0
- package/src/__tests__/atmosphere-preetham.test.ts +132 -0
- package/src/__tests__/atmosphere-three-r184-reference.test.ts +122 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +31 -0
- package/src/__tests__/builtin-texture-sampling-contract.test.ts +56 -0
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +19 -0
- package/src/__tests__/deferred-lighting-ssao.test.ts +114 -0
- package/src/__tests__/fog-oracle.unit.test.ts +132 -0
- package/src/__tests__/fog-orthographic-ray.unit.test.ts +62 -0
- package/src/__tests__/fog-producer-matrix.integration.test.ts +77 -0
- package/src/__tests__/hdrp-ssao-compile.test.ts +185 -0
- package/src/__tests__/ibl-irradiance.unit.test.ts +43 -0
- package/src/__tests__/ibl-sampling.unit.test.ts +40 -0
- package/src/__tests__/lighting-punctual.unit.test.ts +66 -0
- package/src/__tests__/manifest.fixture.json +22 -0
- package/src/__tests__/material-artifact-dedup.unit.test.ts +21 -0
- package/src/__tests__/material-builtins.unit.test.ts +111 -0
- package/src/__tests__/material-contract.unit.test.ts +79 -0
- package/src/__tests__/material-derived-builtins.integration.test.ts +30 -0
- package/src/__tests__/public-surface.unit.test.ts +9 -0
- package/src/__tests__/scene-temporal.unit.test.ts +32 -0
- package/src/__tests__/shader-registry-transaction.unit.test.ts +116 -0
- package/src/__tests__/shader.unit.test.ts +1909 -0
- package/src/__tests__/sprite-lit-shader.test.ts +240 -0
- package/src/__tests__/sprite-variants.unit.test.ts +209 -0
- package/src/__tests__/taa-resolve.unit.test.ts +32 -0
- package/src/__tests__/transparent-pbr.unit.test.ts +21 -0
- package/src/__tests__/vertex-color-variant.unit.test.ts +52 -0
- package/src/atmosphere-background.wgsl +80 -0
- package/src/atmosphere-cubemap.wgsl +72 -0
- package/src/atmosphere-ibl.wgsl +127 -0
- package/src/atmosphere-preetham.wgsl +214 -0
- package/src/bloom-blur.wgsl +89 -0
- package/src/bloom-bright.wgsl +50 -0
- package/src/bloom-composite.wgsl +54 -0
- package/src/brdf.wgsl +25 -0
- package/src/common.wgsl +523 -0
- package/src/default-standard-pbr-skin.wgsl +538 -0
- package/src/default-standard-pbr.wgsl +794 -0
- package/src/errors.ts +169 -0
- package/src/fog.wgsl +27 -0
- package/src/fxaa.wgsl +259 -0
- package/src/hdrp-cluster-forward.wgsl +186 -0
- package/src/hdrp-deferred-lighting.wgsl +69 -0
- package/src/hdrp-ssao.wgsl +174 -0
- package/src/ibl-brdf-lut.wgsl +92 -0
- package/src/ibl-equirect-to-cube.wgsl +56 -0
- package/src/ibl-irradiance.wgsl +89 -0
- package/src/ibl-prefilter.wgsl +101 -0
- package/src/ibl-sampling.wgsl +58 -0
- package/src/ibl-shared.wgsl +118 -0
- package/src/index.ts +166 -0
- package/src/lighting-directional.wgsl +471 -0
- package/src/lighting-punctual.wgsl +274 -0
- package/src/material/artifact-registry.ts +78 -0
- package/src/material/artifact-types.ts +26 -0
- package/src/material-schemas.ts +48 -0
- package/src/msdf-text.wgsl +208 -0
- package/src/pbr-temporal.wgsl +73 -0
- package/src/register-default-sprite-lit.ts +87 -0
- package/src/register-default-standard-pbr-skin.ts +74 -0
- package/src/shadow-pcf.wgsl +120 -0
- package/src/shadow_caster.wgsl +54 -0
- package/src/skybox.wgsl +75 -0
- package/src/sprite-lit.wgsl +307 -0
- package/src/sprite.wgsl +365 -0
- package/src/taa-resolve.wgsl +129 -0
- package/src/tbn.wgsl +54 -0
- package/src/tonemap.ts +13 -0
- package/src/tonemap.wgsl +177 -0
- package/src/types.ts +59 -0
- package/src/unlit.wgsl +186 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#define_import_path forgeax_pbr::temporal
|
|
2
|
+
|
|
3
|
+
#import forgeax_view::common::{sampleMaterialTexture, packSceneTemporal}
|
|
4
|
+
|
|
5
|
+
fn transformedPbrTemporalUv(
|
|
6
|
+
transform : vec4<f32>,
|
|
7
|
+
metadata : vec4<f32>,
|
|
8
|
+
uv0 : vec2<f32>,
|
|
9
|
+
uv1 : vec2<f32>,
|
|
10
|
+
uv2 : vec2<f32>,
|
|
11
|
+
uv3 : vec2<f32>,
|
|
12
|
+
uv4 : vec2<f32>,
|
|
13
|
+
uv5 : vec2<f32>,
|
|
14
|
+
uv6 : vec2<f32>,
|
|
15
|
+
uv7 : vec2<f32>,
|
|
16
|
+
) -> vec2<f32> {
|
|
17
|
+
var source = uv0;
|
|
18
|
+
if (metadata.x >= 1.0) { source = uv1; }
|
|
19
|
+
if (metadata.x >= 2.0) { source = uv2; }
|
|
20
|
+
if (metadata.x >= 3.0) { source = uv3; }
|
|
21
|
+
if (metadata.x >= 4.0) { source = uv4; }
|
|
22
|
+
if (metadata.x >= 5.0) { source = uv5; }
|
|
23
|
+
if (metadata.x >= 6.0) { source = uv6; }
|
|
24
|
+
if (metadata.x >= 7.0) { source = uv7; }
|
|
25
|
+
let scaled = source * transform.zw;
|
|
26
|
+
let angle = metadata.y;
|
|
27
|
+
let c = cos(angle);
|
|
28
|
+
let s = sin(angle);
|
|
29
|
+
return vec2<f32>(scaled.x * c - scaled.y * s, scaled.x * s + scaled.y * c) + transform.xy;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn projectPbrSceneTemporal(
|
|
33
|
+
baseColorAlpha : f32,
|
|
34
|
+
alphaCutoff : f32,
|
|
35
|
+
baseColorTexture : texture_2d<f32>,
|
|
36
|
+
baseColorSampler : sampler,
|
|
37
|
+
transform : vec4<f32>,
|
|
38
|
+
metadata : vec4<f32>,
|
|
39
|
+
currentClip : vec4<f32>,
|
|
40
|
+
previousClip : vec4<f32>,
|
|
41
|
+
reactive : f32,
|
|
42
|
+
uv0 : vec2<f32>,
|
|
43
|
+
uv1 : vec2<f32>,
|
|
44
|
+
uv2 : vec2<f32>,
|
|
45
|
+
uv3 : vec2<f32>,
|
|
46
|
+
uv4 : vec2<f32>,
|
|
47
|
+
uv5 : vec2<f32>,
|
|
48
|
+
uv6 : vec2<f32>,
|
|
49
|
+
uv7 : vec2<f32>,
|
|
50
|
+
) -> vec4<f32> {
|
|
51
|
+
let baseUv = transformedPbrTemporalUv(
|
|
52
|
+
transform,
|
|
53
|
+
metadata,
|
|
54
|
+
uv0,
|
|
55
|
+
uv1,
|
|
56
|
+
uv2,
|
|
57
|
+
uv3,
|
|
58
|
+
uv4,
|
|
59
|
+
uv5,
|
|
60
|
+
uv6,
|
|
61
|
+
uv7,
|
|
62
|
+
);
|
|
63
|
+
let baseSample = sampleMaterialTexture(
|
|
64
|
+
baseColorTexture,
|
|
65
|
+
baseColorSampler,
|
|
66
|
+
baseUv,
|
|
67
|
+
metadata.zw,
|
|
68
|
+
);
|
|
69
|
+
if (alphaCutoff > 0.0 && baseColorAlpha * baseSample.a <= alphaCutoff) {
|
|
70
|
+
discard;
|
|
71
|
+
}
|
|
72
|
+
return packSceneTemporal(currentClip, previousClip, reactive);
|
|
73
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// @forgeax/engine-shader - registerDefaultSpriteLit helper.
|
|
2
|
+
//
|
|
3
|
+
// feat-20260624-sprite-lit-shading-model-pure-2d-lighting M1' / w4
|
|
4
|
+
// (plan-strategy D-10 application registration path).
|
|
5
|
+
//
|
|
6
|
+
// Mirrors registerDefaultStandardPbrSkin (same package; identical
|
|
7
|
+
// registration shape). paramSchema mirrors sprite.material.json minus
|
|
8
|
+
// PBR-only fields; the BGL is derived from paramSchema via
|
|
9
|
+
// `derive(paramSchema).bglEntries` inside the runtime pipeline-builder.
|
|
10
|
+
//
|
|
11
|
+
// Anchors:
|
|
12
|
+
// - requirements section 2 #2 / #10 (sprite-lit.wgsl new + 4 BGL byte-identical)
|
|
13
|
+
// - plan-strategy D-3 (sprite-lit single Half-Lambert formula lock)
|
|
14
|
+
// - plan-strategy D-5 (STORAGE_BUFFER_AVAILABLE variant axis day-1)
|
|
15
|
+
// - plan-strategy D-6 (4 PBR-unused slots default sampler + white tex)
|
|
16
|
+
// - plan-strategy D-10 (application registration path)
|
|
17
|
+
|
|
18
|
+
import type { ShaderRegistry } from './index.js';
|
|
19
|
+
import { DEFAULT_SPRITE_PARAM_SCHEMA } from './material-schemas.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* paramSchema for forgeax::sprite-lit -- mirrors sprite.material.json
|
|
23
|
+
* exactly (same 5 fields: colorTint / region / pivotAndSize /
|
|
24
|
+
* slicesAndMode / baseColorTexture). sprite-lit and sprite share the
|
|
25
|
+
* same Material UBO layout byte-for-byte so the host-side
|
|
26
|
+
* SpriteMaterialAsset writer + 4 BGL JSON are byte-identical (AC-07).
|
|
27
|
+
*
|
|
28
|
+
* OOS-1 (feat-future-sprite-lit-normal-map) will extend this with
|
|
29
|
+
* normalTexture + normalStrength. M1' deliberately keeps the schema
|
|
30
|
+
* byte-identical to sprite so AC-13 "1 string-change between sprite
|
|
31
|
+
* and sprite-lit materials" is provably the only delta required.
|
|
32
|
+
*/
|
|
33
|
+
const RESERVED_ID = 'forgeax::sprite-lit' as const;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Caps shape exposed for sprite-lit registration. Currently only
|
|
37
|
+
* `storageBuffer` matters (STORAGE_BUFFER_AVAILABLE variant axis); the
|
|
38
|
+
* per-shader BGL is derived from paramSchema at pipeline-build time, so
|
|
39
|
+
* the runtime layout factory consumes this caps shape downstream.
|
|
40
|
+
*
|
|
41
|
+
* Kept as a separate type rather than reusing `SkinCaps` to preserve
|
|
42
|
+
* a clear contract surface; the structural shape is currently identical
|
|
43
|
+
* but future divergence (sprite-lit may opt in to OOS-7 cluster-forward
|
|
44
|
+
* later) should not silently leak via a shared type.
|
|
45
|
+
*/
|
|
46
|
+
export interface SpriteLitCaps {
|
|
47
|
+
readonly storageBuffer: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Register the engine-shipped `forgeax::sprite-lit` material shader in
|
|
52
|
+
* the ShaderRegistry. Must be called once at engine boot (createRenderer),
|
|
53
|
+
* after `ShaderRegistry.loadManifest()` has completed.
|
|
54
|
+
*
|
|
55
|
+
* The `composedWgsl` parameter is the post-naga_oil composed WGSL source,
|
|
56
|
+
* compiled by @forgeax/engine-vite-plugin-shader at build time and surfaced
|
|
57
|
+
* in manifest.json. The caller reads it from the manifest entries.
|
|
58
|
+
*
|
|
59
|
+
* `caps.storageBuffer` switches the runtime BGL buffer types for
|
|
60
|
+
* @group(0) bindings 1+2 (pointLightsBuffer / spotLightsBuffer):
|
|
61
|
+
* `false` -> uniform fallback (AC-10 WebGL2 path); `true` -> read-only-storage.
|
|
62
|
+
* The BGL itself is built downstream in runtime/pbr-pipeline.ts (sprite-lit
|
|
63
|
+
* reuses pbr-view / pbr-mesh-array / pbr-instances unchanged; AC-07
|
|
64
|
+
* BGL byte-identical).
|
|
65
|
+
*
|
|
66
|
+
* @param registry ShaderRegistry instance (engine boot path).
|
|
67
|
+
* @param composedWgsl Post-naga_oil composed WGSL source string.
|
|
68
|
+
* @param caps sprite-lit caps (storageBuffer boolean) for BGL entry type selection.
|
|
69
|
+
*
|
|
70
|
+
* Throws if the identifier is already registered (fail-fast per
|
|
71
|
+
* ShaderRegistry.installMaterialArtifact contract).
|
|
72
|
+
*/
|
|
73
|
+
export function registerDefaultSpriteLit(
|
|
74
|
+
registry: ShaderRegistry,
|
|
75
|
+
composedWgsl: string,
|
|
76
|
+
caps: SpriteLitCaps,
|
|
77
|
+
): void {
|
|
78
|
+
// The caps argument is retained for API symmetry with
|
|
79
|
+
// registerDefaultStandardPbrSkin -- equivalent caps gating lives in
|
|
80
|
+
// buildPbrPipelineLayouts since the per-shader BGL is paramSchema-derived
|
|
81
|
+
// and the storage-vs-uniform branching is a runtime-level concern.
|
|
82
|
+
void caps;
|
|
83
|
+
registry.installMaterialArtifact(RESERVED_ID, {
|
|
84
|
+
source: composedWgsl,
|
|
85
|
+
paramSchema: DEFAULT_SPRITE_PARAM_SCHEMA,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// @forgeax/engine-shader - registerDefaultStandardPbrSkin helper.
|
|
2
|
+
//
|
|
3
|
+
// feat-20260523-skin-skeleton-animation M3 / T-30; updated by
|
|
4
|
+
// feat-20260613-material-paramschema-driven-binding M3 / w13: the
|
|
5
|
+
// bindingLayout sidecar field has been removed from MaterialShaderEntry;
|
|
6
|
+
// the BGL is derived from paramSchema via `derive(paramSchema).bglEntries`
|
|
7
|
+
// inside the runtime pipeline-builder. paramSchema remains shared with
|
|
8
|
+
// default-standard-pbr (identical PBR material interface).
|
|
9
|
+
//
|
|
10
|
+
// Anchors:
|
|
11
|
+
// - plan-strategy D-3: register independent WGSL template for skin variant
|
|
12
|
+
// - plan-strategy D-1 / D-2: paramSchema is the single source of truth
|
|
13
|
+
// - R-10: paramSchema reuses default-standard-pbr schema (same params)
|
|
14
|
+
|
|
15
|
+
import type { ShaderRegistry } from './index.js';
|
|
16
|
+
import { DEFAULT_STANDARD_PBR_PARAM_SCHEMA } from './material-schemas.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* paramSchema for forgeax::pbr-skin — mirrors default-standard-pbr 8 fields.
|
|
20
|
+
* Previously sourced from default-standard-pbr.schema.json; now inlined because
|
|
21
|
+
* the .schema.json file has been deleted (feat-20260528-material-shader-registration-unification M1/w4).
|
|
22
|
+
* The SSOT is default-standard-pbr.material.json paramSchema[].
|
|
23
|
+
*/
|
|
24
|
+
const RESERVED_ID = 'forgeax::pbr-skin' as const;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Caps shape historically consumed by the pbr-skin BGL builder. The
|
|
28
|
+
* BGL builder itself moved to runtime/pbr-pipeline.ts (buildPbrSkinLayouts);
|
|
29
|
+
* this type is preserved as the public registration-API contract so
|
|
30
|
+
* existing callers compile unchanged. M3 / w13: per-shader BGL is
|
|
31
|
+
* derived from paramSchema; the storage-buffer-vs-uniform branching
|
|
32
|
+
* the BGL needs lives entirely in the runtime layout factory.
|
|
33
|
+
*/
|
|
34
|
+
export interface SkinCaps {
|
|
35
|
+
readonly storageBuffer: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Register the engine-shipped `forgeax::pbr-skin` material shader in the
|
|
40
|
+
* ShaderRegistry. Must be called once at engine boot (createRenderer), after
|
|
41
|
+
* `ShaderRegistry.loadManifest()` has completed.
|
|
42
|
+
*
|
|
43
|
+
* The `composedWgsl` parameter is the post-naga_oil composed WGSL source,
|
|
44
|
+
* compiled by @forgeax/engine-vite-plugin-shader at build time and surfaced
|
|
45
|
+
* in manifest.json. The caller reads it from the manifest entries.
|
|
46
|
+
*
|
|
47
|
+
* `caps.storageBuffer` switches the runtime BGL buffer types:
|
|
48
|
+
* `false` -> uniform fallback; `true` -> read-only-storage. The BGL itself
|
|
49
|
+
* is built downstream in runtime/pbr-pipeline.ts (M3 / w13).
|
|
50
|
+
*
|
|
51
|
+
* @param registry ShaderRegistry instance (engine boot path).
|
|
52
|
+
* @param composedWgsl Post-naga_oil composed WGSL source string.
|
|
53
|
+
* @param caps PBR caps (storageBuffer boolean) for BGL entry type selection.
|
|
54
|
+
*
|
|
55
|
+
* Throws if the identifier is already registered (fail-fast per
|
|
56
|
+
* ShaderRegistry.installMaterialArtifact contract).
|
|
57
|
+
*/
|
|
58
|
+
export function registerDefaultStandardPbrSkin(
|
|
59
|
+
registry: ShaderRegistry,
|
|
60
|
+
composedWgsl: string,
|
|
61
|
+
caps: SkinCaps,
|
|
62
|
+
): void {
|
|
63
|
+
// M3 / w13 (D-1 / D-2): bindingLayout sidecar deleted from
|
|
64
|
+
// MaterialShaderEntry; paramSchema is the SSOT and downstream
|
|
65
|
+
// pipeline-builder reads `derive(paramSchema).bglEntries` on demand.
|
|
66
|
+
// The `caps` argument is retained for API compat — historically it
|
|
67
|
+
// selected the storage-buffer vs uniform-buffer skin variant of the
|
|
68
|
+
// BGL; equivalent caps gating now lives in buildPbrSkinLayouts.
|
|
69
|
+
void caps;
|
|
70
|
+
registry.installMaterialArtifact(RESERVED_ID, {
|
|
71
|
+
source: composedWgsl,
|
|
72
|
+
paramSchema: DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#define_import_path forgeax_pbr::shadow_pcf
|
|
2
|
+
|
|
3
|
+
// @forgeax/engine-shader - shadow-pcf.wgsl
|
|
4
|
+
// feat-20260612-point-light-shadows-urp-hdrp M2 / T-M2-1 (plan-strategy D-4).
|
|
5
|
+
//
|
|
6
|
+
// Shared PCF (percentage-closer filtering) for directional and point-light
|
|
7
|
+
// shadow sampling. Single SSOT for the slope-scaled bias formula and 9-tap 3x3
|
|
8
|
+
// kernel-offset table. Directional shadows (2D) and point-light shadows
|
|
9
|
+
// (cube_array) each have their own wrapper function; the bias formula and
|
|
10
|
+
// offset table are shared.
|
|
11
|
+
//
|
|
12
|
+
// Exports:
|
|
13
|
+
// - PCF_OFFSETS: const array<vec2<i32>, 9> — 3x3 kernel offset table
|
|
14
|
+
// - sample_shadow_2d(...) -> f32 — directional 2D 9-tap software PCF
|
|
15
|
+
// - sample_shadow_cube_hw2x2(...) -> f32 — point cube hardware 2x2 PCF
|
|
16
|
+
//
|
|
17
|
+
// Bias formula (D-1 directional convention -- normalBias is the slope
|
|
18
|
+
// coefficient, depthBias the floor; feat-20260621 m3-t5 rename):
|
|
19
|
+
// bias = max(normalBias * (1.0 - dot(N, L)), depthBias / 1000.0)
|
|
20
|
+
//
|
|
21
|
+
// Return: 1.0 = fully lit, 0.0 = fully shadowed.
|
|
22
|
+
|
|
23
|
+
// 3x3 integer offset table for PCF kernel (9 taps).
|
|
24
|
+
// Extracted from lighting-directional.wgsl inline loop (T-M2-4);
|
|
25
|
+
// `sample_shadow_2d` walks all 9 taps; `sample_shadow_cube_hw2x2` does NOT
|
|
26
|
+
// use this table (cubemap sampling has no UV parameterization across face
|
|
27
|
+
// seams — see Round-2 F-2 finding).
|
|
28
|
+
const PCF_OFFSETS = array<vec2<i32>, 9>(
|
|
29
|
+
vec2<i32>(-1, -1), vec2<i32>( 0, -1), vec2<i32>( 1, -1),
|
|
30
|
+
vec2<i32>(-1, 0), vec2<i32>( 0, 0), vec2<i32>( 1, 0),
|
|
31
|
+
vec2<i32>(-1, 1), vec2<i32>( 0, 1), vec2<i32>( 1, 1),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// === 2D directional-shadow wrapper ============================================
|
|
35
|
+
//
|
|
36
|
+
// `shadowMap` and `shadowSampler` are @group(0) @binding(3/4) from
|
|
37
|
+
// forgeax_view::common. `uv` is the [0,1]^2 projected light-clip coordinate.
|
|
38
|
+
// `texel` is 1.0 / textureDimensions(shadowMap, 0) precomputed by the caller.
|
|
39
|
+
//
|
|
40
|
+
// The caller must perform the OOB gate `uv in [0,1] && depthRef <= 1.0` before
|
|
41
|
+
// calling this function; OOB returns fully lit (1.0) upstream.
|
|
42
|
+
|
|
43
|
+
fn sample_shadow_2d(
|
|
44
|
+
shadowMap : texture_depth_2d,
|
|
45
|
+
shadowSampler: sampler_comparison,
|
|
46
|
+
uv : vec2<f32>,
|
|
47
|
+
texel : vec2<f32>,
|
|
48
|
+
depthRef : f32,
|
|
49
|
+
normalBias : f32,
|
|
50
|
+
depthBias : f32,
|
|
51
|
+
nDotL : f32,
|
|
52
|
+
) -> f32 {
|
|
53
|
+
// feat-20260621-merge-directionallightshadow-into-directionallight M3 / m3-t5:
|
|
54
|
+
// param names aligned to the D-1 directional convention -- the slope
|
|
55
|
+
// coefficient is `normalBias` (scales the 1 - dot(N,L) term) and the floor is
|
|
56
|
+
// `depthBias`. Pure formal-parameter rename: the two names are swapped in BOTH
|
|
57
|
+
// the signature and the formula, so each positional argument keeps its prior
|
|
58
|
+
// role and all call sites are unaffected. The /1000.0 floor scaling is the
|
|
59
|
+
// existing point-light convention and is preserved.
|
|
60
|
+
let bias = max(normalBias * (1.0 - nDotL), depthBias / 1000.0);
|
|
61
|
+
let adjustedDepth = depthRef - bias;
|
|
62
|
+
|
|
63
|
+
// 9-tap 3x3 PCF kernel. textureSampleCompareLevel returns 1.0 when
|
|
64
|
+
// adjustedDepth < sampledDepth (sampler compare op = 'less'), 0.0 otherwise.
|
|
65
|
+
// Sampler uses clamp-to-edge wrap so OOB texels return the nearest border value.
|
|
66
|
+
var blocked = 0.0;
|
|
67
|
+
for (var i = 0u; i < 9u; i++) {
|
|
68
|
+
let off = PCF_OFFSETS[i];
|
|
69
|
+
let offsetUv = uv + vec2<f32>(f32(off.x), f32(off.y)) * texel;
|
|
70
|
+
let lit = textureSampleCompareLevel(shadowMap, shadowSampler, offsetUv, adjustedDepth);
|
|
71
|
+
blocked = blocked + (1.0 - lit);
|
|
72
|
+
}
|
|
73
|
+
return 1.0 - blocked / 9.0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// === Cube point-light-shadow wrapper =========================================
|
|
77
|
+
//
|
|
78
|
+
// `shadowAtlas` is @group(0) @binding(5) texture_depth_cube_array.
|
|
79
|
+
// `shadowSampler` is @group(0) @binding(4) sampler_comparison (shared with
|
|
80
|
+
// directional shadows — same sampler type, no dimension distinction).
|
|
81
|
+
// `lightLocal` is the fragment-to-light direction vector in the cubemap's
|
|
82
|
+
// coordinate system (Bevy convention: left-handed cubemap; caller applies
|
|
83
|
+
// flip_z = vec3(1,1,-1) to convert right-hand world to left-hand cubemap).
|
|
84
|
+
// `layer` is the cube_array layer index (i32; 0..3 for 4 shadow-casting
|
|
85
|
+
// point lights; sentinel -1 must be gated upstream — this function assumes
|
|
86
|
+
// a valid layer).
|
|
87
|
+
// `depthRef` is the reconstructed [0,1] NDC depth (largest-axis projection,
|
|
88
|
+
// research L0.5 Bevy fetch_point_shadow pattern).
|
|
89
|
+
//
|
|
90
|
+
// Hardware 2x2 PCF only: 2D texel offsets from PCF_OFFSETS have no direct 3D
|
|
91
|
+
// cubemap mapping (no UV parameterization across face seams). A previous
|
|
92
|
+
// 9-tap loop using the same cubemap direction every iteration was algorithmic
|
|
93
|
+
// dead code — every iteration returned the same `lit` value, so 9-tap = 1-tap
|
|
94
|
+
// (Reviewer F-2). This single call lets the GPU's silicon-level 2x2 PCF
|
|
95
|
+
// resolve the local neighborhood within each face natively. Matches Bevy's
|
|
96
|
+
// HARDWARE_2X2 path (research L0.6) and README §AC-11 default. Software 9-tap
|
|
97
|
+
// on cubemaps requires face-basis vector offsets and is OOS-3 here.
|
|
98
|
+
|
|
99
|
+
fn sample_shadow_cube_hw2x2(
|
|
100
|
+
shadowAtlas : texture_depth_cube_array,
|
|
101
|
+
shadowSampler : sampler_comparison,
|
|
102
|
+
lightLocal : vec3<f32>,
|
|
103
|
+
layer : i32,
|
|
104
|
+
depthRef : f32,
|
|
105
|
+
normalBias : f32,
|
|
106
|
+
depthBias : f32,
|
|
107
|
+
nDotL : f32,
|
|
108
|
+
) -> f32 {
|
|
109
|
+
// Slope-scaled bias: same formula as 2D path, shared SSOT. feat-20260621 M3 /
|
|
110
|
+
// m3-t5: param names aligned to D-1 (normalBias = slope coefficient,
|
|
111
|
+
// depthBias = floor). Pure rename -- each positional arg keeps its prior role.
|
|
112
|
+
let bias = max(normalBias * (1.0 - nDotL), depthBias / 1000.0);
|
|
113
|
+
let adjustedDepth = depthRef - bias;
|
|
114
|
+
|
|
115
|
+
// Single textureSampleCompareLevel call: the comparison sampler resolves a
|
|
116
|
+
// 2x2 PCF neighborhood at the silicon level; clamp-to-edge address mode
|
|
117
|
+
// handles face-boundary texel fetches. Returns 1.0 = fully lit, 0.0 = fully
|
|
118
|
+
// shadowed.
|
|
119
|
+
return textureSampleCompareLevel(shadowAtlas, shadowSampler, lightLocal, layer, adjustedDepth);
|
|
120
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#pragma variant_axis STORAGE_BUFFER_AVAILABLE
|
|
2
|
+
|
|
3
|
+
// @forgeax/engine-shader shadow_caster.wgsl
|
|
4
|
+
// feat-20260520-directional-light-shadow-mapping M1c / w9 (D-9 / AC-09):
|
|
5
|
+
// vertex-only depth pass for directional shadow map. No fragment stage --
|
|
6
|
+
// the GPU writes depth automatically from gl_Position.z (depth32float RT).
|
|
7
|
+
//
|
|
8
|
+
// feat-20260613-csm-cascaded-shadow-maps M5 / w28: per-cascade
|
|
9
|
+
// lightViewProj selection. Each cascade pass writes a different
|
|
10
|
+
// `shadowCasterCascade.index` (0..3) before encoder submit; the vertex
|
|
11
|
+
// shader reads it to pick `view.lightViewProj_A..D`. The atlas tile UV
|
|
12
|
+
// inset is already baked into each lightViewProj host-side
|
|
13
|
+
// (render-system-extract.ts), so the per-cascade viewport on the depth
|
|
14
|
+
// pass clips rasterization to the correct atlas tile while the matrix
|
|
15
|
+
// itself maps NDC straight into atlas-space [0,1]^2.
|
|
16
|
+
//
|
|
17
|
+
// Reuses:
|
|
18
|
+
// @group(0) binding(0) view : View -- common.wgsl
|
|
19
|
+
// @group(0) binding(5) shadowCasterCascade -- common.wgsl
|
|
20
|
+
// @group(2) binding(0) meshes : array<Mesh> -- common.wgsl
|
|
21
|
+
// @group(3) binding(0) instances : array<InstanceData> -- common.wgsl
|
|
22
|
+
//
|
|
23
|
+
// Vertex layout: matches the 12F procedural layout of every mesh
|
|
24
|
+
// (position vec3, normal vec3, uv vec2, tangent vec4; 48 B stride).
|
|
25
|
+
|
|
26
|
+
#import forgeax_view::common::{View, Mesh, InstanceData, ShadowCasterCascade, view, shadowCasterCascade, meshes, instances}
|
|
27
|
+
|
|
28
|
+
struct VsInput {
|
|
29
|
+
@location(0) position : vec3<f32>,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
fn _cascadeLightViewProj(layer : u32) -> mat4x4<f32> {
|
|
33
|
+
switch (layer) {
|
|
34
|
+
case 0u: { return view.lightViewProj_A; }
|
|
35
|
+
case 1u: { return view.lightViewProj_B; }
|
|
36
|
+
case 2u: { return view.lightViewProj_C; }
|
|
37
|
+
default: { return view.lightViewProj_D; }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@vertex
|
|
42
|
+
fn vs_main(in : VsInput, @builtin(instance_index) idx : u32) -> @builtin(position) vec4<f32> {
|
|
43
|
+
let worldPos = meshes[0].worldFromLocal * instances[idx].localFromInstance * vec4<f32>(in.position, 1.0);
|
|
44
|
+
// feat-20260625-spot-light-shadow-mapping M2 / w10 (D-1): spot shadow passes
|
|
45
|
+
// set `isSpot = 1u` and write their perspective matrix into
|
|
46
|
+
// `spotLightViewProj`; directional cascade passes keep `isSpot = 0u` and read
|
|
47
|
+
// `view.lightViewProj_A..D` via `index`. Routing on the discriminant keeps
|
|
48
|
+
// the spot matrix out of the directional View UBO (no same-frame contention).
|
|
49
|
+
if (shadowCasterCascade.isSpot == 1u) {
|
|
50
|
+
return shadowCasterCascade.spotLightViewProj * worldPos;
|
|
51
|
+
}
|
|
52
|
+
let lvp = _cascadeLightViewProj(shadowCasterCascade.index);
|
|
53
|
+
return lvp * worldPos;
|
|
54
|
+
}
|
package/src/skybox.wgsl
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#define_import_path forgeax_view::skybox
|
|
2
|
+
|
|
3
|
+
// @forgeax/engine-shader - skybox.wgsl
|
|
4
|
+
// (feat-20260531-skybox-env-background / M3 / w13).
|
|
5
|
+
//
|
|
6
|
+
// Skybox fullscreen pass: vertex stage imports the SSOT fullscreen_triangle()
|
|
7
|
+
// from forgeax_view::common (same single-large-triangle pattern as tonemap +
|
|
8
|
+
// fxaa); fragment stage reconstructs world-space view direction from the
|
|
9
|
+
// screen-space UV + inverseViewProj (View UBO, w3), samples the cubemap
|
|
10
|
+
// texture, and writes HDR colour to the hdrColor rgba16float render target
|
|
11
|
+
// (plan-strategy D-2/D-7).
|
|
12
|
+
//
|
|
13
|
+
// Y-negation: the equirect-to-cube render passes used during cubemap upload
|
|
14
|
+
// follow the WebGL/OpenGL convention (Y-up in world space maps to V=0 at
|
|
15
|
+
// the bottom of the cubemap face). WebGPU's texture coordinate origin is
|
|
16
|
+
// top-left (V=0 at the top), so sampled directions must negate Y to match
|
|
17
|
+
// the same convention used by ibl-sampling.wgsl (plan-strategy R-4, D-7).
|
|
18
|
+
//
|
|
19
|
+
// Bindings (group 0):
|
|
20
|
+
// @binding(0) cubemap : texture_cube<f32> // skybox environment map
|
|
21
|
+
// @binding(1) cubemapSampler : sampler // linear filter + clamp-to-edge
|
|
22
|
+
// @binding(2) view : View (UBO) // carries inverseViewProj (w3)
|
|
23
|
+
// @binding(3) rotation : SkyboxRotation (UBO)
|
|
24
|
+
|
|
25
|
+
#import forgeax_view::common::FullscreenOutput
|
|
26
|
+
#import forgeax_view::common::{View, FogViewParams, FogRay}
|
|
27
|
+
#import forgeax_view::common::fullscreen_triangle
|
|
28
|
+
#import forgeax_view::fog::{apply_fog}
|
|
29
|
+
#import forgeax_pbr::ibl_shared::{inverseRotateEnvironment}
|
|
30
|
+
|
|
31
|
+
@group(0) @binding(0) var cubemap : texture_cube<f32>;
|
|
32
|
+
@group(0) @binding(1) var cubemapSampler : sampler;
|
|
33
|
+
@group(0) @binding(2) var<uniform> view : View;
|
|
34
|
+
|
|
35
|
+
struct SkyboxRotation {
|
|
36
|
+
rotation: vec4<f32>,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
@group(0) @binding(3) var<uniform> environment : SkyboxRotation;
|
|
40
|
+
|
|
41
|
+
@vertex
|
|
42
|
+
fn vs_main(@builtin(vertex_index) vertex_index : u32) -> FullscreenOutput {
|
|
43
|
+
return fullscreen_triangle(vertex_index);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Reconstruct world-space view direction from screen UV + inverseViewProj.
|
|
47
|
+
// clipPos.xy derived from screen-space UV, clipPos.w = 1, apply
|
|
48
|
+
// inverseViewProj to get world-space direction. Negate Y to match the
|
|
49
|
+
// IBL cubemap Y convention (ibl-sampling.wgsl:30,47).
|
|
50
|
+
//
|
|
51
|
+
// bug-20260608-skybox-cubemaps-upside-down: ndc.y must UNDO the V-flip
|
|
52
|
+
// `fullscreen_triangle()` bakes in (`v = 1 - (y + 1) * 0.5`, common.wgsl:204);
|
|
53
|
+
// the prior `uv.y * 2 - 1` produced the OPPOSITE sign of the actual NDC.y at
|
|
54
|
+
// each fragment, so `inverseViewProj * ndc` returned the world ray for the
|
|
55
|
+
// MIRRORED screen position, and the final `-dir.y` (kept for the IBL bake
|
|
56
|
+
// convention) ended up rendering the skybox upside-down. Reverse the V-flip
|
|
57
|
+
// here with `1.0 - uv.y * 2.0`; see skybox-direction.test.ts.
|
|
58
|
+
fn skyboxDirection(uv : vec2<f32>) -> vec3<f32> {
|
|
59
|
+
let ndc = vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 1.0, 1.0);
|
|
60
|
+
let worldDir = view.inverseViewProj * ndc;
|
|
61
|
+
let dir = normalize(worldDir.xyz / worldDir.w);
|
|
62
|
+
let rotated = inverseRotateEnvironment(dir, environment.rotation);
|
|
63
|
+
return vec3<f32>(rotated.x, -rotated.y, rotated.z);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@fragment
|
|
67
|
+
fn skybox_fs(in : FullscreenOutput) -> @location(0) vec4<f32> {
|
|
68
|
+
let dir = skyboxDirection(in.uv);
|
|
69
|
+
let color = textureSample(cubemap, cubemapSampler, dir).rgb;
|
|
70
|
+
return apply_fog(
|
|
71
|
+
view.fog,
|
|
72
|
+
FogRay(view.cameraPos, dir, max(view.temporalProjection.y, 0.0)),
|
|
73
|
+
vec4<f32>(color, 1.0),
|
|
74
|
+
);
|
|
75
|
+
}
|