@forgeax/engine-shader 0.1.27 → 0.1.29
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/NOTICE +35 -0
- package/README.md +39 -0
- package/dist/ShaderRegistry.d.ts +8 -0
- package/dist/ShaderRegistry.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +312 -71
- package/dist/index.mjs.map +1 -1
- package/dist/material/artifact-types.d.ts +12 -2
- 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/package.json +5 -4
- package/src/ShaderRegistry.ts +10 -0
- package/src/__tests__/auto-exposure-graph.unit.test.ts +59 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +30 -15
- package/src/__tests__/builtin-texture-sampling-contract.test.ts +1 -1
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +2 -1
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +167 -6
- package/src/__tests__/direct-light-layout.unit.test.ts +1 -1
- package/src/__tests__/lighting-punctual.unit.test.ts +29 -1
- package/src/__tests__/ltc-provenance.unit.test.ts +13 -2
- package/src/__tests__/material-builtins.unit.test.ts +1 -0
- package/src/__tests__/material-contract.unit.test.ts +42 -9
- package/src/__tests__/material-derived-builtins.integration.test.ts +18 -2
- package/src/__tests__/probe-lighting-composition.unit.test.ts +1 -1
- package/src/__tests__/reflection-probe-sampling.unit.test.ts +23 -2
- package/src/__tests__/scene-temporal.unit.test.ts +10 -0
- package/src/__tests__/shader.unit.test.ts +1 -0
- package/src/__tests__/sprite-variants.unit.test.ts +1 -0
- package/src/__tests__/ssr-artifact.unit.test.ts +141 -0
- package/src/__tests__/ssr-bgl.integration.test.ts +185 -0
- package/src/__tests__/standard-output-domain.unit.test.ts +36 -0
- package/src/__tests__/standard-pbr-artifact-receipt.unit.test.ts +93 -0
- package/src/__tests__/standard-surface-pass-evaluation.integration.test.ts +16 -4
- package/src/__tests__/surface-v1.unit.test.ts +6 -0
- package/src/__tests__/taa-resolve.unit.test.ts +20 -9
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +6 -1
- package/src/__tests__/transparent-pbr.unit.test.ts +1 -1
- package/src/__tests__/vertex-color-variant.unit.test.ts +4 -1
- package/src/atmosphere-background.wgsl +3 -3
- package/src/auto-exposure-meter.wgsl +179 -0
- package/src/color-lut.wgsl +17 -0
- package/src/common.wgsl +7 -5
- package/src/default-standard-pbr-skin.wgsl +142 -40
- package/src/default-standard-pbr.wgsl +198 -82
- package/src/default_standard_surface.wgsl +56 -19
- package/src/fxaa.wgsl +9 -5
- package/src/ibl-sampling.wgsl +30 -6
- package/src/index.ts +189 -0
- package/src/lighting-punctual.wgsl +5 -6
- package/src/material/artifact-types.ts +109 -63
- package/src/material-schemas.ts +84 -8
- package/src/output-encoding.wgsl +10 -0
- package/src/pbr-temporal.wgsl +9 -1
- package/src/scene-temporal.wgsl +2 -0
- package/src/shadow-pcf.wgsl +6 -8
- package/src/shadow-surface.wgsl +16 -0
- package/src/shadow_caster.wgsl +99 -61
- package/src/sprite-lit.wgsl +4 -4
- package/src/sprite.wgsl +3 -3
- package/src/ssr-compose.wgsl +129 -0
- package/src/ssr-hiz-reduce.wgsl +53 -0
- package/src/ssr-hiz.wgsl +66 -0
- package/src/ssr-temporal.wgsl +305 -0
- package/src/ssr-trace.wgsl +784 -0
- package/src/standard-cluster.wgsl +6 -4
- package/src/standard-surface.wgsl +696 -0
- package/src/surface_v1.wgsl +6 -0
- package/src/taa-resolve.wgsl +222 -28
- package/src/tbn.wgsl +1 -1
- package/src/tonemap.wgsl +37 -18
- package/src/unlit.wgsl +3 -3
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
// interface remains the source of the scalar and texture values; the
|
|
7
7
|
// Standard template owns physical-layer selection and lighting.
|
|
8
8
|
fn surfaceUv(input : SurfaceInput, transform : vec4<f32>, metadata : vec4<f32>) -> vec2<f32> {
|
|
9
|
-
|
|
9
|
+
var source = input.uv0;
|
|
10
|
+
if (metadata.x >= 1.0) { source = input.uv1; }
|
|
11
|
+
if (metadata.x >= 2.0) { source = input.uv2; }
|
|
12
|
+
if (metadata.x >= 3.0) { source = input.uv3; }
|
|
13
|
+
if (metadata.x >= 4.0) { source = input.uv4; }
|
|
14
|
+
if (metadata.x >= 5.0) { source = input.uv5; }
|
|
15
|
+
if (metadata.x >= 6.0) { source = input.uv6; }
|
|
16
|
+
if (metadata.x >= 7.0) { source = input.uv7; }
|
|
10
17
|
let scaled = source * transform.zw;
|
|
11
18
|
let c = cos(metadata.y);
|
|
12
19
|
let s = sin(metadata.y);
|
|
@@ -31,58 +38,88 @@ fn surfaceNormal(input : SurfaceInput, encoded : vec4<f32>, normalScale : f32) -
|
|
|
31
38
|
return normalize(tangent * tangentXY.x + bitangent * tangentXY.y + geometric * tangentZ);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
fn
|
|
35
|
-
|
|
41
|
+
fn evaluate_standard_surface(input : SurfaceInput, materialValue : MaterialParameters) -> SurfaceData {
|
|
42
|
+
var baseSample = vec4<f32>(1.0);
|
|
43
|
+
#ifdef BASE_COLOR_TEXTURE_AVAILABLE
|
|
44
|
+
if (standardUsesBaseColorTexture()) {
|
|
45
|
+
baseSample = textureSample(
|
|
36
46
|
baseColorTexture,
|
|
37
47
|
baseColorTexture_sampler,
|
|
38
|
-
surfaceUv(input,
|
|
48
|
+
surfaceUv(input, materialValue.baseColorTextureCoordinatesTransform, materialValue.baseColorTextureCoordinatesMetadata) * materialValue.baseColorTextureCoordinatesMetadata.zw,
|
|
39
49
|
);
|
|
40
|
-
|
|
50
|
+
}
|
|
51
|
+
#endif
|
|
52
|
+
var metallicRoughnessSample = vec4<f32>(1.0);
|
|
53
|
+
#ifdef METALLIC_ROUGHNESS_TEXTURE_AVAILABLE
|
|
54
|
+
if (standardUsesMetallicRoughnessTexture()) {
|
|
55
|
+
metallicRoughnessSample = textureSample(
|
|
41
56
|
metallicRoughnessTexture,
|
|
42
57
|
metallicRoughnessTexture_sampler,
|
|
43
|
-
surfaceUv(input,
|
|
58
|
+
surfaceUv(input, materialValue.metallicRoughnessTextureCoordinatesTransform, materialValue.metallicRoughnessTextureCoordinatesMetadata) * materialValue.metallicRoughnessTextureCoordinatesMetadata.zw,
|
|
44
59
|
);
|
|
60
|
+
}
|
|
61
|
+
#endif
|
|
62
|
+
var normal = normalize(input.geometricNormalWS);
|
|
63
|
+
#ifdef NORMAL_TEXTURE_AVAILABLE
|
|
64
|
+
if (standardUsesNormalTexture()) {
|
|
45
65
|
let normalSample = textureSample(
|
|
46
66
|
normalTexture,
|
|
47
67
|
normalTexture_sampler,
|
|
48
|
-
surfaceUv(input,
|
|
68
|
+
surfaceUv(input, materialValue.normalTextureCoordinatesTransform, materialValue.normalTextureCoordinatesMetadata) * materialValue.normalTextureCoordinatesMetadata.zw,
|
|
49
69
|
);
|
|
50
|
-
|
|
70
|
+
normal = surfaceNormal(input, normalSample, materialValue.normalScale);
|
|
71
|
+
}
|
|
72
|
+
#endif
|
|
73
|
+
var emissiveSample = vec4<f32>(1.0);
|
|
74
|
+
#ifdef EMISSIVE_TEXTURE_AVAILABLE
|
|
75
|
+
if (standardUsesEmissiveTexture()) {
|
|
76
|
+
emissiveSample = textureSample(
|
|
51
77
|
emissiveTexture,
|
|
52
78
|
emissiveTexture_sampler,
|
|
53
|
-
surfaceUv(input,
|
|
79
|
+
surfaceUv(input, materialValue.emissiveTextureCoordinatesTransform, materialValue.emissiveTextureCoordinatesMetadata) * materialValue.emissiveTextureCoordinatesMetadata.zw,
|
|
54
80
|
);
|
|
55
|
-
|
|
81
|
+
}
|
|
82
|
+
#endif
|
|
83
|
+
var occlusionSample = vec4<f32>(1.0);
|
|
84
|
+
#ifdef OCCLUSION_TEXTURE_AVAILABLE
|
|
85
|
+
if (standardUsesOcclusionTexture()) {
|
|
86
|
+
occlusionSample = textureSample(
|
|
56
87
|
occlusionTexture,
|
|
57
88
|
occlusionTexture_sampler,
|
|
58
|
-
surfaceUv(input,
|
|
89
|
+
surfaceUv(input, materialValue.occlusionTextureCoordinatesTransform, materialValue.occlusionTextureCoordinatesMetadata) * materialValue.occlusionTextureCoordinatesMetadata.zw,
|
|
59
90
|
);
|
|
91
|
+
}
|
|
92
|
+
#endif
|
|
60
93
|
let vertexColor = input.vertexColor;
|
|
61
|
-
let baseColor =
|
|
94
|
+
let baseColor = materialValue.baseColor.rgb * baseSample.rgb * vertexColor.rgb;
|
|
62
95
|
let metallic = clamp(
|
|
63
|
-
|
|
96
|
+
materialValue.metallic * surfaceChannel(metallicRoughnessSample, u32(materialValue.metallicChannel)),
|
|
64
97
|
0.0,
|
|
65
98
|
1.0,
|
|
66
99
|
);
|
|
67
100
|
let roughness = clamp(
|
|
68
|
-
|
|
101
|
+
materialValue.roughness * surfaceChannel(metallicRoughnessSample, u32(materialValue.roughnessChannel)),
|
|
69
102
|
0.04,
|
|
70
103
|
1.0,
|
|
71
104
|
);
|
|
72
|
-
let emissive =
|
|
105
|
+
let emissive = materialValue.emissive * materialValue.emissiveIntensity * emissiveSample.rgb;
|
|
73
106
|
let occlusion = clamp(
|
|
74
|
-
1.0 + (occlusionSample.r - 1.0) *
|
|
107
|
+
1.0 + (occlusionSample.r - 1.0) * materialValue.occlusionStrength,
|
|
75
108
|
0.0,
|
|
76
109
|
1.0,
|
|
77
110
|
);
|
|
78
111
|
return SurfaceData(
|
|
79
112
|
baseColor,
|
|
80
|
-
|
|
113
|
+
normal,
|
|
81
114
|
metallic,
|
|
82
115
|
roughness,
|
|
83
116
|
emissive,
|
|
84
117
|
occlusion,
|
|
85
|
-
clamp(
|
|
86
|
-
clamp(
|
|
118
|
+
clamp(materialValue.baseColor.a * baseSample.a * vertexColor.a, 0.0, 1.0),
|
|
119
|
+
clamp(materialValue.alphaCutoff, 0.0, 1.0),
|
|
87
120
|
);
|
|
88
121
|
}
|
|
122
|
+
|
|
123
|
+
fn evaluate_surface(input : SurfaceInput) -> SurfaceData {
|
|
124
|
+
return evaluate_standard_surface(input, material);
|
|
125
|
+
}
|
package/src/fxaa.wgsl
CHANGED
|
@@ -33,13 +33,15 @@
|
|
|
33
33
|
// holes in shaded surfaces -- the bug this rewrite fixes.
|
|
34
34
|
//
|
|
35
35
|
// Bindings (group 0):
|
|
36
|
-
// @binding(0) screenTexture : texture_2d<f32> -- sampled
|
|
36
|
+
// @binding(0) screenTexture : texture_2d<f32> -- sampled pass-domain input
|
|
37
37
|
// @binding(1) samp : sampler -- linear filterable sampler
|
|
38
38
|
// @binding(2) params : FxaaParams -- final-output policy
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
39
|
+
// DOMAIN: the positive LUT path samples and writes linear-LDR values. Its
|
|
40
|
+
// ditherEnabled uniform is forced to zero because the following fs_encode_only
|
|
41
|
+
// surface writer owns the sole OETF and final dither. The legacy direct path
|
|
42
|
+
// may still enable the same bounded dither when FXAA is itself the surface
|
|
43
|
+
// writer; that legacy route remains display-encoded. FXAA never performs an
|
|
44
|
+
// OETF.
|
|
43
45
|
|
|
44
46
|
#import forgeax_view::common::{FullscreenOutput, ditherUnorm8}
|
|
45
47
|
#import forgeax_view::common::fullscreen_triangle
|
|
@@ -53,6 +55,8 @@ const ITERATIONS: i32 = 12;
|
|
|
53
55
|
@group(0) @binding(1) var samp: sampler;
|
|
54
56
|
|
|
55
57
|
struct FxaaParams {
|
|
58
|
+
// Zero for graph-owned linear-LDR intermediates; non-zero is reserved for a
|
|
59
|
+
// direct final surface writer and never performs color-space conversion.
|
|
56
60
|
ditherEnabled: f32,
|
|
57
61
|
// Keep the uniform block at one 16-byte slot. WebGL2/GLES uniform-buffer
|
|
58
62
|
// validation uses the std140-aligned block size, while the host uploads a
|
package/src/ibl-sampling.wgsl
CHANGED
|
@@ -21,6 +21,22 @@
|
|
|
21
21
|
|
|
22
22
|
#import forgeax_pbr::ibl_shared::{fresnelSchlickRoughness, inverseRotateEnvironment}
|
|
23
23
|
|
|
24
|
+
// Decode the renderer's Skylight/probe scalar without touching the probe box
|
|
25
|
+
// metadata carried in the color lanes. A negative intensity is the
|
|
26
|
+
// ReflectionProbe sentinel `-(probeIntensity + 1)`; regular Skylights retain
|
|
27
|
+
// their linear RGB tint and intensity product. Both the PBR ambient specular
|
|
28
|
+
// term and its detached SSR fallback consume this one scale.
|
|
29
|
+
fn decodeSpecularEnvironmentScale(
|
|
30
|
+
skyColor: vec3<f32>,
|
|
31
|
+
intensity: f32,
|
|
32
|
+
) -> vec3<f32> {
|
|
33
|
+
return select(
|
|
34
|
+
skyColor * intensity,
|
|
35
|
+
vec3<f32>(max(-intensity - 1.0, 0.0)),
|
|
36
|
+
intensity < 0.0,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
24
40
|
// Sample pre-convolved irradiance from the irradiance cubemap.
|
|
25
41
|
// Y is negated to compensate for WebGPU's top-left texture origin vs the
|
|
26
42
|
// OpenGL convention used during equirect-to-cube render passes.
|
|
@@ -37,6 +53,18 @@ fn sampleIblDiffuse(
|
|
|
37
53
|
}
|
|
38
54
|
|
|
39
55
|
// Split-sum specular IBL: prefiltered env * (F0 * scale + bias).
|
|
56
|
+
fn projectSpecularRadiance(
|
|
57
|
+
radiance: vec3<f32>, normal: vec3<f32>, view: vec3<f32>,
|
|
58
|
+
roughness: f32, F0: vec3<f32>, brdfLut: texture_2d<f32>, brdfLutSampler: sampler,
|
|
59
|
+
) -> vec3<f32> {
|
|
60
|
+
let NdotV = max(dot(normal, view), 0.001);
|
|
61
|
+
// The preintegrated LUT has one level; explicit LOD remains valid when the
|
|
62
|
+
// Standard evaluator is selected by a non-uniform particle control path.
|
|
63
|
+
let envBRDF = textureSampleLevel(brdfLut, brdfLutSampler, vec2<f32>(NdotV, roughness), 0.0).rg;
|
|
64
|
+
let F = fresnelSchlickRoughness(NdotV, F0, roughness);
|
|
65
|
+
return radiance * (F * envBRDF.r + envBRDF.g);
|
|
66
|
+
}
|
|
67
|
+
|
|
40
68
|
fn sampleIblSpecular(
|
|
41
69
|
normal: vec3<f32>,
|
|
42
70
|
view: vec3<f32>,
|
|
@@ -54,9 +82,7 @@ fn sampleIblSpecular(
|
|
|
54
82
|
let Rflip = vec3<f32>(rotated.x, -rotated.y, rotated.z);
|
|
55
83
|
let mip = roughness * 4.0;
|
|
56
84
|
let prefilteredColor = textureSampleLevel(prefilterMap, prefilterSampler, Rflip, mip).rgb;
|
|
57
|
-
|
|
58
|
-
let F = fresnelSchlickRoughness(NdotV, F0, roughness);
|
|
59
|
-
return prefilteredColor * (F * envBRDF.r + envBRDF.g);
|
|
85
|
+
return projectSpecularRadiance(prefilteredColor, normal, view, roughness, F0, brdfLut, brdfLutSampler);
|
|
60
86
|
}
|
|
61
87
|
|
|
62
88
|
// Project a reflection ray from a point inside a probe box onto the box
|
|
@@ -106,7 +132,5 @@ fn sampleReflectionProbeSpecular(
|
|
|
106
132
|
let probeDirection = vec3<f32>(rotated.x, -rotated.y, rotated.z);
|
|
107
133
|
let mip = roughness * 4.0;
|
|
108
134
|
let prefilteredColor = textureSampleLevel(probeMap, probeSampler, probeDirection, mip).rgb;
|
|
109
|
-
|
|
110
|
-
let F = fresnelSchlickRoughness(NdotV, F0, roughness);
|
|
111
|
-
return prefilteredColor * (F * envBRDF.r + envBRDF.g);
|
|
135
|
+
return projectSpecularRadiance(prefilteredColor, normal, view, roughness, F0, brdfLut, brdfLutSampler);
|
|
112
136
|
}
|
package/src/index.ts
CHANGED
|
@@ -69,6 +69,7 @@ export {
|
|
|
69
69
|
DEFAULT_SPRITE_PARAM_SCHEMA,
|
|
70
70
|
DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
|
|
71
71
|
DEFAULT_UNLIT_PARAM_SCHEMA,
|
|
72
|
+
PARTICLE_MESH_SURFACE_PARAM_SCHEMA,
|
|
72
73
|
STANDARD_BASE_PARAM_SCHEMA,
|
|
73
74
|
STANDARD_PBR_ALPHA_CUTOFF_DEFAULT,
|
|
74
75
|
STANDARD_PBR_ARTIFACT_RECEIPT,
|
|
@@ -76,8 +77,10 @@ export {
|
|
|
76
77
|
STANDARD_PHYSICAL_LAYER_PARAM_SCHEMA,
|
|
77
78
|
STANDARD_PHYSICAL_TEXTURE_FIELDS,
|
|
78
79
|
STANDARD_PIPELINE_PARAM_SCHEMA,
|
|
80
|
+
STANDARD_TEXTURE_MASK_OVERRIDE,
|
|
79
81
|
type StandardPhysicalTextureField,
|
|
80
82
|
standardPhysicalTextureFields,
|
|
83
|
+
standardTextureMask,
|
|
81
84
|
} from './material-schemas.js';
|
|
82
85
|
export {
|
|
83
86
|
registerDefaultSpriteLit,
|
|
@@ -95,6 +98,8 @@ export {
|
|
|
95
98
|
type ShaderRegistryDevice as ShaderCatalogDevice,
|
|
96
99
|
type ShaderRegistryOptions,
|
|
97
100
|
type ShaderRegistryOptions as ShaderCatalogOptions,
|
|
101
|
+
SSR_SHADER_MODULES,
|
|
102
|
+
type SsrShaderModule,
|
|
98
103
|
} from './ShaderRegistry.js';
|
|
99
104
|
export {
|
|
100
105
|
findVariantByKey,
|
|
@@ -119,6 +124,189 @@ export const DEFAULT_STANDARD_SURFACE_MODULE =
|
|
|
119
124
|
/** Stable build-time module id for the temporal-v1 accessor owner. */
|
|
120
125
|
export const SCENE_DATA_TEMPORAL_V1_SHADER_MODULE = 'forgeax_scene_temporal' as const;
|
|
121
126
|
|
|
127
|
+
/** Stable build-time module id for the single Standard output encoder. */
|
|
128
|
+
export const STANDARD_OUTPUT_ENCODING_SHADER_MODULE = 'forgeax_view::output_encoding' as const;
|
|
129
|
+
|
|
130
|
+
/** Runtime WGSL for the fixed-cohort, same-frame auto exposure meter. */
|
|
131
|
+
export const AUTO_EXPOSURE_METER_WGSL = /* wgsl */ `
|
|
132
|
+
struct AutoExposureParameters {
|
|
133
|
+
compensationEv: f32,
|
|
134
|
+
rangeMinEv: f32,
|
|
135
|
+
rangeMaxEv: f32,
|
|
136
|
+
upRate: f32,
|
|
137
|
+
downRate: f32,
|
|
138
|
+
deltaTime: f32,
|
|
139
|
+
fallback: f32,
|
|
140
|
+
generation: f32,
|
|
141
|
+
};
|
|
142
|
+
@group(0) @binding(0) var source: texture_2d<f32>;
|
|
143
|
+
@group(0) @binding(1) var<storage, read_write> histogram: array<atomic<u32>>;
|
|
144
|
+
@group(0) @binding(2) var<storage, read_write> state: array<vec4<f32>>;
|
|
145
|
+
@group(0) @binding(3) var<storage, read_write> candidate: array<vec4<f32>>;
|
|
146
|
+
@group(0) @binding(4) var<storage, read> parameters: AutoExposureParameters;
|
|
147
|
+
|
|
148
|
+
var<workgroup> localHistogram: array<atomic<u32>, 256>;
|
|
149
|
+
var<workgroup> totals: array<u32, 64>;
|
|
150
|
+
var<workgroup> sampleTotal: u32;
|
|
151
|
+
|
|
152
|
+
fn finite(value: f32) -> bool {
|
|
153
|
+
// Equality rejects NaN; the magnitude bound rejects +/-infinity.
|
|
154
|
+
return value == value && abs(value) <= 3.402823e+37;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fn centerWeight(
|
|
158
|
+
block: vec2<u32>,
|
|
159
|
+
center: vec2<f32>,
|
|
160
|
+
inverseExtentSquared: vec2<f32>,
|
|
161
|
+
) -> u32 {
|
|
162
|
+
let delta = vec2<f32>(block) - center;
|
|
163
|
+
let distanceSquared =
|
|
164
|
+
delta.x * delta.x * inverseExtentSquared.x +
|
|
165
|
+
delta.y * delta.y * inverseExtentSquared.y;
|
|
166
|
+
if (distanceSquared <= 0.25) { return 3u; }
|
|
167
|
+
if (distanceSquared <= 0.75) { return 2u; }
|
|
168
|
+
return 1u;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fn accumulateSample(
|
|
172
|
+
block: vec2<u32>,
|
|
173
|
+
sampleGrid: vec2<u32>,
|
|
174
|
+
dimensions: vec2<u32>,
|
|
175
|
+
center: vec2<f32>,
|
|
176
|
+
extent: vec2<f32>,
|
|
177
|
+
) {
|
|
178
|
+
if (block.x >= sampleGrid.x || block.y >= sampleGrid.y) { return; }
|
|
179
|
+
let pixel = min(block * vec2<u32>(4u) + vec2<u32>(2u), dimensions - vec2<u32>(1u));
|
|
180
|
+
let sample = textureLoad(source, vec2<i32>(pixel), 0);
|
|
181
|
+
if (!finite(sample.r) || !finite(sample.g) || !finite(sample.b)) { return; }
|
|
182
|
+
let luminance = dot(sample.rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
|
|
183
|
+
if (!finite(luminance) || luminance <= 0.0) { return; }
|
|
184
|
+
let bin = min(u32(clamp(log2(luminance) + 12.0, 0.0, 23.999) * (256.0 / 24.0)), 255u);
|
|
185
|
+
atomicAdd(&localHistogram[bin], centerWeight(block, center, extent));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@compute @workgroup_size(256, 1, 1)
|
|
189
|
+
fn auto_exposure_clear(@builtin(local_invocation_index) localIndex: u32) {
|
|
190
|
+
atomicStore(&histogram[localIndex], 0u);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@compute @workgroup_size(256, 1, 1)
|
|
194
|
+
fn auto_exposure_histogram(
|
|
195
|
+
@builtin(local_invocation_index) localIndex: u32,
|
|
196
|
+
@builtin(workgroup_id) workgroupId: vec3<u32>,
|
|
197
|
+
@builtin(num_workgroups) numWorkgroups: vec3<u32>,
|
|
198
|
+
) {
|
|
199
|
+
let dimensions = textureDimensions(source);
|
|
200
|
+
let sampleGrid = (dimensions.xy + vec2<u32>(3u)) / vec2<u32>(4u);
|
|
201
|
+
let grid = vec2<f32>(sampleGrid);
|
|
202
|
+
let center = (grid - vec2<f32>(1.0)) * 0.5;
|
|
203
|
+
let extent = max(center, vec2<f32>(1.0));
|
|
204
|
+
let inverseExtent = 1.0 / extent;
|
|
205
|
+
let inverseExtentSquared = inverseExtent * inverseExtent;
|
|
206
|
+
|
|
207
|
+
// Map the 256 lanes to a 16x16 logical tile. The fixed 4x8 dispatch grid
|
|
208
|
+
// then uses a two-dimensional tile stride to cover every 4x4 sample block.
|
|
209
|
+
atomicStore(&localHistogram[localIndex], 0u);
|
|
210
|
+
workgroupBarrier();
|
|
211
|
+
let localGrid = vec2<u32>(localIndex % 16u, localIndex / 16u);
|
|
212
|
+
let tileSize = vec2<u32>(16u);
|
|
213
|
+
let blockStart = workgroupId.xy * tileSize + localGrid;
|
|
214
|
+
let blockStride = numWorkgroups.xy * tileSize;
|
|
215
|
+
for (var blockY = blockStart.y; blockY < sampleGrid.y; blockY += blockStride.y) {
|
|
216
|
+
for (var blockX = blockStart.x; blockX < sampleGrid.x; blockX += blockStride.x) {
|
|
217
|
+
accumulateSample(
|
|
218
|
+
vec2<u32>(blockX, blockY),
|
|
219
|
+
sampleGrid,
|
|
220
|
+
dimensions,
|
|
221
|
+
center,
|
|
222
|
+
inverseExtentSquared,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
workgroupBarrier();
|
|
227
|
+
|
|
228
|
+
// Every lane owns one bin. All local accumulation is complete before the
|
|
229
|
+
// owners atomically publish their finite result to the one global 1KiB map.
|
|
230
|
+
let localCount = atomicLoad(&localHistogram[localIndex]);
|
|
231
|
+
if (localCount > 0u) {
|
|
232
|
+
atomicAdd(&histogram[localIndex], localCount);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@compute @workgroup_size(64, 1, 1)
|
|
237
|
+
fn auto_exposure_adapt(@builtin(local_invocation_index) localIndex: u32) {
|
|
238
|
+
var total = 0u;
|
|
239
|
+
for (var index = localIndex; index < 256u; index += 64u) {
|
|
240
|
+
total += atomicLoad(&histogram[index]);
|
|
241
|
+
}
|
|
242
|
+
totals[localIndex] = total;
|
|
243
|
+
workgroupBarrier();
|
|
244
|
+
if (localIndex == 0u) {
|
|
245
|
+
var reducedTotal = 0u;
|
|
246
|
+
for (var lane = 0u; lane < 64u; lane += 1u) {
|
|
247
|
+
reducedTotal += totals[lane];
|
|
248
|
+
}
|
|
249
|
+
sampleTotal = reducedTotal;
|
|
250
|
+
}
|
|
251
|
+
workgroupBarrier();
|
|
252
|
+
if (localIndex == 0u) {
|
|
253
|
+
let validParameters =
|
|
254
|
+
finite(parameters.compensationEv) &&
|
|
255
|
+
finite(parameters.rangeMinEv) &&
|
|
256
|
+
finite(parameters.rangeMaxEv) &&
|
|
257
|
+
parameters.rangeMinEv <= parameters.rangeMaxEv &&
|
|
258
|
+
finite(parameters.upRate) &&
|
|
259
|
+
finite(parameters.downRate) &&
|
|
260
|
+
finite(parameters.deltaTime) &&
|
|
261
|
+
parameters.deltaTime >= 0.0 &&
|
|
262
|
+
finite(parameters.fallback) &&
|
|
263
|
+
parameters.fallback > 0.0 &&
|
|
264
|
+
finite(parameters.generation);
|
|
265
|
+
let safeFallback = select(
|
|
266
|
+
1.0,
|
|
267
|
+
parameters.fallback,
|
|
268
|
+
finite(parameters.fallback) && parameters.fallback > 0.0,
|
|
269
|
+
);
|
|
270
|
+
let safeGeneration = select(0.0, parameters.generation, finite(parameters.generation));
|
|
271
|
+
let lowRank = min(sampleTotal, u32(f32(sampleTotal) * 0.05));
|
|
272
|
+
let highRank = min(sampleTotal, max(lowRank + 1u, u32(ceil(f32(sampleTotal) * 0.95))));
|
|
273
|
+
var cumulative = 0u;
|
|
274
|
+
var clippedWeightedLog = 0.0;
|
|
275
|
+
var clippedTotal = 0u;
|
|
276
|
+
for (var index = 0u; index < 256u; index += 1u) {
|
|
277
|
+
let count = atomicLoad(&histogram[index]);
|
|
278
|
+
let begin = cumulative;
|
|
279
|
+
cumulative += count;
|
|
280
|
+
let keptBegin = max(begin, lowRank);
|
|
281
|
+
let keptEnd = min(cumulative, highRank);
|
|
282
|
+
if (keptEnd > keptBegin) {
|
|
283
|
+
let kept = keptEnd - keptBegin;
|
|
284
|
+
clippedTotal += kept;
|
|
285
|
+
clippedWeightedLog += f32(kept) * (-12.0 + (f32(index) + 0.5) * (24.0 / 256.0));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
let averageLog = select(0.0, clippedWeightedLog / f32(clippedTotal), clippedTotal > 0u);
|
|
289
|
+
let measuredExposure = exp2(-averageLog) * 0.18;
|
|
290
|
+
let targetExposure = select(
|
|
291
|
+
safeFallback,
|
|
292
|
+
exp2(clamp(log2(max(measuredExposure, 1e-6)) + parameters.compensationEv, parameters.rangeMinEv, parameters.rangeMaxEv)),
|
|
293
|
+
validParameters && clippedTotal > 0u && finite(measuredExposure),
|
|
294
|
+
);
|
|
295
|
+
let previous = state[0];
|
|
296
|
+
let hasPrevious = validParameters && previous.y > 0.5 && previous.z == safeGeneration && previous.w == safeFallback && finite(previous.x) && previous.x > 0.0;
|
|
297
|
+
let current = select(safeFallback, previous.x, hasPrevious);
|
|
298
|
+
let rate = select(parameters.downRate, parameters.upRate, targetExposure > current);
|
|
299
|
+
let safeRate = select(0.0, rate, finite(rate) && rate >= 0.0);
|
|
300
|
+
let dt = select(0.0, parameters.deltaTime, finite(parameters.deltaTime) && parameters.deltaTime >= 0.0);
|
|
301
|
+
let amount = 1.0 - exp(-safeRate * dt);
|
|
302
|
+
let adapted = select(current, current + (targetExposure - current) * amount, validParameters && finite(targetExposure) && finite(current) && dt > 0.0 && safeRate > 0.0);
|
|
303
|
+
let exposure = select(safeFallback, adapted, finite(adapted) && adapted > 0.0);
|
|
304
|
+
candidate[0] = vec4<f32>(exposure, 1.0, safeGeneration, safeFallback);
|
|
305
|
+
state[0] = candidate[0];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
`;
|
|
309
|
+
|
|
122
310
|
/**
|
|
123
311
|
* Material modules whose shader source and parameter contract are owned by
|
|
124
312
|
* the Engine. They live in the runtime ShaderRegistry, so a Pack containing
|
|
@@ -203,6 +391,7 @@ export function createBuiltinMaterialAsset(kind: BuiltinMaterialKind): MaterialA
|
|
|
203
391
|
? { moduleSlots: { surface: DEFAULT_STANDARD_SURFACE_MODULE } }
|
|
204
392
|
: {}),
|
|
205
393
|
},
|
|
394
|
+
renderState: { tags: { LightMode: 'Forward' } },
|
|
206
395
|
},
|
|
207
396
|
],
|
|
208
397
|
parameters:
|
|
@@ -218,13 +218,12 @@ fn evalPointShadowed(
|
|
|
218
218
|
lightPos, colorTimesIntensity, invRangeSquared,
|
|
219
219
|
worldPos, normal, viewDir, baseColor, metallic, alphaSq, F0,
|
|
220
220
|
);
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
// cubemap convention flips Z so the +Z face look direction matches.
|
|
221
|
+
// The atlas uses the shared CubeCamera face directions. The raster matrices
|
|
222
|
+
// reflect clip-space X to preserve authored front-face winding, so sampling
|
|
223
|
+
// must use the corresponding raw light-to-fragment direction.
|
|
225
224
|
let toLight = lightPos - worldPos;
|
|
226
225
|
let fromLight = worldPos - lightPos;
|
|
227
|
-
let lightLocal =
|
|
226
|
+
let lightLocal = fromLight;
|
|
228
227
|
// Reconstruct [0,1] NDC depth from world-space distance: largest-axis
|
|
229
228
|
// projection (research L0.5). Match the per-face perspective near / far
|
|
230
229
|
// configured by buildPointShadowMatrices (PointLightShadow.nearPlane /
|
|
@@ -239,7 +238,7 @@ fn evalPointShadowed(
|
|
|
239
238
|
let nDotL = max(dot(normal, normalize(toLight)), 0.0);
|
|
240
239
|
let shadowFactor = sample_shadow_cube_hw2x2(
|
|
241
240
|
shadowAtlas, shadowSampler, lightLocal, shadowAtlasLayer,
|
|
242
|
-
depthRef,
|
|
241
|
+
depthRef, normalBias, depthBias, nDotL,
|
|
243
242
|
);
|
|
244
243
|
return lit * shadowFactor;
|
|
245
244
|
}
|