@forgeax/engine-shader 0.1.20 → 0.1.21
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 +10 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/ShaderRegistry.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +52 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/ShaderRegistry.ts +65 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +30 -4
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +7 -0
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +69 -24
- package/src/__tests__/lighting-point-spot-volume.unit.test.ts +8 -0
- package/src/__tests__/lighting-punctual.unit.test.ts +32 -6
- package/src/__tests__/material-derived-builtins.integration.test.ts +27 -0
- package/src/__tests__/reflection-probe-sampling.unit.test.ts +23 -0
- package/src/__tests__/scene-temporal.unit.test.ts +20 -0
- package/src/__tests__/shader.unit.test.ts +34 -13
- package/src/__tests__/shadow-pcss.unit.test.ts +131 -0
- package/src/__tests__/sprite-lit-shader.test.ts +24 -11
- package/src/__tests__/sprite-variants.unit.test.ts +1 -0
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +7 -1
- package/src/__tests__/transparent-pbr.unit.test.ts +7 -0
- package/src/common.wgsl +42 -12
- package/src/default-standard-pbr-skin.wgsl +30 -71
- package/src/default-standard-pbr.wgsl +92 -163
- package/src/fxaa.wgsl +27 -6
- package/src/ibl-sampling.wgsl +54 -0
- package/src/index.ts +6 -0
- package/src/lighting-directional.wgsl +157 -18
- package/src/lighting-punctual.wgsl +51 -21
- package/src/scene-temporal.wgsl +14 -8
- package/src/shadow-pcf.wgsl +44 -1
- package/src/sprite-lit.wgsl +36 -52
- package/src/{hdrp-cluster-forward.wgsl → standard-cluster.wgsl} +73 -60
- package/src/tonemap.wgsl +8 -3
- package/src/volume/volume-inject.wgsl +14 -2
- package/src/volume/volume-integrate.wgsl +1 -6
package/src/sprite-lit.wgsl
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#pragma variant_axis STORAGE_BUFFER_AVAILABLE
|
|
2
|
+
#pragma variant_axis CLUSTER_FORWARD_AVAILABLE
|
|
2
3
|
#define_import_path forgeax_material::sprite-lit
|
|
3
4
|
|
|
4
|
-
#import forgeax_view::common::{View, Mesh, InstanceData,
|
|
5
|
-
#import forgeax_scene_temporal::{packSceneTemporalV1}
|
|
5
|
+
#import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, sampleMaterialTexture}
|
|
6
|
+
#import forgeax_scene_temporal::{packSceneTemporalV1, sceneViewZ}
|
|
7
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
8
|
+
#import forgeax_standard::cluster::{evaluateStandardClusterLights}
|
|
9
|
+
#endif
|
|
6
10
|
|
|
7
11
|
// @forgeax/engine-shader - sprite-lit.wgsl
|
|
8
12
|
// (tweak-20260701-sprite-lit-flat-default-drop-ndotl-for-2d).
|
|
@@ -26,7 +30,8 @@
|
|
|
26
30
|
// - no shadow receiver / caster
|
|
27
31
|
// - no Godot-style light_mode blends
|
|
28
32
|
// - no IBL ambient
|
|
29
|
-
// -
|
|
33
|
+
// - Standard clustered punctual lights are enabled by the
|
|
34
|
+
// CLUSTER_FORWARD_AVAILABLE variant.
|
|
30
35
|
//
|
|
31
36
|
// >>> AI user error self-recovery hint:
|
|
32
37
|
// sprite-lit needs at least one light in scene; for unlit sprites use
|
|
@@ -38,8 +43,7 @@
|
|
|
38
43
|
// pipelineState.defaultSampler + .defaultWhiteTextureView on the host side):
|
|
39
44
|
//
|
|
40
45
|
// @group(0) @binding(0) view uniform
|
|
41
|
-
// @group(0) @binding(1)
|
|
42
|
-
// @group(0) @binding(2) spotLightsBuffer storage / uniform (variant)
|
|
46
|
+
// @group(0) @binding(1..2) Standard Cluster storage (selected variant)
|
|
43
47
|
// @group(1) @binding(0) material uniform (sprite layout
|
|
44
48
|
// colorTint /
|
|
45
49
|
// region /
|
|
@@ -80,16 +84,17 @@ struct VsIn {
|
|
|
80
84
|
@location(3) tangent : vec4<f32>,
|
|
81
85
|
};
|
|
82
86
|
|
|
83
|
-
// VsOut carries the atlas UV plus the per-fragment world-space position
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
// multi-instance draws and could not handle the 9-slice quad layout.
|
|
87
|
+
// VsOut carries the atlas UV plus the per-fragment world-space position and
|
|
88
|
+
// the Standard cluster coordinates produced by the vertex stage. Fragment
|
|
89
|
+
// `@builtin(position)` is framebuffer-space after rasterization, so it is not
|
|
90
|
+
// a valid source for NDC cluster lookup; keep the perspective divide here and
|
|
91
|
+
// interpolate the result alongside worldPos.
|
|
89
92
|
struct VsOut {
|
|
90
93
|
@builtin(position) clip : vec4<f32>,
|
|
91
94
|
@location(0) uv_atlas : vec2<f32>,
|
|
92
95
|
@location(1) worldPos : vec3<f32>,
|
|
96
|
+
@location(2) ndc : vec3<f32>,
|
|
97
|
+
@location(3) viewZ : f32,
|
|
93
98
|
};
|
|
94
99
|
|
|
95
100
|
struct SpriteVertex {
|
|
@@ -155,6 +160,11 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32, @builtin(vertex_index)
|
|
|
155
160
|
out.clip = view.worldViewProj * world;
|
|
156
161
|
out.uv_atlas = vertex.uvAtlas;
|
|
157
162
|
out.worldPos = world.xyz;
|
|
163
|
+
let clipPos = out.clip;
|
|
164
|
+
out.ndc = clipPos.xyz / clipPos.w;
|
|
165
|
+
// Keep the cluster depth exactly aligned with the CPU binner for both
|
|
166
|
+
// perspective and off-axis orthographic projections.
|
|
167
|
+
out.viewZ = sceneViewZ(clipPos, view.temporalProjection);
|
|
158
168
|
return out;
|
|
159
169
|
}
|
|
160
170
|
|
|
@@ -174,50 +184,24 @@ fn spriteLitDirectional(albedo : vec3<f32>) -> vec3<f32> {
|
|
|
174
184
|
return albedo * view.lightColor;
|
|
175
185
|
}
|
|
176
186
|
|
|
177
|
-
// Flat point-light contribution. Applies the KHR-lights-punctual smooth-
|
|
178
|
-
// window range attenuation only. `attenuation` is 0 outside the light's
|
|
179
|
-
// range (`invRangeSquared`) and 1 at the light center; the reciprocal-square
|
|
180
|
-
// term gives the physical falloff. URP cap = 4 point lights per pass.
|
|
181
|
-
fn spriteLitPoint(p : PointLight, worldPos : vec3<f32>, albedo : vec3<f32>) -> vec3<f32> {
|
|
182
|
-
let toLight = p.position - worldPos;
|
|
183
|
-
let dSquared = max(dot(toLight, toLight), 1e-4);
|
|
184
|
-
let factor = 1.0 - (dSquared * p.invRangeSquared) * (dSquared * p.invRangeSquared);
|
|
185
|
-
let attenuation = max(min(factor, 1.0), 0.0) / dSquared;
|
|
186
|
-
return albedo * p.colorTimesIntensity * attenuation;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Flat spot-light contribution. Same range attenuation as spriteLitPoint,
|
|
190
|
-
// modulated by a smoothstep cone factor (host-side degree -> cosine
|
|
191
|
-
// conversion). Spot direction only shapes the cone; it does not project
|
|
192
|
-
// onto a surface normal. URP cap = 4 spot lights per pass.
|
|
193
|
-
fn spriteLitSpot(s : SpotLight, worldPos : vec3<f32>, albedo : vec3<f32>) -> vec3<f32> {
|
|
194
|
-
let toLight = s.position - worldPos;
|
|
195
|
-
let dSquared = max(dot(toLight, toLight), 1e-4);
|
|
196
|
-
let l = toLight / sqrt(dSquared);
|
|
197
|
-
let factor = 1.0 - (dSquared * s.invRangeSquared) * (dSquared * s.invRangeSquared);
|
|
198
|
-
let attenuation = max(min(factor, 1.0), 0.0) / dSquared;
|
|
199
|
-
let cone = smoothstep(s.cosOuter, s.cosInner, dot(l, -s.direction));
|
|
200
|
-
return albedo * s.colorTimesIntensity * attenuation * cone;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
187
|
// Shared shading body used by both fs_main (LDR) and fs_main_hdr (HDR).
|
|
204
188
|
// The two entry points differ only in their tail-end clamp + srgb encode
|
|
205
189
|
// step (LDR clamp / HDR pass-through boundary).
|
|
206
|
-
fn spriteLitShadeAccum(
|
|
190
|
+
fn spriteLitShadeAccum(
|
|
191
|
+
albedo : vec3<f32>,
|
|
192
|
+
worldPos : vec3<f32>,
|
|
193
|
+
ndc : vec3<f32>,
|
|
194
|
+
viewZ : f32,
|
|
195
|
+
) -> vec3<f32> {
|
|
207
196
|
// Directional contribution (1 light, View UBO).
|
|
208
197
|
var lit = spriteLitDirectional(albedo);
|
|
209
|
-
|
|
210
|
-
let
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
let spotCount = spotLightsBuffer.count;
|
|
217
|
-
for (var i : u32 = 0u; i < spotCount; i = i + 1u) {
|
|
218
|
-
let s = spotLightsBuffer.slots[i];
|
|
219
|
-
lit = lit + spriteLitSpot(s, worldPos, albedo);
|
|
220
|
-
}
|
|
198
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
199
|
+
let viewDir = normalize(view.cameraPos - worldPos);
|
|
200
|
+
lit = lit + evaluateStandardClusterLights(
|
|
201
|
+
ndc, viewZ, worldPos, vec3<f32>(0.0, 0.0, 1.0),
|
|
202
|
+
viewDir, albedo, 0.0, 1.0, vec3<f32>(0.04), true,
|
|
203
|
+
);
|
|
204
|
+
#endif
|
|
221
205
|
return lit;
|
|
222
206
|
}
|
|
223
207
|
|
|
@@ -225,7 +209,7 @@ fn spriteLitShadeAccum(albedo : vec3<f32>, worldPos : vec3<f32>) -> vec3<f32> {
|
|
|
225
209
|
fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
226
210
|
let texel = sampleMaterialTexture(baseColorTexture, baseColorSampler, in.uv_atlas, material.baseColorTextureCoordinatesMetadata.zw);
|
|
227
211
|
let albedo4 = texel * material.colorTint;
|
|
228
|
-
let lit = spriteLitShadeAccum(albedo4.rgb, in.worldPos);
|
|
212
|
+
let lit = spriteLitShadeAccum(albedo4.rgb, in.worldPos, in.ndc, in.viewZ);
|
|
229
213
|
// Strict clamp 0..1 before premultiplied alpha multiply keeps the LDR
|
|
230
214
|
// path bounded even when a scene stacks many lights.
|
|
231
215
|
let lit_rgba = clamp(vec4<f32>(lit, albedo4.a), vec4<f32>(0.0), vec4<f32>(1.0));
|
|
@@ -243,7 +227,7 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
243
227
|
fn fs_main_hdr(in : VsOut) -> @location(0) vec4<f32> {
|
|
244
228
|
let texel = sampleMaterialTexture(baseColorTexture, baseColorSampler, in.uv_atlas, material.baseColorTextureCoordinatesMetadata.zw);
|
|
245
229
|
let albedo4 = texel * material.colorTint;
|
|
246
|
-
let lit = spriteLitShadeAccum(albedo4.rgb, in.worldPos);
|
|
230
|
+
let lit = spriteLitShadeAccum(albedo4.rgb, in.worldPos, in.ndc, in.viewZ);
|
|
247
231
|
// HDR variant: do NOT clamp the lit output to [0, 1]; let the tonemap
|
|
248
232
|
// pass absorb HDR values > 1. Alpha stays clamped because the premult
|
|
249
233
|
// math requires alpha in [0, 1].
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
//
|
|
1
|
+
// standard-cluster.wgsl — Standard clustered punctual light evaluation.
|
|
2
2
|
// feat-20260608-cluster-lighting M4 / w17.
|
|
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
6
|
|
|
7
|
-
#define_import_path
|
|
7
|
+
#define_import_path forgeax_standard::cluster
|
|
8
8
|
|
|
9
|
-
#import forgeax_pbr::lighting_punctual::{evalPoint, evalSpot,
|
|
9
|
+
#import forgeax_pbr::lighting_punctual::{evalPoint, evalPointFlat, evalSpot, evalSpotFlat, evalSpotShadowed}
|
|
10
|
+
#import forgeax_pbr::lighting_attenuation::{projectSpotUv}
|
|
10
11
|
#import forgeax_view::common::{view}
|
|
11
12
|
#ifdef PROJECTOR_AVAILABLE
|
|
12
13
|
#import forgeax_view::common::{projectorTexture, projectorSampler}
|
|
@@ -19,15 +20,18 @@
|
|
|
19
20
|
|
|
20
21
|
const KIND_POINT: u32 = 0u;
|
|
21
22
|
const KIND_SPOT: u32 = 1u;
|
|
22
|
-
|
|
23
|
+
// LightSlot.kind_and_shadow.y uses one high bit as the accepted projector
|
|
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.
|
|
26
|
+
const PROJECTOR_FLAG: i32 = 0x40000000;
|
|
27
|
+
const TILE_MASK: i32 = 0x3fffffff;
|
|
23
28
|
|
|
24
29
|
// LightSlot is the byte-frozen 64B std430/std140 transport contract.
|
|
25
30
|
// [0..2] position, [3] invRangeSquared
|
|
26
31
|
// [4..6] colorTimesIntensity, [7] cosInner
|
|
27
32
|
// [8..10] direction, [11] cosOuter
|
|
28
33
|
// [12] raw kind u32, [13] shadow identity i32
|
|
29
|
-
// [14] point near
|
|
30
|
-
// [15] point far / spot PCF kernel width f32 bits
|
|
34
|
+
// [14..15] point near/far f32 bits; spot keeps these lanes zero.
|
|
31
35
|
struct LightSlot {
|
|
32
36
|
position : vec4<f32>,
|
|
33
37
|
color : vec4<f32>,
|
|
@@ -37,35 +41,14 @@ struct LightSlot {
|
|
|
37
41
|
|
|
38
42
|
const LIGHTSLOT_BYTE_SIZE: u32 = 64u;
|
|
39
43
|
|
|
40
|
-
fn sampleClusterSpotProjector(lightViewProj : mat4x4<f32>, worldPos : vec3<f32>) -> vec3<f32> {
|
|
41
|
-
#ifdef PROJECTOR_AVAILABLE
|
|
42
|
-
let clip = lightViewProj * vec4<f32>(worldPos, 1.0);
|
|
43
|
-
// The projector is a cookie, not an extra cone. A fragment that cannot be
|
|
44
|
-
// projected (or lies outside the tile) keeps the analytic Spot contribution
|
|
45
|
-
// intact, matching Three's SpotLightNode fail-open map gate.
|
|
46
|
-
if (abs(clip.w) < 1e-6) { return vec3<f32>(1.0); }
|
|
47
|
-
let uv = projectSpotUv(lightViewProj, worldPos);
|
|
48
|
-
if (any(uv < vec2<f32>(0.0)) || any(uv > vec2<f32>(1.0))) {
|
|
49
|
-
return vec3<f32>(1.0);
|
|
50
|
-
}
|
|
51
|
-
return textureSampleLevel(projectorTexture, projectorSampler, uv, 0.0).rgb;
|
|
52
|
-
#else
|
|
53
|
-
return vec3<f32>(1.0);
|
|
54
|
-
#endif
|
|
55
|
-
}
|
|
56
|
-
|
|
57
44
|
struct ClusterUniform {
|
|
58
45
|
grid : vec4<u32>,
|
|
59
46
|
near_far_log : vec4<f32>,
|
|
60
47
|
};
|
|
61
48
|
|
|
62
|
-
#if STORAGE_BUFFER_AVAILABLE == true
|
|
63
49
|
@group(2) @binding(3) var<storage, read> light_data : array<LightSlot, 256>;
|
|
64
50
|
@group(2) @binding(4) var<storage, read> cluster_grid : array<u32>;
|
|
65
51
|
@group(2) @binding(5) var<storage, read> light_index_list : array<u32>;
|
|
66
|
-
#else
|
|
67
|
-
@group(2) @binding(3) var<uniform> light_data_uniform : array<LightSlot, 128>;
|
|
68
|
-
#endif
|
|
69
52
|
@group(2) @binding(6) var<uniform> cluster_uniform : ClusterUniform;
|
|
70
53
|
|
|
71
54
|
fn get_ssao_intensity() -> f32 {
|
|
@@ -106,6 +89,26 @@ fn ndc_position_to_cluster(
|
|
|
106
89
|
return vec3(cx, cy, cz);
|
|
107
90
|
}
|
|
108
91
|
|
|
92
|
+
// Surface SpotLight projector sampling is owned by the same clustered light
|
|
93
|
+
// evaluator as the analytic cone/range contribution. The host marks the
|
|
94
|
+
// selected projector in the LightSlot flag and writes its matrix to the
|
|
95
|
+
// corresponding View lane (lane 0 for projector-only); no second
|
|
96
|
+
// material-local sampling loop or resource owner is allowed.
|
|
97
|
+
fn sampleStandardSpotProjector(
|
|
98
|
+
lightViewProj : mat4x4<f32>,
|
|
99
|
+
world_pos : vec3<f32>,
|
|
100
|
+
) -> vec3<f32> {
|
|
101
|
+
#ifdef PROJECTOR_AVAILABLE
|
|
102
|
+
let uv = projectSpotUv(lightViewProj, world_pos);
|
|
103
|
+
if (!(uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0)) {
|
|
104
|
+
return vec3<f32>(1.0);
|
|
105
|
+
}
|
|
106
|
+
return textureSampleLevel(projectorTexture, projectorSampler, uv, 0.0).rgb;
|
|
107
|
+
#else
|
|
108
|
+
return vec3<f32>(1.0);
|
|
109
|
+
#endif
|
|
110
|
+
}
|
|
111
|
+
|
|
109
112
|
// Decode one kind first, then delegate the complete evaluation to the shared
|
|
110
113
|
// punctual owner. Unknown kinds are explicitly zero contribution.
|
|
111
114
|
fn evaluate_cluster_light(
|
|
@@ -117,9 +120,16 @@ fn evaluate_cluster_light(
|
|
|
117
120
|
metallic : f32,
|
|
118
121
|
alpha_sq : f32,
|
|
119
122
|
f0 : vec3<f32>,
|
|
123
|
+
flat_2d : bool,
|
|
120
124
|
) -> vec3<f32> {
|
|
121
125
|
let kind = light.kind_and_shadow.x;
|
|
122
126
|
if (kind == KIND_POINT) {
|
|
127
|
+
if (flat_2d) {
|
|
128
|
+
return evalPointFlat(
|
|
129
|
+
light.position.xyz, light.color.xyz, light.position.w,
|
|
130
|
+
world_pos, base_color,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
123
133
|
#ifdef POINT_SHADOW_AVAILABLE
|
|
124
134
|
let layer = bitcast<i32>(light.kind_and_shadow.y);
|
|
125
135
|
if (layer >= 0) {
|
|
@@ -139,9 +149,36 @@ fn evaluate_cluster_light(
|
|
|
139
149
|
);
|
|
140
150
|
}
|
|
141
151
|
if (kind == KIND_SPOT) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
152
|
+
if (flat_2d) {
|
|
153
|
+
return evalSpotFlat(
|
|
154
|
+
light.position.xyz, light.direction.xyz, light.color.xyz,
|
|
155
|
+
light.color.w, light.direction.w, light.position.w,
|
|
156
|
+
world_pos, base_color,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
let encoded_tile = bitcast<i32>(light.kind_and_shadow.y);
|
|
160
|
+
// The unshadowed sentinel is -1. A signed bitwise test against the
|
|
161
|
+
// projector flag would misclassify that sentinel because -1 has every bit
|
|
162
|
+
// set, so admit the flag only from the non-negative encoded domain.
|
|
163
|
+
if (encoded_tile >= 0 && (encoded_tile & PROJECTOR_FLAG) != 0) {
|
|
164
|
+
let tile = encoded_tile & TILE_MASK;
|
|
165
|
+
if (tile < 4) {
|
|
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);
|
|
179
|
+
}
|
|
180
|
+
if (encoded_tile >= 0 && encoded_tile < 4) {
|
|
181
|
+
let tile = encoded_tile;
|
|
145
182
|
return evalSpotShadowed(
|
|
146
183
|
light.position.xyz, light.direction.xyz, light.color.xyz,
|
|
147
184
|
// LightSlot packs cosInner in color.w and cosOuter in direction.w;
|
|
@@ -149,22 +186,9 @@ fn evaluate_cluster_light(
|
|
|
149
186
|
light.color.w, light.direction.w, light.position.w,
|
|
150
187
|
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
151
188
|
view.spotLightViewProj[tile], tile, 0.005, 0.05,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
1.0,
|
|
156
|
-
5.0,
|
|
157
|
-
),
|
|
158
|
-
clamp(bitcast<f32>(light.kind_and_shadow.z), 0.0, 1.0),
|
|
159
|
-
) * projector;
|
|
160
|
-
}
|
|
161
|
-
if (tile == PROJECTOR_ONLY_TILE) {
|
|
162
|
-
let projector = sampleClusterSpotProjector(view.spotLightViewProj[0], world_pos);
|
|
163
|
-
return evalSpot(
|
|
164
|
-
light.position.xyz, light.direction.xyz, light.color.xyz,
|
|
165
|
-
light.color.w, light.direction.w, light.position.w,
|
|
166
|
-
world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
167
|
-
) * projector;
|
|
189
|
+
bitcast<f32>(light.kind_and_shadow.w),
|
|
190
|
+
bitcast<f32>(light.kind_and_shadow.z),
|
|
191
|
+
);
|
|
168
192
|
}
|
|
169
193
|
return evalSpot(
|
|
170
194
|
light.position.xyz, light.direction.xyz, light.color.xyz,
|
|
@@ -175,7 +199,7 @@ fn evaluate_cluster_light(
|
|
|
175
199
|
return vec3<f32>(0.0);
|
|
176
200
|
}
|
|
177
201
|
|
|
178
|
-
fn
|
|
202
|
+
fn evaluateStandardClusterLights(
|
|
179
203
|
ndc : vec3<f32>,
|
|
180
204
|
view_z : f32,
|
|
181
205
|
world_pos : vec3<f32>,
|
|
@@ -185,6 +209,7 @@ fn evaluate_cluster_lights(
|
|
|
185
209
|
metallic : f32,
|
|
186
210
|
alpha_sq : f32,
|
|
187
211
|
f0 : vec3<f32>,
|
|
212
|
+
flat_2d : bool,
|
|
188
213
|
) -> vec3<f32> {
|
|
189
214
|
let gx = cluster_uniform.grid.x;
|
|
190
215
|
let gy = cluster_uniform.grid.y;
|
|
@@ -194,7 +219,6 @@ fn evaluate_cluster_lights(
|
|
|
194
219
|
let log_far = cluster_uniform.near_far_log.z;
|
|
195
220
|
var total_radiance = vec3<f32>(0.0);
|
|
196
221
|
|
|
197
|
-
#if STORAGE_BUFFER_AVAILABLE == true
|
|
198
222
|
let cluster_idx = ndc_position_to_cluster(ndc, view_z, gx, gy, gz, near, far, log_far);
|
|
199
223
|
let cluster_linear = cluster_idx.z * gy * gx + cluster_idx.y * gx + cluster_idx.x;
|
|
200
224
|
let grid_offset = cluster_linear * 2u;
|
|
@@ -204,20 +228,9 @@ fn evaluate_cluster_lights(
|
|
|
204
228
|
let light = light_data[light_index_list[list_offset + i]];
|
|
205
229
|
total_radiance += evaluate_cluster_light(
|
|
206
230
|
light, world_pos, normal, view_dir, base_color, metallic, alpha_sq, f0,
|
|
231
|
+
flat_2d,
|
|
207
232
|
);
|
|
208
233
|
}
|
|
209
|
-
#else
|
|
210
|
-
let light_count = min(cluster_uniform.grid.w, 128u);
|
|
211
|
-
for (var i = 0u; i < 128u; i = i + 1u) {
|
|
212
|
-
if (i >= light_count) {
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
total_radiance += evaluate_cluster_light(
|
|
216
|
-
light_data_uniform[i], world_pos, normal, view_dir,
|
|
217
|
-
base_color, metallic, alpha_sq, f0,
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
#endif
|
|
221
234
|
return total_radiance;
|
|
222
235
|
}
|
|
223
236
|
|
package/src/tonemap.wgsl
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
#import forgeax_view::common::FullscreenOutput
|
|
11
11
|
#import forgeax_view::common::fullscreen_triangle
|
|
12
|
-
#import forgeax_view::common::linearToSrgbOetf
|
|
12
|
+
#import forgeax_view::common::{linearToSrgbOetf, ditherUnorm8}
|
|
13
13
|
|
|
14
14
|
const TONEMAP_LUMINANCE_EPSILON : f32 = 1e-5;
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ struct TonemapParams {
|
|
|
17
17
|
exposure : f32,
|
|
18
18
|
whitePoint : f32,
|
|
19
19
|
mode : u32,
|
|
20
|
-
|
|
20
|
+
ditherEnabled : f32,
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
@group(1) @binding(0) var hdr : texture_2d<f32>;
|
|
@@ -171,5 +171,10 @@ fn fs_main(in : FullscreenOutput) -> @location(0) vec4<f32> {
|
|
|
171
171
|
}
|
|
172
172
|
// Every public mode reaches exactly one shared sRGB OETF.
|
|
173
173
|
let encoded = linearToSrgbOetf(mapped);
|
|
174
|
-
|
|
174
|
+
let output = select(
|
|
175
|
+
encoded,
|
|
176
|
+
ditherUnorm8(encoded, in.position.xy),
|
|
177
|
+
params.ditherEnabled > 0.5,
|
|
178
|
+
);
|
|
179
|
+
return vec4<f32>(output, source.a);
|
|
175
180
|
}
|
|
@@ -9,7 +9,8 @@ struct View {
|
|
|
9
9
|
worldViewProj : mat4x4<f32>, lightDir : vec3<f32>, lightColor : vec3<f32>, cameraPos : vec3<f32>,
|
|
10
10
|
lightViewProj_A : mat4x4<f32>, inverseViewProj : mat4x4<f32>, lightViewProj_B : mat4x4<f32>,
|
|
11
11
|
lightViewProj_C : mat4x4<f32>, lightViewProj_D : mat4x4<f32>, splitPlanes : array<vec4<f32>,4>,
|
|
12
|
-
cascadeCount : f32, cascadeBlend : f32, depthBias : f32, normalBias : f32,
|
|
12
|
+
cascadeCount : f32, cascadeBlend : f32, depthBias : f32, normalBias : f32,
|
|
13
|
+
directionalShadowFilter : vec4<f32>,
|
|
13
14
|
spotLightViewProj : array<mat4x4<f32>,4>, temporalCurrentViewProj : mat4x4<f32>,
|
|
14
15
|
temporalPreviousViewProj : mat4x4<f32>, temporalProjection : vec4<f32>, temporalPreviousCameraPos : vec4<f32>,
|
|
15
16
|
};
|
|
@@ -143,9 +144,20 @@ fn volume_cascade(world_position : vec3<f32>, view_depth : f32) -> f32 {
|
|
|
143
144
|
if (!(tile_uv.x >= 0.0 && tile_uv.x <= 1.0 && tile_uv.y >= 0.0 && tile_uv.y <= 1.0 && projected.z <= 1.0)) { return 1.0; }
|
|
144
145
|
let uv = tile_uv * tile_scale + vec2<f32>(tile) * tile_scale;
|
|
145
146
|
let shadow_size = vec2<f32>(textureDimensions(shadowMap));
|
|
147
|
+
let filter_profile = clamp(u32(round(view.directionalShadowFilter.x)), 1u, 5u);
|
|
148
|
+
// Volumetric injection currently owns a depth-only PCF receiver. Directional
|
|
149
|
+
// PCSS profiles remain an explicit capability fallback here: the surface
|
|
150
|
+
// directional owner performs blocker search, while volume keeps the same
|
|
151
|
+
// accepted filter carrier and uses the stable PCF3 receiver instead of
|
|
152
|
+
// misreading profiles 4/5 as a kernel width.
|
|
153
|
+
let volume_kernel = select(
|
|
154
|
+
select(select(3.0, 5.0, filter_profile == 3u), 1.0, filter_profile == 1u),
|
|
155
|
+
3.0,
|
|
156
|
+
filter_profile >= 4u,
|
|
157
|
+
);
|
|
146
158
|
return sample_shadow_2d_kernel(
|
|
147
159
|
shadowMap, shadowSampler, uv, 1.0 / shadow_size, projected.z,
|
|
148
|
-
0.0, view.depthBias, 1.0,
|
|
160
|
+
0.0, view.depthBias, 1.0, volume_kernel,
|
|
149
161
|
);
|
|
150
162
|
}
|
|
151
163
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#define_import_path forgeax_view::volume_integrate
|
|
2
2
|
// forgeax_pbr::lighting_punctual remains the surface contract; the isolated
|
|
3
3
|
// attenuation module below provides its resource-free volume equivalents.
|
|
4
|
+
#import forgeax_pbr::lighting_attenuation::{projectSpotUv}
|
|
4
5
|
|
|
5
6
|
struct VolumeView {
|
|
6
7
|
_prefix : array<vec4<f32>, 6>,
|
|
@@ -106,12 +107,6 @@ fn evalSpotAttenuation(
|
|
|
106
107
|
return distance * smoothstep(cosOuter, cosInner, dot(direction, -normalize(lightDir)));
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
fn projectSpotUv(lightViewProj : mat4x4<f32>, worldPos : vec3<f32>) -> vec2<f32> {
|
|
110
|
-
let clip = lightViewProj * vec4<f32>(worldPos, 1.0);
|
|
111
|
-
let invW = select(1.0 / clip.w, 0.0, abs(clip.w) < 1e-6);
|
|
112
|
-
return vec2<f32>(clip.x * invW * 0.5 + 0.5, clip.y * invW * -0.5 + 0.5);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
110
|
fn spotProjectorMatrix(light : SpotLight) -> mat4x4<f32> {
|
|
116
111
|
if (light.shadowAtlasTile >= 0) {
|
|
117
112
|
return volume_view.spotLightViewProj[light.shadowAtlasTile];
|