@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.
- package/README.md +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +33019 -8
- package/dist/index.mjs.map +1 -1
- package/dist/ltc/provenance.d.ts +17 -0
- package/dist/ltc/provenance.d.ts.map +1 -0
- package/dist/ltc/tables.d.ts +22 -0
- package/dist/ltc/tables.d.ts.map +1 -0
- package/dist/material/artifact-types.d.ts +52 -0
- package/dist/material/artifact-types.d.ts.map +1 -1
- package/dist/material-schemas.d.ts +5 -0
- package/dist/material-schemas.d.ts.map +1 -1
- package/dist/register-default-sprite-lit.d.ts +3 -6
- package/dist/register-default-sprite-lit.d.ts.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +121 -58
- package/src/__tests__/direct-light-layout.unit.test.ts +40 -0
- package/src/__tests__/ibl-irradiance.unit.test.ts +7 -4
- package/src/__tests__/ibl-sampling.unit.test.ts +3 -4
- package/src/__tests__/lighting-point-spot-volume.unit.test.ts +8 -2
- package/src/__tests__/lighting-punctual.unit.test.ts +29 -2
- package/src/__tests__/lighting-spot-volume-attenuation.unit.test.ts +1 -1
- package/src/__tests__/ltc-provenance.unit.test.ts +33 -0
- package/src/__tests__/manifest.fixture.json +33 -0
- package/src/__tests__/material-contract.unit.test.ts +61 -1
- package/src/__tests__/probe-lighting-composition.unit.test.ts +38 -0
- package/src/__tests__/rect-area-brdf.unit.test.ts +36 -0
- package/src/__tests__/reflection-probe-sampling.unit.test.ts +24 -2
- package/src/__tests__/shader.unit.test.ts +6 -2
- package/src/__tests__/spot-cookie-composition.unit.test.ts +57 -0
- package/src/__tests__/spot-modifier-composition.unit.test.ts +19 -0
- package/src/__tests__/sprite-variants.unit.test.ts +4 -1
- package/src/brdf.wgsl +115 -1
- package/src/common.wgsl +53 -79
- package/src/default-standard-pbr-skin.wgsl +80 -23
- package/src/default-standard-pbr.wgsl +119 -50
- package/src/ibl-irradiance.wgsl +4 -1
- package/src/ibl-sampling.wgsl +2 -2
- package/src/index.ts +17 -0
- package/src/lighting-directional.wgsl +6 -116
- package/src/lighting-probe.wgsl +51 -0
- package/src/lighting-punctual.wgsl +21 -11
- package/src/lighting-rect-area.wgsl +162 -0
- package/src/lighting-spot-modifiers.wgsl +93 -0
- package/src/ltc/provenance.ts +19 -0
- package/src/ltc/tables.ts +2831 -0
- package/src/material/artifact-types.ts +137 -0
- package/src/material-schemas.ts +12 -0
- package/src/register-default-sprite-lit.ts +3 -6
- package/src/sprite-lit.wgsl +3 -3
- package/src/sprite.wgsl +1 -1
- package/src/standard-cluster.wgsl +94 -57
- package/src/types.ts +2 -1
- package/src/volume/volume-inject.wgsl +34 -36
- package/src/volume/volume-integrate.wgsl +48 -38
- package/dist/.tsbuildinfo +0 -1
|
@@ -1,14 +1,151 @@
|
|
|
1
1
|
import type { BindGroupLayoutDescriptor } from '@forgeax/engine-types';
|
|
2
2
|
|
|
3
|
+
export interface MaterialShaderResourceSlot {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly parameter: string;
|
|
6
|
+
readonly kind: 'sampler' | 'texture';
|
|
7
|
+
readonly group: number;
|
|
8
|
+
readonly binding: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface MaterialShaderVertexInput {
|
|
12
|
+
readonly semantic: string;
|
|
13
|
+
readonly location: number;
|
|
14
|
+
readonly format: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MaterialShaderArtifactReceipt {
|
|
18
|
+
readonly directEntry: string;
|
|
19
|
+
readonly sceneIndexEntry: string;
|
|
20
|
+
readonly materialRow: {
|
|
21
|
+
readonly byteLength: number;
|
|
22
|
+
readonly fields: readonly string[];
|
|
23
|
+
};
|
|
24
|
+
readonly resourceSlots: readonly MaterialShaderResourceSlot[];
|
|
25
|
+
readonly uvSets: readonly { readonly parameter: string; readonly set: number }[];
|
|
26
|
+
readonly vertexInputs: readonly MaterialShaderVertexInput[];
|
|
27
|
+
readonly alphaMask: { readonly cutoff: string; readonly source: string };
|
|
28
|
+
readonly skinPaletteAddress?: {
|
|
29
|
+
readonly group: number;
|
|
30
|
+
readonly binding: number;
|
|
31
|
+
readonly stride: number;
|
|
32
|
+
};
|
|
33
|
+
readonly reflection: {
|
|
34
|
+
readonly layoutIdentity: string;
|
|
35
|
+
readonly resourceSlots: readonly MaterialShaderResourceSlot[];
|
|
36
|
+
readonly vertexInputs: readonly MaterialShaderVertexInput[];
|
|
37
|
+
};
|
|
38
|
+
readonly receiptIdentity: string;
|
|
39
|
+
readonly generation: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
3
42
|
export interface MaterialShaderArtifact {
|
|
4
43
|
readonly material: string;
|
|
5
44
|
readonly pass: string;
|
|
6
45
|
readonly wgsl: string;
|
|
7
46
|
readonly layoutIdentity: string;
|
|
47
|
+
/** Naga-reflected UV set count used to derive clamp-to-last vertex aliases. */
|
|
48
|
+
readonly uvSetCount?: number;
|
|
8
49
|
readonly bindings: readonly BindGroupLayoutDescriptor[];
|
|
9
50
|
readonly deps: readonly string[];
|
|
10
51
|
readonly vertexInputs: readonly Readonly<Record<string, unknown>>[];
|
|
11
52
|
readonly specializationKey?: string;
|
|
53
|
+
/** Producer-owned ABI facts shared by direct and scene-index consumers. */
|
|
54
|
+
readonly receipt?: MaterialShaderArtifactReceipt;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const STANDARD_PBR_TEXTURES = [
|
|
58
|
+
'baseColorTexture',
|
|
59
|
+
'metallicRoughnessTexture',
|
|
60
|
+
'normalTexture',
|
|
61
|
+
'specularTintTexture',
|
|
62
|
+
'emissiveTexture',
|
|
63
|
+
'occlusionTexture',
|
|
64
|
+
'transmissionTexture',
|
|
65
|
+
'thicknessTexture',
|
|
66
|
+
] as const;
|
|
67
|
+
|
|
68
|
+
const STANDARD_PBR_NUMERIC_FIELDS = [
|
|
69
|
+
'baseColor',
|
|
70
|
+
'metallic',
|
|
71
|
+
'roughness',
|
|
72
|
+
'metallicChannel',
|
|
73
|
+
'roughnessChannel',
|
|
74
|
+
'aoChannel',
|
|
75
|
+
'extraChannel',
|
|
76
|
+
'emissive',
|
|
77
|
+
'emissiveIntensity',
|
|
78
|
+
'occlusionStrength',
|
|
79
|
+
'alphaCutoff',
|
|
80
|
+
'clearcoat',
|
|
81
|
+
'clearcoatRoughness',
|
|
82
|
+
'specularTint',
|
|
83
|
+
'normalScale',
|
|
84
|
+
'transmission',
|
|
85
|
+
'ior',
|
|
86
|
+
'thickness',
|
|
87
|
+
'attenuationColor',
|
|
88
|
+
'attenuationDistance',
|
|
89
|
+
] as const;
|
|
90
|
+
|
|
91
|
+
const STANDARD_PBR_VERTEX_INPUTS: readonly MaterialShaderVertexInput[] = [
|
|
92
|
+
{ semantic: 'position', location: 0, format: 'float32x3' },
|
|
93
|
+
{ semantic: 'normal', location: 1, format: 'float32x3' },
|
|
94
|
+
{ semantic: 'uv', location: 2, format: 'float32x2' },
|
|
95
|
+
{ semantic: 'tangent', location: 3, format: 'float32x4' },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
function standardPbrResourceSlots(): MaterialShaderResourceSlot[] {
|
|
99
|
+
return STANDARD_PBR_TEXTURES.flatMap((parameter, index) => [
|
|
100
|
+
{
|
|
101
|
+
name: `${parameter}_sampler`,
|
|
102
|
+
parameter,
|
|
103
|
+
kind: 'sampler' as const,
|
|
104
|
+
group: 1,
|
|
105
|
+
binding: index * 2 + 1,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: parameter,
|
|
109
|
+
parameter,
|
|
110
|
+
kind: 'texture' as const,
|
|
111
|
+
group: 1,
|
|
112
|
+
binding: index * 2 + 2,
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Builds the one Standard PBR ABI receipt consumed by both material lanes.
|
|
119
|
+
* The skinned module uses the same material producer and only adds its palette
|
|
120
|
+
* address; it must retain the same receipt identity for material compatibility.
|
|
121
|
+
*/
|
|
122
|
+
export function createStandardPbrArtifactReceipt(skinned = false): MaterialShaderArtifactReceipt {
|
|
123
|
+
const resourceSlots = standardPbrResourceSlots();
|
|
124
|
+
const uvSets = STANDARD_PBR_TEXTURES.map((parameter) => ({ parameter, set: 0 }));
|
|
125
|
+
const vertexInputs = skinned
|
|
126
|
+
? [
|
|
127
|
+
...STANDARD_PBR_VERTEX_INPUTS,
|
|
128
|
+
{ semantic: 'skinIndex', location: 4, format: 'uint16x4' },
|
|
129
|
+
{ semantic: 'skinWeight', location: 5, format: 'float32x4' },
|
|
130
|
+
]
|
|
131
|
+
: [...STANDARD_PBR_VERTEX_INPUTS];
|
|
132
|
+
return {
|
|
133
|
+
directEntry: 'vs_main',
|
|
134
|
+
sceneIndexEntry: 'vs_scene_index',
|
|
135
|
+
materialRow: { byteLength: 384, fields: [...STANDARD_PBR_NUMERIC_FIELDS] },
|
|
136
|
+
resourceSlots,
|
|
137
|
+
uvSets,
|
|
138
|
+
vertexInputs,
|
|
139
|
+
alphaMask: { cutoff: 'alphaCutoff', source: 'baseColor.a' },
|
|
140
|
+
...(skinned ? { skinPaletteAddress: { group: 2, binding: 1, stride: 64 } } : {}),
|
|
141
|
+
reflection: {
|
|
142
|
+
layoutIdentity: 'standard-pbr/material-row-v1',
|
|
143
|
+
resourceSlots,
|
|
144
|
+
vertexInputs,
|
|
145
|
+
},
|
|
146
|
+
receiptIdentity: 'standard-pbr/material-row-v1',
|
|
147
|
+
generation: 1,
|
|
148
|
+
};
|
|
12
149
|
}
|
|
13
150
|
|
|
14
151
|
export function isMaterialShaderArtifact(value: unknown): value is MaterialShaderArtifact {
|
package/src/material-schemas.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ParamSchemaEntry } from '@forgeax/engine-types';
|
|
2
|
+
import {
|
|
3
|
+
createStandardPbrArtifactReceipt,
|
|
4
|
+
type MaterialShaderArtifactReceipt,
|
|
5
|
+
} from './material/artifact-types.js';
|
|
2
6
|
|
|
3
7
|
export const STANDARD_PBR_ALPHA_CUTOFF_DEFAULT = 0;
|
|
4
8
|
|
|
@@ -34,6 +38,14 @@ export const DEFAULT_STANDARD_PBR_PARAM_SCHEMA: readonly ParamSchemaEntry[] = [
|
|
|
34
38
|
{ name: 'thicknessTexture', type: 'texture2d' },
|
|
35
39
|
];
|
|
36
40
|
|
|
41
|
+
/** The single producer receipt shared by direct and scene-index Standard PBR. */
|
|
42
|
+
export const STANDARD_PBR_ARTIFACT_RECEIPT: MaterialShaderArtifactReceipt =
|
|
43
|
+
createStandardPbrArtifactReceipt();
|
|
44
|
+
|
|
45
|
+
/** Skinned Standard PBR keeps the material receipt identity and adds palette ABI. */
|
|
46
|
+
export const STANDARD_PBR_SKIN_ARTIFACT_RECEIPT: MaterialShaderArtifactReceipt =
|
|
47
|
+
createStandardPbrArtifactReceipt(true);
|
|
48
|
+
|
|
37
49
|
export const DEFAULT_UNLIT_PARAM_SCHEMA: readonly ParamSchemaEntry[] = [
|
|
38
50
|
{ name: 'baseColor', type: 'color', default: [1, 1, 1, 1] },
|
|
39
51
|
{ name: 'alphaCutoff', type: 'f32', default: 0 },
|
|
@@ -56,12 +56,9 @@ export interface SpriteLitCaps {
|
|
|
56
56
|
* compiled by @forgeax/engine-vite-plugin-shader at build time and surfaced
|
|
57
57
|
* in manifest.json. The caller reads it from the manifest entries.
|
|
58
58
|
*
|
|
59
|
-
* `caps.storageBuffer`
|
|
60
|
-
*
|
|
61
|
-
*
|
|
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).
|
|
59
|
+
* `caps.storageBuffer` is retained for the shared mesh/instance layout
|
|
60
|
+
* selection. Sprite-lit local lighting reads the same Standard Cluster group
|
|
61
|
+
* as PBR; the view group has no direct point/spot arrays.
|
|
65
62
|
*
|
|
66
63
|
* @param registry ShaderRegistry instance (engine boot path).
|
|
67
64
|
* @param composedWgsl Post-naga_oil composed WGSL source string.
|
package/src/sprite-lit.wgsl
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#pragma variant_axis CLUSTER_FORWARD_AVAILABLE
|
|
3
3
|
#define_import_path forgeax_material::sprite-lit
|
|
4
4
|
|
|
5
|
-
#import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, sampleMaterialTexture}
|
|
5
|
+
#import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, sampleMaterialTexture, packSceneTemporal}
|
|
6
6
|
#import forgeax_scene_temporal::{packSceneTemporalV1, sceneViewZ}
|
|
7
7
|
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
8
8
|
#import forgeax_standard::cluster::{evaluateStandardClusterLights}
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
// 1 string change in MaterialAsset.passes[0].shader).
|
|
40
40
|
//
|
|
41
41
|
// Bindings (byte-identical to sprite.wgsl so the 4 BindGroupLayout chain is
|
|
42
|
-
// reused without a per-pipeline BGL; the
|
|
42
|
+
// reused without a per-pipeline BGL; the PBR-unused entries 3..6 bind
|
|
43
43
|
// pipelineState.defaultSampler + .defaultWhiteTextureView on the host side):
|
|
44
44
|
//
|
|
45
45
|
// @group(0) @binding(0) view uniform
|
|
46
|
-
// @group(
|
|
46
|
+
// @group(2) @binding(3..6) Standard Cluster storage (selected variant)
|
|
47
47
|
// @group(1) @binding(0) material uniform (sprite layout
|
|
48
48
|
// colorTint /
|
|
49
49
|
// region /
|
package/src/sprite.wgsl
CHANGED
|
@@ -152,7 +152,7 @@ fn resolveSpriteVertex(in : VsIn, idx : u32, vertex_index : u32) -> SpriteVertex
|
|
|
152
152
|
// pivotAndSize.zw scaled the local quad AND the world matrix also scaled
|
|
153
153
|
// it, producing the scale^2 visual size debt that research F-4 flagged.
|
|
154
154
|
// Post-w11 there is one and only one scale source: the entity's
|
|
155
|
-
//
|
|
155
|
+
// GlobalTransform.world. The mesh's built-in vertex.pos is NOT consumed (sprite
|
|
156
156
|
// geometry is fully re-derived from uv + pivot) so HANDLE_QUAD's mesh.pos
|
|
157
157
|
// remains a placeholder driving vertex count (4) and topology only.
|
|
158
158
|
//
|
|
@@ -3,50 +3,56 @@
|
|
|
3
3
|
//
|
|
4
4
|
// The cluster module owns membership and raw payload decoding only. Punctual
|
|
5
5
|
// BRDF, range, cone, PCF, and fail-open behavior live in lighting-punctual.
|
|
6
|
+
// ProbeBlendRecord is intentionally absent: probes are not DirectLightSlot
|
|
7
|
+
// members and this cluster owner never performs an all-probe loop.
|
|
6
8
|
|
|
7
9
|
#define_import_path forgeax_standard::cluster
|
|
8
10
|
|
|
9
11
|
#import forgeax_pbr::lighting_punctual::{evalPoint, evalPointFlat, evalSpot, evalSpotFlat, evalSpotShadowed}
|
|
10
12
|
#import forgeax_pbr::lighting_attenuation::{projectSpotUv}
|
|
13
|
+
#import forgeax_pbr::lighting_spot_modifiers::{spotModifierFactors}
|
|
14
|
+
#import forgeax_pbr::lighting_rect_area::{evalRectAreaLtcGgx}
|
|
11
15
|
#import forgeax_view::common::{view}
|
|
12
16
|
#ifdef PROJECTOR_AVAILABLE
|
|
17
|
+
#ifdef EXTENDED_LIGHTING_AVAILABLE
|
|
18
|
+
#import forgeax_view::common::{spotModifierSampler, cookieTexture}
|
|
19
|
+
#else
|
|
20
|
+
#ifndef EXTENDED_LIGHTING_AVAILABLE
|
|
13
21
|
#import forgeax_view::common::{projectorTexture, projectorSampler}
|
|
14
22
|
#endif
|
|
23
|
+
#endif
|
|
24
|
+
#endif
|
|
15
25
|
#ifdef POINT_SHADOW_AVAILABLE
|
|
16
26
|
#import forgeax_pbr::lighting_punctual::{evalPointShadowed}
|
|
27
|
+
#import forgeax_view::common::{shadowParams}
|
|
17
28
|
#endif
|
|
18
29
|
|
|
19
30
|
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
20
31
|
|
|
21
32
|
const KIND_POINT: u32 = 0u;
|
|
22
33
|
const KIND_SPOT: u32 = 1u;
|
|
23
|
-
|
|
24
|
-
// marker. The remaining bits are the shadow-atlas tile (0..3), with zero as
|
|
25
|
-
// the explicit View-UBO lane for projector-only spots.
|
|
34
|
+
const KIND_RECT_AREA: u32 = 2u;
|
|
26
35
|
const PROJECTOR_FLAG: i32 = 0x40000000;
|
|
27
36
|
const TILE_MASK: i32 = 0x3fffffff;
|
|
28
37
|
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
color : vec4<f32>,
|
|
38
|
-
direction : vec4<f32>,
|
|
39
|
-
kind_and_shadow : vec4<u32>,
|
|
38
|
+
// DirectLightSlot is the byte-frozen 80B std430/std140 transport contract.
|
|
39
|
+
// The named rows match the host packer; metadata carries kind and identities.
|
|
40
|
+
struct DirectLightSlot {
|
|
41
|
+
position : vec4<f32>,
|
|
42
|
+
colorTimesIntensity : vec4<f32>,
|
|
43
|
+
direction : vec4<f32>,
|
|
44
|
+
auxiliary : vec4<f32>,
|
|
45
|
+
metadata : vec4<u32>,
|
|
40
46
|
};
|
|
41
47
|
|
|
42
|
-
const
|
|
48
|
+
const DIRECT_LIGHT_SLOT_BYTE_SIZE: u32 = 80u;
|
|
43
49
|
|
|
44
50
|
struct ClusterUniform {
|
|
45
51
|
grid : vec4<u32>,
|
|
46
52
|
near_far_log : vec4<f32>,
|
|
47
53
|
};
|
|
48
54
|
|
|
49
|
-
@group(2) @binding(3) var<storage, read> light_data : array<
|
|
55
|
+
@group(2) @binding(3) var<storage, read> light_data : array<DirectLightSlot, 256>;
|
|
50
56
|
@group(2) @binding(4) var<storage, read> cluster_grid : array<u32>;
|
|
51
57
|
@group(2) @binding(5) var<storage, read> light_index_list : array<u32>;
|
|
52
58
|
@group(2) @binding(6) var<uniform> cluster_uniform : ClusterUniform;
|
|
@@ -91,19 +97,27 @@ fn ndc_position_to_cluster(
|
|
|
91
97
|
|
|
92
98
|
// Surface SpotLight projector sampling is owned by the same clustered light
|
|
93
99
|
// evaluator as the analytic cone/range contribution. The host marks the
|
|
94
|
-
// selected projector in the
|
|
100
|
+
// selected projector in the DirectLightSlot flag and writes its matrix to the
|
|
95
101
|
// corresponding View lane (lane 0 for projector-only); no second
|
|
96
102
|
// material-local sampling loop or resource owner is allowed.
|
|
97
103
|
fn sampleStandardSpotProjector(
|
|
98
104
|
lightViewProj : mat4x4<f32>,
|
|
99
105
|
world_pos : vec3<f32>,
|
|
106
|
+
metadata : vec4<u32>,
|
|
100
107
|
) -> vec3<f32> {
|
|
101
108
|
#ifdef PROJECTOR_AVAILABLE
|
|
102
109
|
let uv = projectSpotUv(lightViewProj, world_pos);
|
|
103
110
|
if (!(uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0)) {
|
|
104
111
|
return vec3<f32>(1.0);
|
|
105
112
|
}
|
|
113
|
+
#ifdef EXTENDED_LIGHTING_AVAILABLE
|
|
114
|
+
if (metadata.w == 0xffffffffu) {
|
|
115
|
+
return vec3<f32>(1.0);
|
|
116
|
+
}
|
|
117
|
+
return textureSampleLevel(cookieTexture, spotModifierSampler, uv, metadata.w, 0.0).rgb;
|
|
118
|
+
#else
|
|
106
119
|
return textureSampleLevel(projectorTexture, projectorSampler, uv, 0.0).rgb;
|
|
120
|
+
#endif
|
|
107
121
|
#else
|
|
108
122
|
return vec3<f32>(1.0);
|
|
109
123
|
#endif
|
|
@@ -112,7 +126,7 @@ fn sampleStandardSpotProjector(
|
|
|
112
126
|
// Decode one kind first, then delegate the complete evaluation to the shared
|
|
113
127
|
// punctual owner. Unknown kinds are explicitly zero contribution.
|
|
114
128
|
fn evaluate_cluster_light(
|
|
115
|
-
light :
|
|
129
|
+
light : DirectLightSlot,
|
|
116
130
|
world_pos : vec3<f32>,
|
|
117
131
|
normal : vec3<f32>,
|
|
118
132
|
view_dir : vec3<f32>,
|
|
@@ -122,78 +136,101 @@ fn evaluate_cluster_light(
|
|
|
122
136
|
f0 : vec3<f32>,
|
|
123
137
|
flat_2d : bool,
|
|
124
138
|
) -> vec3<f32> {
|
|
125
|
-
let kind = light.
|
|
139
|
+
let kind = light.metadata.x;
|
|
126
140
|
if (kind == KIND_POINT) {
|
|
127
141
|
if (flat_2d) {
|
|
128
142
|
return evalPointFlat(
|
|
129
|
-
light.position.xyz, light.
|
|
143
|
+
light.position.xyz, light.colorTimesIntensity.xyz, light.position.w,
|
|
130
144
|
world_pos, base_color,
|
|
131
145
|
);
|
|
132
146
|
}
|
|
133
147
|
#ifdef POINT_SHADOW_AVAILABLE
|
|
134
|
-
let layer = bitcast<i32>(light.
|
|
148
|
+
let layer = bitcast<i32>(light.metadata.y);
|
|
135
149
|
if (layer >= 0) {
|
|
150
|
+
let shadow = shadowParams[layer];
|
|
136
151
|
return evalPointShadowed(
|
|
137
|
-
light.position.xyz, light.
|
|
152
|
+
light.position.xyz, light.colorTimesIntensity.xyz, light.position.w,
|
|
138
153
|
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
139
154
|
layer,
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
shadow.x,
|
|
156
|
+
shadow.y,
|
|
142
157
|
0.005, 0.05,
|
|
143
158
|
);
|
|
144
159
|
}
|
|
145
160
|
#endif
|
|
146
161
|
return evalPoint(
|
|
147
|
-
light.position.xyz, light.
|
|
162
|
+
light.position.xyz, light.colorTimesIntensity.xyz, light.position.w,
|
|
148
163
|
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
149
164
|
);
|
|
150
165
|
}
|
|
151
166
|
if (kind == KIND_SPOT) {
|
|
152
167
|
if (flat_2d) {
|
|
153
168
|
return evalSpotFlat(
|
|
154
|
-
light.position.xyz, light.direction.xyz, light.
|
|
155
|
-
light.
|
|
169
|
+
light.position.xyz, light.direction.xyz, light.colorTimesIntensity.xyz,
|
|
170
|
+
light.colorTimesIntensity.w, light.direction.w, light.position.w,
|
|
156
171
|
world_pos, base_color,
|
|
157
172
|
);
|
|
158
173
|
}
|
|
159
|
-
let
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
174
|
+
let modifierFactors = spotModifierFactors(
|
|
175
|
+
light.position.xyz,
|
|
176
|
+
light.direction.xyz,
|
|
177
|
+
light.direction.w,
|
|
178
|
+
world_pos,
|
|
179
|
+
light.auxiliary.w,
|
|
180
|
+
light.metadata,
|
|
181
|
+
);
|
|
182
|
+
let modifier = vec3<f32>(modifierFactors.x) * modifierFactors.yzw;
|
|
183
|
+
let encoded_tile = bitcast<i32>(light.metadata.y);
|
|
184
|
+
// The optional projector marker shares the non-negative shadow identity
|
|
185
|
+
// lane. The direct-light packer keeps the lower tile bits intact.
|
|
186
|
+
var projector_selected = false;
|
|
187
|
+
var tile : i32 = encoded_tile;
|
|
163
188
|
if (encoded_tile >= 0 && (encoded_tile & PROJECTOR_FLAG) != 0) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
let projector = sampleStandardSpotProjector(view.spotLightViewProj[tile], world_pos);
|
|
167
|
-
return evalSpotShadowed(
|
|
168
|
-
light.position.xyz, light.direction.xyz, light.color.xyz,
|
|
169
|
-
// LightSlot packs cosInner in color.w and cosOuter in direction.w;
|
|
170
|
-
// keep the evaluator's named cone order intact at the decode boundary.
|
|
171
|
-
light.color.w, light.direction.w, light.position.w,
|
|
172
|
-
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
173
|
-
view.spotLightViewProj[tile], tile, 0.005, 0.05,
|
|
174
|
-
bitcast<f32>(light.kind_and_shadow.w),
|
|
175
|
-
bitcast<f32>(light.kind_and_shadow.z),
|
|
176
|
-
) * projector;
|
|
177
|
-
}
|
|
178
|
-
return vec3<f32>(0.0);
|
|
189
|
+
projector_selected = true;
|
|
190
|
+
tile = encoded_tile & TILE_MASK;
|
|
179
191
|
}
|
|
180
|
-
if (
|
|
181
|
-
let tile = encoded_tile;
|
|
192
|
+
if (tile >= 0 && tile < 4) {
|
|
182
193
|
return evalSpotShadowed(
|
|
183
|
-
light.position.xyz, light.direction.xyz, light.
|
|
184
|
-
//
|
|
194
|
+
light.position.xyz, light.direction.xyz, light.colorTimesIntensity.xyz,
|
|
195
|
+
// DirectLightSlot packs cosInner in color.w and cosOuter in direction.w;
|
|
185
196
|
// keep the evaluator's named cone order intact at the decode boundary.
|
|
186
|
-
light.
|
|
187
|
-
|
|
197
|
+
light.colorTimesIntensity.w, light.direction.w, light.position.w,
|
|
198
|
+
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
188
199
|
view.spotLightViewProj[tile], tile, 0.005, 0.05,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
)
|
|
200
|
+
3.0,
|
|
201
|
+
1.0,
|
|
202
|
+
) * modifier
|
|
203
|
+
#ifdef PROJECTOR_AVAILABLE
|
|
204
|
+
* select(
|
|
205
|
+
vec3<f32>(1.0),
|
|
206
|
+
sampleStandardSpotProjector(view.spotLightViewProj[tile], world_pos, light.metadata),
|
|
207
|
+
projector_selected,
|
|
208
|
+
)
|
|
209
|
+
#endif
|
|
210
|
+
;
|
|
192
211
|
}
|
|
193
212
|
return evalSpot(
|
|
194
|
-
light.position.xyz, light.direction.xyz, light.
|
|
195
|
-
light.
|
|
213
|
+
light.position.xyz, light.direction.xyz, light.colorTimesIntensity.xyz,
|
|
214
|
+
light.colorTimesIntensity.w, light.direction.w, light.position.w,
|
|
196
215
|
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
216
|
+
) * modifier;
|
|
217
|
+
}
|
|
218
|
+
if (kind == KIND_RECT_AREA) {
|
|
219
|
+
return evalRectAreaLtcGgx(
|
|
220
|
+
light.position.xyz,
|
|
221
|
+
light.colorTimesIntensity.xyz,
|
|
222
|
+
light.auxiliary.xyz,
|
|
223
|
+
light.direction.xyz,
|
|
224
|
+
light.colorTimesIntensity.w,
|
|
225
|
+
light.direction.w,
|
|
226
|
+
light.position.w,
|
|
227
|
+
world_pos,
|
|
228
|
+
normal,
|
|
229
|
+
view_dir,
|
|
230
|
+
base_color,
|
|
231
|
+
metallic,
|
|
232
|
+
alpha_sq,
|
|
233
|
+
f0,
|
|
197
234
|
);
|
|
198
235
|
}
|
|
199
236
|
return vec3<f32>(0.0);
|
package/src/types.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* no longer carry a bindingLayout sidecar (§Change stance: no v1/v2
|
|
18
18
|
* dual-path, no incremental drop window).
|
|
19
19
|
*/
|
|
20
|
+
import type { MaterialShaderArtifactReceipt } from './material/artifact-types.js';
|
|
20
21
|
export interface MaterialShaderManifestVariant {
|
|
21
22
|
readonly definesKey: string;
|
|
22
23
|
readonly defines: Record<string, boolean>;
|
|
@@ -33,7 +34,7 @@ export interface MaterialShaderManifestVariant {
|
|
|
33
34
|
*
|
|
34
35
|
* For Engine.create() internal use only; AI users never see this type.
|
|
35
36
|
*/
|
|
36
|
-
export interface MaterialShaderManifestEntry {
|
|
37
|
+
export interface MaterialShaderManifestEntry extends Partial<MaterialShaderArtifactReceipt> {
|
|
37
38
|
readonly identifier: string;
|
|
38
39
|
readonly sourcePath: string;
|
|
39
40
|
readonly composedWgsl: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#define_import_path forgeax_view::volume_inject
|
|
2
2
|
#import forgeax_pbr::shadow_pcf::{sample_shadow_2d_kernel}
|
|
3
3
|
#import forgeax_pbr::lighting_attenuation::{projectSpotUv}
|
|
4
|
+
#import forgeax_view::common::{DirectLightSlot}
|
|
4
5
|
// forgeax_pbr::lighting_punctual exports the surface-equivalent
|
|
5
6
|
// evalVolumePoint and evalVolumeSpot contract; this stage consumes their
|
|
6
7
|
// shared projector and shadow visibility facts without a second light owner.
|
|
@@ -29,41 +30,18 @@ struct VolumeParams {
|
|
|
29
30
|
optics : vec4<f32>,
|
|
30
31
|
};
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
invRangeSquared : f32,
|
|
35
|
-
colorTimesIntensity : vec3<f32>,
|
|
36
|
-
cosInner : f32,
|
|
37
|
-
direction : vec3<f32>,
|
|
38
|
-
cosOuter : f32,
|
|
39
|
-
depthBias : f32,
|
|
40
|
-
normalBias : f32,
|
|
41
|
-
pcfKernelSize : f32,
|
|
42
|
-
shadowAtlasTile : i32,
|
|
43
|
-
shadowIntensity : f32,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
struct SpotLightsArray {
|
|
47
|
-
count : u32,
|
|
48
|
-
slots : array<SpotLight, 4>,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
struct PointLight {
|
|
52
|
-
position : vec3<f32>,
|
|
53
|
-
invRangeSquared : f32,
|
|
54
|
-
colorTimesIntensity : vec3<f32>,
|
|
55
|
-
shadowAtlasLayer : i32,
|
|
56
|
-
};
|
|
33
|
+
const DIRECT_LIGHT_METADATA_SENTINEL : u32 = 0xffffffffu;
|
|
34
|
+
const DIRECT_LIGHT_TILE_MASK : u32 = 0x3fffffffu;
|
|
57
35
|
|
|
58
|
-
struct
|
|
59
|
-
|
|
60
|
-
|
|
36
|
+
struct ClusterUniform {
|
|
37
|
+
grid : vec4<u32>,
|
|
38
|
+
near_far_log : vec4<f32>,
|
|
61
39
|
};
|
|
62
40
|
|
|
63
41
|
@group(0) @binding(5) var<uniform> volume_params : VolumeParams;
|
|
64
42
|
@group(0) @binding(6) var volume_froxel : texture_storage_2d_array<rgba8unorm, write>;
|
|
65
|
-
@group(0) @binding(7) var<storage, read>
|
|
66
|
-
@group(0) @binding(8) var<
|
|
43
|
+
@group(0) @binding(7) var<storage, read> light_data : array<DirectLightSlot, 256>;
|
|
44
|
+
@group(0) @binding(8) var<uniform> cluster_uniform : ClusterUniform;
|
|
67
45
|
@group(0) @binding(0) var<uniform> view : View;
|
|
68
46
|
|
|
69
47
|
fn reconstruct_world(uv : vec2<f32>, depth : f32) -> vec3<f32> {
|
|
@@ -161,12 +139,32 @@ fn volume_cascade(world_position : vec3<f32>, view_depth : f32) -> f32 {
|
|
|
161
139
|
);
|
|
162
140
|
}
|
|
163
141
|
|
|
142
|
+
fn shadowAtlasTile(light : DirectLightSlot) -> i32 {
|
|
143
|
+
let encoded = light.metadata.y;
|
|
144
|
+
if (encoded == DIRECT_LIGHT_METADATA_SENTINEL) { return -1; }
|
|
145
|
+
// Projector-selected spots carry the marker in bit 30 and retain the
|
|
146
|
+
// selected shadow tile in the lower 30 bits. A projector-only spot uses
|
|
147
|
+
// tile zero, matching the surface projector contract.
|
|
148
|
+
let tile = encoded & DIRECT_LIGHT_TILE_MASK;
|
|
149
|
+
if (tile >= 4u) { return -1; }
|
|
150
|
+
return i32(tile);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fn selected_cluster_light_slot(value : f32) -> i32 {
|
|
154
|
+
let slot = i32(round(value));
|
|
155
|
+
if (slot < 0) { return -1; }
|
|
156
|
+
let index = u32(slot);
|
|
157
|
+
if (index >= cluster_uniform.grid.w || index >= 256u) { return -1; }
|
|
158
|
+
return slot;
|
|
159
|
+
}
|
|
160
|
+
|
|
164
161
|
fn spot_shadow_visibility_at(world_position : vec3<f32>) -> f32 {
|
|
165
|
-
let slot =
|
|
166
|
-
if (slot
|
|
167
|
-
let light =
|
|
168
|
-
|
|
169
|
-
|
|
162
|
+
let slot = selected_cluster_light_slot(volume_params.light_color.w);
|
|
163
|
+
if (slot < 0) { return 0.0; }
|
|
164
|
+
let light = light_data[u32(slot)];
|
|
165
|
+
let encodedTile = shadowAtlasTile(light);
|
|
166
|
+
if (encodedTile < 0) { return 1.0; }
|
|
167
|
+
let tile = u32(encodedTile);
|
|
170
168
|
let projectorUv = projectSpotUv(view.spotLightViewProj[tile], world_position);
|
|
171
169
|
// The accepted projector tuple is sampled with textureSampleLevel by the
|
|
172
170
|
// surface and volume owners when a projector texture is present.
|
|
@@ -186,7 +184,7 @@ fn spot_shadow_visibility_at(world_position : vec3<f32>) -> f32 {
|
|
|
186
184
|
let shadow_size = vec2<f32>(textureDimensions(shadowMap));
|
|
187
185
|
return sample_shadow_2d_kernel(
|
|
188
186
|
shadowMap, shadowSampler, atlas_uv, 1.0 / shadow_size, projected.z,
|
|
189
|
-
light.
|
|
187
|
+
light.auxiliary.y, light.auxiliary.x, 1.0, 3.0,
|
|
190
188
|
);
|
|
191
189
|
}
|
|
192
190
|
|