@forgeax/engine-shader 0.1.23 → 0.1.25
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 +110 -4
- package/dist/ShaderRegistry.d.ts.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +59 -55
- package/dist/index.mjs.map +1 -1
- package/dist/material/artifact-registry.d.ts +1 -0
- package/dist/material/artifact-registry.d.ts.map +1 -1
- package/dist/material-schemas.d.ts +31 -0
- package/dist/material-schemas.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ShaderRegistry.ts +0 -17
- package/src/__tests__/builtin-texture-sampling-contract.test.ts +10 -1
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +9 -3
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +48 -166
- package/src/__tests__/deferred-lighting-ssao.test.ts +7 -4
- package/src/__tests__/ibl-irradiance.unit.test.ts +9 -0
- package/src/__tests__/lighting-directional.unit.test.ts +36 -0
- package/src/__tests__/lighting-punctual.unit.test.ts +12 -4
- package/src/__tests__/material-builtins.unit.test.ts +21 -3
- package/src/__tests__/material-contract.unit.test.ts +131 -17
- package/src/__tests__/physical-clearcoat.unit.test.ts +65 -0
- package/src/__tests__/probe-lighting-composition.unit.test.ts +3 -2
- package/src/__tests__/shader-manifest-transmission.unit.test.ts +8 -10
- package/src/__tests__/shader.unit.test.ts +52 -87
- package/src/__tests__/spot-cookie-composition.unit.test.ts +2 -1
- package/src/__tests__/standard-pbr-webgl2-interface.unit.test.ts +33 -0
- package/src/__tests__/standard-physical-layer.unit.test.ts +16 -0
- package/src/__tests__/standard-surface-pass-evaluation.integration.test.ts +38 -0
- package/src/__tests__/standard-webgl2-varying-budget.unit.test.ts +17 -0
- package/src/__tests__/surface-lighting-provenance.integration.test.ts +59 -0
- package/src/__tests__/surface-pass-templates.integration.test.ts +28 -0
- package/src/__tests__/surface-v1-negative.unit.test.ts +37 -0
- package/src/__tests__/surface-v1.unit.test.ts +83 -0
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +2 -6
- package/src/__tests__/vertex-color-variant.unit.test.ts +16 -5
- package/src/default-standard-pbr-skin.wgsl +704 -192
- package/src/default-standard-pbr.wgsl +563 -279
- package/src/default_standard_surface.wgsl +88 -0
- package/src/hdrp-cluster-forward.wgsl +225 -0
- package/src/ibl-irradiance.wgsl +43 -8
- package/src/ibl-prefilter.wgsl +13 -3
- package/src/ibl-shared.wgsl +9 -2
- package/src/index.ts +37 -3
- package/src/lighting-directional.wgsl +3 -1
- package/src/lighting-spot-modifiers.wgsl +8 -1
- package/src/lighting-spot-projector.wgsl +31 -0
- package/src/material/artifact-registry.ts +20 -1
- package/src/material/artifact-types.ts +2 -2
- package/src/material/physical/anisotropy.wgsl +26 -0
- package/src/material/physical/clearcoat.wgsl +24 -0
- package/src/material/physical/iridescence.wgsl +23 -0
- package/src/material/physical/sheen.wgsl +17 -0
- package/src/material/standard-physical-layer.wgsl +12 -0
- package/src/material-schemas.ts +65 -30
- package/src/register-default-standard-pbr-skin.ts +2 -2
- package/src/shadow_caster.wgsl +80 -6
- package/src/standard-cluster.wgsl +1 -36
- package/src/surface_v1.wgsl +26 -0
- package/src/unlit.wgsl +12 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#define_import_path forgeax_pbr::anisotropy
|
|
2
|
+
|
|
3
|
+
// Engine-owned anisotropy helpers. The root contract supplies strength and
|
|
4
|
+
// rotation; the map (when present) supplies tangent direction in RG and a
|
|
5
|
+
// strength multiplier in B. Keeping the frame operation here means the
|
|
6
|
+
// Standard evaluator and custom Surface share one deterministic seam.
|
|
7
|
+
fn evaluateAnisotropicNormal(
|
|
8
|
+
normal : vec3<f32>,
|
|
9
|
+
tangent : vec4<f32>,
|
|
10
|
+
strength : f32,
|
|
11
|
+
rotation : f32,
|
|
12
|
+
direction : vec2<f32>,
|
|
13
|
+
) -> vec3<f32> {
|
|
14
|
+
let frameTangent = normalize(tangent.xyz);
|
|
15
|
+
let handedBitangent = normalize(cross(normal, frameTangent)) * select(-1.0, 1.0, tangent.w >= 0.0);
|
|
16
|
+
var mapDirection = vec2<f32>(1.0, 0.0);
|
|
17
|
+
if (dot(direction, direction) >= 1e-6) {
|
|
18
|
+
mapDirection = normalize(direction);
|
|
19
|
+
}
|
|
20
|
+
let mappedTangent = normalize(frameTangent * mapDirection.x + handedBitangent * mapDirection.y);
|
|
21
|
+
let mappedBitangent = normalize(cross(normal, mappedTangent)) * select(-1.0, 1.0, tangent.w >= 0.0);
|
|
22
|
+
let axis = normalize(mappedTangent * cos(rotation) + mappedBitangent * sin(rotation));
|
|
23
|
+
// A bounded bent normal preserves the zero-strength isotropic path while
|
|
24
|
+
// moving only the base specular highlight for non-zero anisotropy.
|
|
25
|
+
return normalize(normal + axis * clamp(strength, -0.999, 0.999) * 0.35);
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#define_import_path forgeax_pbr::clearcoat
|
|
2
|
+
|
|
3
|
+
// Clearcoat is the terminal physical layer. Its Fresnel term owns both the
|
|
4
|
+
// attenuation of lower layers and the weight of the coat radiance.
|
|
5
|
+
// The root supplies clearcoatRoughness and clearcoatNormalScale after their
|
|
6
|
+
// texture projections; this module deliberately owns only layer energy.
|
|
7
|
+
fn evaluateClearcoatFresnel(viewCosine : f32, clearcoatFactor : f32) -> f32 {
|
|
8
|
+
let cosine = clamp(viewCosine, 0.0, 1.0);
|
|
9
|
+
let oneMinusCosine = 1.0 - cosine;
|
|
10
|
+
let dielectricFresnel = 0.04 + 0.96 * oneMinusCosine * oneMinusCosine *
|
|
11
|
+
oneMinusCosine * oneMinusCosine * oneMinusCosine;
|
|
12
|
+
return clamp(clearcoatFactor, 0.0, 1.0) * dielectricFresnel;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
fn evaluateClearcoatLayer(
|
|
16
|
+
baseRadiance : vec3<f32>,
|
|
17
|
+
coatRadiance : vec3<f32>,
|
|
18
|
+
viewCosine : f32,
|
|
19
|
+
clearcoatFactor : f32,
|
|
20
|
+
) -> vec3<f32> {
|
|
21
|
+
let fresnel = evaluateClearcoatFresnel(viewCosine, clearcoatFactor);
|
|
22
|
+
let attenuatedBase = baseRadiance * (1.0 - fresnel);
|
|
23
|
+
return attenuatedBase + coatRadiance * fresnel;
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#define_import_path forgeax_pbr::iridescence
|
|
2
|
+
|
|
3
|
+
// Bounded RGB thin-film Fresnel approximation. Thickness is deliberately
|
|
4
|
+
// not clamped to min/max order: the glTF contract permits inverted bounds and
|
|
5
|
+
// the caller supplies the authored interval verbatim.
|
|
6
|
+
fn evaluateIridescenceFresnel(
|
|
7
|
+
baseF0 : vec3<f32>,
|
|
8
|
+
strength : f32,
|
|
9
|
+
filmIor : f32,
|
|
10
|
+
thicknessNanometres : f32,
|
|
11
|
+
) -> vec3<f32> {
|
|
12
|
+
let safeStrength = clamp(strength, 0.0, 1.0);
|
|
13
|
+
let safeIor = max(filmIor, 1.0);
|
|
14
|
+
let base = clamp((safeIor - 1.0) / (safeIor + 1.0), 0.0, 1.0);
|
|
15
|
+
let phase = thicknessNanometres * 0.018;
|
|
16
|
+
let spectral = vec3<f32>(
|
|
17
|
+
0.5 + 0.5 * cos(phase),
|
|
18
|
+
0.5 + 0.5 * cos(phase + 2.0943952),
|
|
19
|
+
0.5 + 0.5 * cos(phase + 4.1887903),
|
|
20
|
+
);
|
|
21
|
+
let film = clamp(vec3<f32>(base) * spectral, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
22
|
+
return clamp(mix(baseF0, film, safeStrength), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#define_import_path forgeax_pbr::sheen
|
|
2
|
+
|
|
3
|
+
// Charlie-like grazing lobe approximation with explicit energy compensation.
|
|
4
|
+
// It is evaluated below clearcoat, so the caller attenuates this result when
|
|
5
|
+
// the top coat Fresnel is applied.
|
|
6
|
+
fn evaluateSheenLayer(
|
|
7
|
+
baseRadiance : vec3<f32>,
|
|
8
|
+
sheenColor : vec3<f32>,
|
|
9
|
+
sheenRoughness : f32,
|
|
10
|
+
viewCosine : f32,
|
|
11
|
+
) -> vec3<f32> {
|
|
12
|
+
let roughness = clamp(sheenRoughness, 0.04, 1.0);
|
|
13
|
+
let grazing = pow(1.0 - clamp(viewCosine, 0.0, 1.0), 2.0);
|
|
14
|
+
let lobe = grazing * (1.0 - 0.5 * roughness);
|
|
15
|
+
let energy = clamp(max(max(sheenColor.r, sheenColor.g), sheenColor.b), 0.0, 1.0);
|
|
16
|
+
return baseRadiance * (1.0 - energy * lobe * 0.5) + sheenColor * lobe;
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#define_import_path forgeax_material::standard_physical_layer
|
|
2
|
+
|
|
3
|
+
// Engine-owned physical layer facts are selected from the root StandardLayerPlan.
|
|
4
|
+
// Surface modules only produce base SurfaceData and never own this interface.
|
|
5
|
+
// The Standard template owns the facts struct; this module supplies the one
|
|
6
|
+
// physical evaluator body when the clearcoat plan is present.
|
|
7
|
+
fn evaluate_standard_physical_layer() -> StandardPhysicalLayerFacts {
|
|
8
|
+
return StandardPhysicalLayerFacts(
|
|
9
|
+
clamp(material.clearcoat, 0.0, 1.0),
|
|
10
|
+
clamp(material.clearcoatRoughness, 0.04, 1.0),
|
|
11
|
+
);
|
|
12
|
+
}
|
package/src/material-schemas.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ParamSchemaEntry } from '@forgeax/engine-types';
|
|
2
|
+
import {
|
|
3
|
+
STANDARD_MATERIAL_PARAM_SCHEMA,
|
|
4
|
+
STANDARD_PHYSICAL_PARAMETER_NAMES,
|
|
5
|
+
} from '@forgeax/engine-types';
|
|
2
6
|
import {
|
|
3
7
|
createStandardPbrArtifactReceipt,
|
|
4
8
|
type MaterialShaderArtifactReceipt,
|
|
@@ -7,36 +11,67 @@ import {
|
|
|
7
11
|
export const STANDARD_PBR_ALPHA_CUTOFF_DEFAULT = 0;
|
|
8
12
|
|
|
9
13
|
/** Shared material contract for the standard PBR and skinned PBR shaders. */
|
|
10
|
-
export const DEFAULT_STANDARD_PBR_PARAM_SCHEMA
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
]
|
|
14
|
+
export const DEFAULT_STANDARD_PBR_PARAM_SCHEMA = STANDARD_MATERIAL_PARAM_SCHEMA;
|
|
15
|
+
|
|
16
|
+
export type { StandardPhysicalTextureField } from '@forgeax/engine-types';
|
|
17
|
+
/**
|
|
18
|
+
* Backward-compatible export name for callers that need the physical
|
|
19
|
+
* projection. Every entry is selected from the Standard root schema above;
|
|
20
|
+
* no independent defaults or binding inventory can drift from it.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Stable physical texture injection order. The order is part of the
|
|
24
|
+
* Standard template's resource ABI and is derived from the root schema by
|
|
25
|
+
* `standardPhysicalTextureFields`; render/cook must not maintain another
|
|
26
|
+
* field inventory.
|
|
27
|
+
*/
|
|
28
|
+
export {
|
|
29
|
+
STANDARD_PHYSICAL_TEXTURE_FIELDS,
|
|
30
|
+
standardPhysicalTextureFields,
|
|
31
|
+
} from '@forgeax/engine-types';
|
|
32
|
+
|
|
33
|
+
export const STANDARD_PHYSICAL_LAYER_PARAM_SCHEMA: readonly ParamSchemaEntry[] =
|
|
34
|
+
DEFAULT_STANDARD_PBR_PARAM_SCHEMA.filter((entry) =>
|
|
35
|
+
STANDARD_PHYSICAL_PARAMETER_NAMES.has(entry.name),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Canonical static Standard entry. It deliberately contains only the base
|
|
40
|
+
* contract (including the specular extension) so a base-only material keeps
|
|
41
|
+
* its Deferred path and carries no second-stage physical resources.
|
|
42
|
+
*/
|
|
43
|
+
export const STANDARD_BASE_PARAM_SCHEMA: readonly ParamSchemaEntry[] =
|
|
44
|
+
DEFAULT_STANDARD_PBR_PARAM_SCHEMA.filter(
|
|
45
|
+
(entry) =>
|
|
46
|
+
!STANDARD_PHYSICAL_PARAMETER_NAMES.has(entry.name) &&
|
|
47
|
+
![
|
|
48
|
+
'transmission',
|
|
49
|
+
'thickness',
|
|
50
|
+
'attenuationColor',
|
|
51
|
+
'attenuationDistance',
|
|
52
|
+
'transmissionTexture',
|
|
53
|
+
'thicknessTexture',
|
|
54
|
+
].includes(entry.name),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Canonical boot-time user region for the shared Standard material BGL.
|
|
59
|
+
*
|
|
60
|
+
* It is still a projection of the root schema, but unlike the author-facing
|
|
61
|
+
* base-only projection it retains the pre-existing transmission texture pair.
|
|
62
|
+
* The template reserves those two pairs before the IBL/backdrop injection so
|
|
63
|
+
* a base-only shader and a transmissive shader can share one boot layout;
|
|
64
|
+
* physical texture pairs are never included here and are appended only for a
|
|
65
|
+
* root contract that declares them.
|
|
66
|
+
*/
|
|
67
|
+
export const STANDARD_PIPELINE_PARAM_SCHEMA: readonly ParamSchemaEntry[] =
|
|
68
|
+
DEFAULT_STANDARD_PBR_PARAM_SCHEMA.filter(
|
|
69
|
+
// The canonical Standard UBO keeps every numeric root coordinate,
|
|
70
|
+
// including the physical-layer tail. Physical map pairs are engine-owned
|
|
71
|
+
// resources and are injected after the stable user region.
|
|
72
|
+
(entry) =>
|
|
73
|
+
!(STANDARD_PHYSICAL_PARAMETER_NAMES.has(entry.name) && entry.type.startsWith('texture')),
|
|
74
|
+
);
|
|
40
75
|
|
|
41
76
|
/** The single producer receipt shared by direct and scene-index Standard PBR. */
|
|
42
77
|
export const STANDARD_PBR_ARTIFACT_RECEIPT: MaterialShaderArtifactReceipt =
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// - R-10: paramSchema reuses default-standard-pbr schema (same params)
|
|
14
14
|
|
|
15
15
|
import type { ShaderRegistry } from './index.js';
|
|
16
|
-
import {
|
|
16
|
+
import { STANDARD_PIPELINE_PARAM_SCHEMA } from './material-schemas.js';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* paramSchema for forgeax::pbr-skin — mirrors default-standard-pbr 8 fields.
|
|
@@ -69,6 +69,6 @@ export function registerDefaultStandardPbrSkin(
|
|
|
69
69
|
void caps;
|
|
70
70
|
registry.installMaterialArtifact(RESERVED_ID, {
|
|
71
71
|
source: composedWgsl,
|
|
72
|
-
paramSchema:
|
|
72
|
+
paramSchema: STANDARD_PIPELINE_PARAM_SCHEMA,
|
|
73
73
|
});
|
|
74
74
|
}
|
package/src/shadow_caster.wgsl
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
#pragma variant_axis STORAGE_BUFFER_AVAILABLE
|
|
2
2
|
#pragma variant_axis SKINNING_DISABLED
|
|
3
|
+
#pragma material_slot surface
|
|
4
|
+
#define_import_path forgeax::default-shadow-caster
|
|
5
|
+
#import forgeax_material::slot::surface::{evaluate_surface}
|
|
6
|
+
#import forgeax_material::surface_v1::{SurfaceInput, SurfaceData}
|
|
7
|
+
#import forgeax_material::parameters::{material}
|
|
3
8
|
|
|
4
9
|
// @forgeax/engine-shader shadow_caster.wgsl
|
|
5
10
|
// feat-20260520-directional-light-shadow-mapping M1c / w9 (D-9 / AC-09):
|
|
6
|
-
//
|
|
7
|
-
//
|
|
11
|
+
// depth pass for directional shadow map. The fragment stage evaluates the
|
|
12
|
+
// selected Standard Surface so opacity/alpha-clip stays identical to Forward
|
|
13
|
+
// and Deferred (depth32float is still the only render target).
|
|
8
14
|
//
|
|
9
15
|
// feat-20260613-csm-cascaded-shadow-maps M5 / w28: per-cascade
|
|
10
16
|
// lightViewProj selection. Each cascade pass writes a different
|
|
@@ -29,12 +35,25 @@
|
|
|
29
35
|
|
|
30
36
|
struct VsInput {
|
|
31
37
|
@location(0) position : vec3<f32>,
|
|
38
|
+
@location(1) normal : vec3<f32>,
|
|
39
|
+
@location(2) uv : vec2<f32>,
|
|
40
|
+
@location(3) tangent : vec4<f32>,
|
|
32
41
|
#if SKINNING_DISABLED == false
|
|
33
42
|
@location(4) skinIndex : vec4<u32>,
|
|
34
43
|
@location(5) skinWeight : vec4<f32>,
|
|
35
44
|
#endif
|
|
36
45
|
};
|
|
37
46
|
|
|
47
|
+
struct VsOut {
|
|
48
|
+
@builtin(position) clip : vec4<f32>,
|
|
49
|
+
@location(0) positionOS : vec3<f32>,
|
|
50
|
+
@location(1) positionWS : vec3<f32>,
|
|
51
|
+
@location(2) normalWS : vec3<f32>,
|
|
52
|
+
@location(3) tangentWS : vec4<f32>,
|
|
53
|
+
@location(4) surfaceUv : vec2<f32>,
|
|
54
|
+
@location(5) vertexColor : vec4<f32>,
|
|
55
|
+
};
|
|
56
|
+
|
|
38
57
|
#if SKINNING_DISABLED == false
|
|
39
58
|
#if STORAGE_BUFFER_AVAILABLE == true
|
|
40
59
|
@group(2) @binding(1) var<storage, read> palette : array<mat4x4<f32>>;
|
|
@@ -53,7 +72,7 @@ fn _cascadeLightViewProj(layer : u32) -> mat4x4<f32> {
|
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
@vertex
|
|
56
|
-
fn vs_main(in : VsInput, @builtin(instance_index) idx : u32) ->
|
|
75
|
+
fn vs_main(in : VsInput, @builtin(instance_index) idx : u32) -> VsOut {
|
|
57
76
|
#if SKINNING_DISABLED == false
|
|
58
77
|
let skinMatrix = palette[in.skinIndex.x] * in.skinWeight.x +
|
|
59
78
|
palette[in.skinIndex.y] * in.skinWeight.y +
|
|
@@ -63,8 +82,14 @@ fn vs_main(in : VsInput, @builtin(instance_index) idx : u32) -> @builtin(positio
|
|
|
63
82
|
// produce world-space positions. Applying meshes[0].worldFromLocal again
|
|
64
83
|
// would double-transform a parented skinned entity.
|
|
65
84
|
let worldPos = skinMatrix * vec4<f32>(in.position, 1.0);
|
|
85
|
+
let worldNormal = normalize((skinMatrix * vec4<f32>(in.normal, 0.0)).xyz);
|
|
86
|
+
let worldTangent = normalize((skinMatrix * vec4<f32>(in.tangent.xyz, 0.0)).xyz);
|
|
66
87
|
#else
|
|
67
|
-
let
|
|
88
|
+
let instanceLocal = instances[idx].localFromInstance;
|
|
89
|
+
let worldMatrix = meshes[0].worldFromLocal * instanceLocal;
|
|
90
|
+
let worldPos = worldMatrix * vec4<f32>(in.position, 1.0);
|
|
91
|
+
let worldNormal = normalize((worldMatrix * vec4<f32>(in.normal, 0.0)).xyz);
|
|
92
|
+
let worldTangent = normalize((worldMatrix * vec4<f32>(in.tangent.xyz, 0.0)).xyz);
|
|
68
93
|
#endif
|
|
69
94
|
// feat-20260625-spot-light-shadow-mapping M2 / w10 (D-1): spot shadow passes
|
|
70
95
|
// set `isSpot = 1u` and write their perspective matrix into
|
|
@@ -72,8 +97,57 @@ fn vs_main(in : VsInput, @builtin(instance_index) idx : u32) -> @builtin(positio
|
|
|
72
97
|
// `view.lightViewProj_A..D` via `index`. Routing on the discriminant keeps
|
|
73
98
|
// the spot matrix out of the directional View UBO (no same-frame contention).
|
|
74
99
|
if (shadowCasterCascade.isSpot == 1u) {
|
|
75
|
-
|
|
100
|
+
var out : VsOut;
|
|
101
|
+
out.clip = shadowCasterCascade.spotLightViewProj * worldPos;
|
|
102
|
+
out.positionOS = in.position;
|
|
103
|
+
out.positionWS = worldPos.xyz;
|
|
104
|
+
out.normalWS = worldNormal;
|
|
105
|
+
out.tangentWS = vec4<f32>(worldTangent, in.tangent.w);
|
|
106
|
+
out.surfaceUv = in.uv;
|
|
107
|
+
out.vertexColor = vec4<f32>(1.0);
|
|
108
|
+
return out;
|
|
76
109
|
}
|
|
77
110
|
let lvp = _cascadeLightViewProj(shadowCasterCascade.index);
|
|
78
|
-
|
|
111
|
+
var out : VsOut;
|
|
112
|
+
out.clip = lvp * worldPos;
|
|
113
|
+
out.positionOS = in.position;
|
|
114
|
+
out.positionWS = worldPos.xyz;
|
|
115
|
+
out.normalWS = worldNormal;
|
|
116
|
+
out.tangentWS = vec4<f32>(worldTangent, in.tangent.w);
|
|
117
|
+
out.surfaceUv = in.uv;
|
|
118
|
+
out.vertexColor = vec4<f32>(1.0);
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn evaluateShadowSurface(in : VsOut, frontFacing : bool) -> SurfaceData {
|
|
123
|
+
let viewDirectionWS = normalize(view.cameraPos - in.positionWS);
|
|
124
|
+
return evaluate_surface(SurfaceInput(
|
|
125
|
+
in.positionOS,
|
|
126
|
+
in.positionWS,
|
|
127
|
+
in.normalWS,
|
|
128
|
+
in.tangentWS,
|
|
129
|
+
viewDirectionWS,
|
|
130
|
+
in.surfaceUv,
|
|
131
|
+
in.surfaceUv,
|
|
132
|
+
in.vertexColor,
|
|
133
|
+
frontFacing,
|
|
134
|
+
));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fn alphaTestShadowSurface(surface : SurfaceData) {
|
|
138
|
+
if (surface.alphaClipThreshold > 0.0 && surface.opacity <= surface.alphaClipThreshold) {
|
|
139
|
+
discard;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// The depth-only path evaluates the selected Surface so alpha-clip ownership
|
|
144
|
+
// remains shared with Forward and Deferred without mutating vertex data.
|
|
145
|
+
@fragment
|
|
146
|
+
fn fs_shadow(in : VsOut, @builtin(front_facing) frontFacing : bool) {
|
|
147
|
+
alphaTestShadowSurface(evaluateShadowSurface(in, frontFacing));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@fragment
|
|
151
|
+
fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) {
|
|
152
|
+
alphaTestShadowSurface(evaluateShadowSurface(in, frontFacing));
|
|
79
153
|
}
|
|
@@ -9,18 +9,11 @@
|
|
|
9
9
|
#define_import_path forgeax_standard::cluster
|
|
10
10
|
|
|
11
11
|
#import forgeax_pbr::lighting_punctual::{evalPoint, evalPointFlat, evalSpot, evalSpotFlat, evalSpotShadowed}
|
|
12
|
-
#import forgeax_pbr::lighting_attenuation::{projectSpotUv}
|
|
13
12
|
#import forgeax_pbr::lighting_spot_modifiers::{spotModifierFactors}
|
|
14
13
|
#import forgeax_pbr::lighting_rect_area::{evalRectAreaLtcGgx}
|
|
15
14
|
#import forgeax_view::common::{view}
|
|
16
15
|
#ifdef PROJECTOR_AVAILABLE
|
|
17
|
-
#
|
|
18
|
-
#import forgeax_view::common::{spotModifierSampler, cookieTexture}
|
|
19
|
-
#else
|
|
20
|
-
#ifndef EXTENDED_LIGHTING_AVAILABLE
|
|
21
|
-
#import forgeax_view::common::{projectorTexture, projectorSampler}
|
|
22
|
-
#endif
|
|
23
|
-
#endif
|
|
16
|
+
#import forgeax_pbr::lighting_spot_projector::{sampleStandardSpotProjector}
|
|
24
17
|
#endif
|
|
25
18
|
#ifdef POINT_SHADOW_AVAILABLE
|
|
26
19
|
#import forgeax_pbr::lighting_punctual::{evalPointShadowed}
|
|
@@ -95,34 +88,6 @@ fn ndc_position_to_cluster(
|
|
|
95
88
|
return vec3(cx, cy, cz);
|
|
96
89
|
}
|
|
97
90
|
|
|
98
|
-
// Surface SpotLight projector sampling is owned by the same clustered light
|
|
99
|
-
// evaluator as the analytic cone/range contribution. The host marks the
|
|
100
|
-
// selected projector in the DirectLightSlot flag and writes its matrix to the
|
|
101
|
-
// corresponding View lane (lane 0 for projector-only); no second
|
|
102
|
-
// material-local sampling loop or resource owner is allowed.
|
|
103
|
-
fn sampleStandardSpotProjector(
|
|
104
|
-
lightViewProj : mat4x4<f32>,
|
|
105
|
-
world_pos : vec3<f32>,
|
|
106
|
-
metadata : vec4<u32>,
|
|
107
|
-
) -> vec3<f32> {
|
|
108
|
-
#ifdef PROJECTOR_AVAILABLE
|
|
109
|
-
let uv = projectSpotUv(lightViewProj, world_pos);
|
|
110
|
-
if (!(uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0)) {
|
|
111
|
-
return vec3<f32>(1.0);
|
|
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
|
|
119
|
-
return textureSampleLevel(projectorTexture, projectorSampler, uv, 0.0).rgb;
|
|
120
|
-
#endif
|
|
121
|
-
#else
|
|
122
|
-
return vec3<f32>(1.0);
|
|
123
|
-
#endif
|
|
124
|
-
}
|
|
125
|
-
|
|
126
91
|
// Decode one kind first, then delegate the complete evaluation to the shared
|
|
127
92
|
// punctual owner. Unknown kinds are explicitly zero contribution.
|
|
128
93
|
fn evaluate_cluster_light(
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#define_import_path forgeax_material::surface_v1
|
|
2
|
+
|
|
3
|
+
// Engine-owned input ABI for authored Standard material surfaces.
|
|
4
|
+
struct SurfaceInput {
|
|
5
|
+
positionOS : vec3<f32>,
|
|
6
|
+
positionWS : vec3<f32>,
|
|
7
|
+
geometricNormalWS : vec3<f32>,
|
|
8
|
+
tangentWS : vec4<f32>,
|
|
9
|
+
viewDirectionWS : vec3<f32>,
|
|
10
|
+
uv0 : vec2<f32>,
|
|
11
|
+
uv1 : vec2<f32>,
|
|
12
|
+
vertexColor : vec4<f32>,
|
|
13
|
+
frontFacing : bool,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Surface output is consumed by the Standard BRDF and pass family.
|
|
17
|
+
struct SurfaceData {
|
|
18
|
+
baseColor : vec3<f32>,
|
|
19
|
+
normalWS : vec3<f32>,
|
|
20
|
+
metallic : f32,
|
|
21
|
+
roughness : f32,
|
|
22
|
+
emissive : vec3<f32>,
|
|
23
|
+
occlusion : f32,
|
|
24
|
+
opacity : f32,
|
|
25
|
+
alphaClipThreshold : f32,
|
|
26
|
+
};
|
package/src/unlit.wgsl
CHANGED
|
@@ -111,6 +111,18 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
111
111
|
return vec4<f32>(material.baseColor.rgb * texSample.rgb * vertexColor.rgb, alpha);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// Depth-only shadow variant. Keep alpha clipping identical to the color path,
|
|
115
|
+
// but return no color target because the shadow pass has a depth attachment only.
|
|
116
|
+
@fragment
|
|
117
|
+
fn fs_shadow(in : VsOut) {
|
|
118
|
+
let texSample = sampleMaterialTextureLinear(baseColorTexture, baseColorSampler, in.uv, material.baseColorTextureCoordinatesMetadata.zw);
|
|
119
|
+
let vertexColor = materialVertexColor(in);
|
|
120
|
+
let alpha = material.baseColor.a * texSample.a * vertexColor.a;
|
|
121
|
+
if (material.alphaCutoff > 0.0 && alpha < material.alphaCutoff) {
|
|
122
|
+
discard;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
114
126
|
struct TemporalVsOut {
|
|
115
127
|
@builtin(position) clip : vec4<f32>,
|
|
116
128
|
@location(0) uv : vec2<f32>,
|