@forgeax/engine-shader 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +99 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/ShaderRegistry.d.ts +208 -0
- package/dist/ShaderRegistry.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-background.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-background.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham-red.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-preetham.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-preetham.test.d.ts.map +1 -0
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts +2 -0
- package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts.map +1 -0
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts +2 -0
- package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts.map +1 -0
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts +2 -0
- package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts.map +1 -0
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts +2 -0
- package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts.map +1 -0
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts +2 -0
- package/dist/__tests__/deferred-lighting-ssao.test.d.ts.map +1 -0
- package/dist/__tests__/fog-oracle.unit.test.d.ts +2 -0
- package/dist/__tests__/fog-oracle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts +2 -0
- package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-producer-matrix.integration.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts +2 -0
- package/dist/__tests__/hdrp-ssao-compile.test.d.ts.map +1 -0
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl-irradiance.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ibl-sampling.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl-sampling.unit.test.d.ts.map +1 -0
- package/dist/__tests__/lighting-punctual.unit.test.d.ts +2 -0
- package/dist/__tests__/lighting-punctual.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts +2 -0
- package/dist/__tests__/material-artifact-dedup.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-builtins.unit.test.d.ts +2 -0
- package/dist/__tests__/material-builtins.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/material-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts +2 -0
- package/dist/__tests__/material-derived-builtins.integration.test.d.ts.map +1 -0
- package/dist/__tests__/public-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/public-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-temporal.unit.test.d.ts +2 -0
- package/dist/__tests__/scene-temporal.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts +2 -0
- package/dist/__tests__/shader-registry-transaction.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shader.unit.test.d.ts +2 -0
- package/dist/__tests__/shader.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-lit-shader.test.d.ts +2 -0
- package/dist/__tests__/sprite-lit-shader.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-variants.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-variants.unit.test.d.ts.map +1 -0
- package/dist/__tests__/taa-resolve.unit.test.d.ts +2 -0
- package/dist/__tests__/taa-resolve.unit.test.d.ts.map +1 -0
- package/dist/__tests__/transparent-pbr.unit.test.d.ts +2 -0
- package/dist/__tests__/transparent-pbr.unit.test.d.ts.map +1 -0
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts +2 -0
- package/dist/__tests__/vertex-color-variant.unit.test.d.ts.map +1 -0
- package/dist/errors.d.ts +96 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +560 -0
- package/dist/index.mjs.map +1 -0
- package/dist/material/artifact-registry.d.ts +31 -0
- package/dist/material/artifact-registry.d.ts.map +1 -0
- package/dist/material/artifact-types.d.ts +13 -0
- package/dist/material/artifact-types.d.ts.map +1 -0
- package/dist/material-schemas.d.ts +8 -0
- package/dist/material-schemas.d.ts.map +1 -0
- package/dist/register-default-sprite-lit.d.ts +40 -0
- package/dist/register-default-sprite-lit.d.ts.map +1 -0
- package/dist/register-default-standard-pbr-skin.d.ts +34 -0
- package/dist/register-default-standard-pbr-skin.d.ts.map +1 -0
- package/dist/tonemap.d.ts +13 -0
- package/dist/tonemap.d.ts.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/ShaderRegistry.ts +519 -0
- package/src/__tests__/atmosphere-background.test.ts +33 -0
- package/src/__tests__/atmosphere-cubemap-orientation.test.ts +68 -0
- package/src/__tests__/atmosphere-preetham-numeric.test.ts +122 -0
- package/src/__tests__/atmosphere-preetham-red.test.ts +52 -0
- package/src/__tests__/atmosphere-preetham.test.ts +132 -0
- package/src/__tests__/atmosphere-three-r184-reference.test.ts +122 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +31 -0
- package/src/__tests__/builtin-texture-sampling-contract.test.ts +56 -0
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +19 -0
- package/src/__tests__/deferred-lighting-ssao.test.ts +114 -0
- package/src/__tests__/fog-oracle.unit.test.ts +132 -0
- package/src/__tests__/fog-orthographic-ray.unit.test.ts +62 -0
- package/src/__tests__/fog-producer-matrix.integration.test.ts +77 -0
- package/src/__tests__/hdrp-ssao-compile.test.ts +185 -0
- package/src/__tests__/ibl-irradiance.unit.test.ts +43 -0
- package/src/__tests__/ibl-sampling.unit.test.ts +40 -0
- package/src/__tests__/lighting-punctual.unit.test.ts +66 -0
- package/src/__tests__/manifest.fixture.json +22 -0
- package/src/__tests__/material-artifact-dedup.unit.test.ts +21 -0
- package/src/__tests__/material-builtins.unit.test.ts +111 -0
- package/src/__tests__/material-contract.unit.test.ts +79 -0
- package/src/__tests__/material-derived-builtins.integration.test.ts +30 -0
- package/src/__tests__/public-surface.unit.test.ts +9 -0
- package/src/__tests__/scene-temporal.unit.test.ts +32 -0
- package/src/__tests__/shader-registry-transaction.unit.test.ts +116 -0
- package/src/__tests__/shader.unit.test.ts +1909 -0
- package/src/__tests__/sprite-lit-shader.test.ts +240 -0
- package/src/__tests__/sprite-variants.unit.test.ts +209 -0
- package/src/__tests__/taa-resolve.unit.test.ts +32 -0
- package/src/__tests__/transparent-pbr.unit.test.ts +21 -0
- package/src/__tests__/vertex-color-variant.unit.test.ts +52 -0
- package/src/atmosphere-background.wgsl +80 -0
- package/src/atmosphere-cubemap.wgsl +72 -0
- package/src/atmosphere-ibl.wgsl +127 -0
- package/src/atmosphere-preetham.wgsl +214 -0
- package/src/bloom-blur.wgsl +89 -0
- package/src/bloom-bright.wgsl +50 -0
- package/src/bloom-composite.wgsl +54 -0
- package/src/brdf.wgsl +25 -0
- package/src/common.wgsl +523 -0
- package/src/default-standard-pbr-skin.wgsl +538 -0
- package/src/default-standard-pbr.wgsl +794 -0
- package/src/errors.ts +169 -0
- package/src/fog.wgsl +27 -0
- package/src/fxaa.wgsl +259 -0
- package/src/hdrp-cluster-forward.wgsl +186 -0
- package/src/hdrp-deferred-lighting.wgsl +69 -0
- package/src/hdrp-ssao.wgsl +174 -0
- package/src/ibl-brdf-lut.wgsl +92 -0
- package/src/ibl-equirect-to-cube.wgsl +56 -0
- package/src/ibl-irradiance.wgsl +89 -0
- package/src/ibl-prefilter.wgsl +101 -0
- package/src/ibl-sampling.wgsl +58 -0
- package/src/ibl-shared.wgsl +118 -0
- package/src/index.ts +166 -0
- package/src/lighting-directional.wgsl +471 -0
- package/src/lighting-punctual.wgsl +274 -0
- package/src/material/artifact-registry.ts +78 -0
- package/src/material/artifact-types.ts +26 -0
- package/src/material-schemas.ts +48 -0
- package/src/msdf-text.wgsl +208 -0
- package/src/pbr-temporal.wgsl +73 -0
- package/src/register-default-sprite-lit.ts +87 -0
- package/src/register-default-standard-pbr-skin.ts +74 -0
- package/src/shadow-pcf.wgsl +120 -0
- package/src/shadow_caster.wgsl +54 -0
- package/src/skybox.wgsl +75 -0
- package/src/sprite-lit.wgsl +307 -0
- package/src/sprite.wgsl +365 -0
- package/src/taa-resolve.wgsl +129 -0
- package/src/tbn.wgsl +54 -0
- package/src/tonemap.ts +13 -0
- package/src/tonemap.wgsl +177 -0
- package/src/types.ts +59 -0
- package/src/unlit.wgsl +186 -0
package/src/common.wgsl
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
#define_import_path forgeax_view::common
|
|
2
|
+
|
|
3
|
+
// The texture view owns the texture transfer: an sRGB view decodes once during
|
|
4
|
+
// textureSample, while a linear view is returned unchanged. Authored scalar
|
|
5
|
+
// values are decoded by the material extraction path before the uniform upload.
|
|
6
|
+
fn sampleMaterialTextureLinear(
|
|
7
|
+
texture : texture_2d<f32>,
|
|
8
|
+
textureSampler : sampler,
|
|
9
|
+
uv : vec2<f32>,
|
|
10
|
+
uvScale : vec2<f32>,
|
|
11
|
+
) -> vec4<f32> {
|
|
12
|
+
return textureSample(texture, textureSampler, uv * uvScale);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Keep the established helper for the other material paths; the explicit
|
|
16
|
+
// linear name is the owner-facing entry point for the unlit parity path.
|
|
17
|
+
fn sampleMaterialTexture(
|
|
18
|
+
texture : texture_2d<f32>,
|
|
19
|
+
textureSampler : sampler,
|
|
20
|
+
uv : vec2<f32>,
|
|
21
|
+
uvScale : vec2<f32>,
|
|
22
|
+
) -> vec4<f32> {
|
|
23
|
+
return sampleMaterialTextureLinear(texture, textureSampler, uv, uvScale);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Shared linear-LDR -> display-sRGB OETF for raw swap-chain storage writes.
|
|
27
|
+
// sRGB texture views perform this conversion on attachment writes, but the
|
|
28
|
+
// native output and FXAA passes intentionally target the non-sRGB storage view.
|
|
29
|
+
fn linearToSrgbOetf(color : vec3<f32>) -> vec3<f32> {
|
|
30
|
+
let safe = max(color, vec3<f32>(0.0));
|
|
31
|
+
let high = pow(safe, vec3<f32>(0.41666)) * vec3<f32>(1.055) - vec3<f32>(0.055);
|
|
32
|
+
let low = safe * vec3<f32>(12.92);
|
|
33
|
+
return select(high, low, safe <= vec3<f32>(0.0031308));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// @forgeax/engine-shader - common.wgsl (M5 T-18 feat-20260512-naga-oil-composition-hmr).
|
|
37
|
+
//
|
|
38
|
+
// Shared view + mesh structs extracted from pbr.wgsl + unlit.wgsl via
|
|
39
|
+
// naga_oil #define_import_path / #import (D-04 moduleId convention). Both
|
|
40
|
+
// material shaders pull View + Mesh from here so the RenderSystem can
|
|
41
|
+
// uniform-upload view + mesh storage once per frame for all pipelines.
|
|
42
|
+
//
|
|
43
|
+
// `View` carries the full PBR-superset field set (worldViewProj + light +
|
|
44
|
+
// cameraPos). Unlit only reads `worldViewProj`; the extra fields stay bound
|
|
45
|
+
// but unused in the unlit path (zero perf cost because the fragment shader
|
|
46
|
+
// never references them). This is the canonical "superset struct" pattern
|
|
47
|
+
// aligned with Bevy's bevy_view::common and matches charter proposition 5
|
|
48
|
+
// (consistent abstraction - one View struct everywhere).
|
|
49
|
+
|
|
50
|
+
// View UBO byte layout (592 B std140):
|
|
51
|
+
// [ 0.. 64) worldViewProj mat4x4<f32> (align 16, size 64)
|
|
52
|
+
// [ 64.. 80) lightDir vec3<f32> (align 16, 12+4 pad)
|
|
53
|
+
// [ 80.. 96) lightColor vec3<f32> (align 16, 12+4 pad)
|
|
54
|
+
// [ 96..112) cameraPos vec3<f32> (align 16, 12+4 pad)
|
|
55
|
+
// [112..176) lightViewProj_A mat4x4<f32> (align 16, size 64)
|
|
56
|
+
// [176..240) inverseViewProj mat4x4<f32> (align 16, size 64)
|
|
57
|
+
// [240..304) lightViewProj_B mat4x4<f32> (align 16, size 64)
|
|
58
|
+
// [304..368) lightViewProj_C mat4x4<f32> (align 16, size 64)
|
|
59
|
+
// [368..432) lightViewProj_D mat4x4<f32> (align 16, size 64)
|
|
60
|
+
// [432..448) splitPlanes[0] vec4<f32> (align 16, 16; .x=depth)
|
|
61
|
+
// [448..464) splitPlanes[1] vec4<f32> (align 16, 16)
|
|
62
|
+
// [464..480) splitPlanes[2] vec4<f32> (align 16, 16)
|
|
63
|
+
// [480..496) splitPlanes[3] vec4<f32> (align 16, 16)
|
|
64
|
+
// [496..500) cascadeCount f32 (align 4, size 4)
|
|
65
|
+
// [500..504) cascadeBlend f32 (align 4, size 4)
|
|
66
|
+
// [504..508) depthBias f32 (align 4, size 4)
|
|
67
|
+
// [508..512) normalBias f32 (align 4, size 4)
|
|
68
|
+
// [512..516) pcfKernelSize f32 (align 4, size 4)
|
|
69
|
+
// [516..528) _align_pad — (mat4 array align=16, 12 B)
|
|
70
|
+
// [528..784) spotLightViewProj array<mat4x4<f32>,4> (align 16, size 256)
|
|
71
|
+
// WGSL prefix = 784 B. Host UBO = VIEW_UBO_BYTES = 960 B (renderer-factory.ts);
|
|
72
|
+
// view-ubo.ts writes the full 240 f32 payload. The spot matrix
|
|
73
|
+
// array fold (feat-20260625 w25) removes the standalone @group(0) binding 9
|
|
74
|
+
// uniform buffer so the WebGL2 fallback fragment uniform-buffer count returns
|
|
75
|
+
// to 11 (GLES 3.0 max).
|
|
76
|
+
// temporal projection facts and Fog payload append after the spot matrix;
|
|
77
|
+
// total = 960 B.
|
|
78
|
+
//
|
|
79
|
+
// Field order must stay byte-for-byte identical to every prior release
|
|
80
|
+
// (charter P4 consistent abstraction); new fields append at the tail.
|
|
81
|
+
// Host write in view-ubo.ts builds the 240-float payload and the renderer
|
|
82
|
+
// allocates VIEW_UBO_BYTES = 960.
|
|
83
|
+
//
|
|
84
|
+
// feat-20260531-skybox-env-background M2 / w3: inverseViewProj appended
|
|
85
|
+
// at the tail (64 B mat4 at byte offset 176). The host pre-computes
|
|
86
|
+
// mat4.invert(inverseViewProj, worldViewProj) so the skybox fragment
|
|
87
|
+
// shader can reconstruct world-space view direction without per-pixel
|
|
88
|
+
// matrix inversion.
|
|
89
|
+
//
|
|
90
|
+
// feat-20260613-csm-cascaded-shadow-maps M4 / w16 + M5 / w28:
|
|
91
|
+
// the legacy single-cascade light-space matrix is replaced by a 4-cascade
|
|
92
|
+
// `lightViewProj_A..D` array at offset 112 (inverseViewProj stays at 176;
|
|
93
|
+
// `lightViewProj_B..D` + `splitPlanes` + cascadeCount/cascadeBlend follow
|
|
94
|
+
// inverseViewProj). WGSL struct is 512 B (auto-padded tail); the host
|
|
95
|
+
// allocates the larger View slot and writes trailing zero lanes to satisfy
|
|
96
|
+
// the AC-08 fixed-UBO-size invariant (the extra bytes are never read).
|
|
97
|
+
// shadow_caster.wgsl indexes the 4 fields via `shadowCasterCascade.index`
|
|
98
|
+
// (binding 5, written per shadow pass).
|
|
99
|
+
//
|
|
100
|
+
// feat-20260621-merge-directionallightshadow-into-directionallight M3 / m3-t3:
|
|
101
|
+
// the merged DirectionalLight's shadow bias + PCF kernel width append at the
|
|
102
|
+
// tail (depthBias / normalBias / pcfKernelSize at bytes 504/508/512, floats
|
|
103
|
+
// 126/127/128). Tail-append only -- field order is byte-for-byte stable
|
|
104
|
+
// (charter P4); the prior 88 B host tail pad shrinks to 64 B, total stays
|
|
105
|
+
// View slot remains within the existing 1024 B stride. lighting-directional.wgsl drives the
|
|
106
|
+
// directional shadow bias (D-1: bias = max(normalBias*(1-N.L), depthBias)) and
|
|
107
|
+
// a pcfKernelSize-wide PCF loop from these fields.
|
|
108
|
+
//
|
|
109
|
+
// feat-20260625-spot-light-shadow-mapping w25 (scope-amend webkit-fallback):
|
|
110
|
+
// the per-spot perspective `spotLightViewProj` matrices (4 lanes, fragment-read)
|
|
111
|
+
// fold into the View UBO tail instead of a standalone @group(0) binding 9
|
|
112
|
+
// uniform buffer. The standalone binding pushed the fragment-stage uniform
|
|
113
|
+
// buffer count to 12 (view + pointLights + spotLights + shadowParams +
|
|
114
|
+
// shadowCasterCascade + spotLightViewProj on the WebGL2 fallback path),
|
|
115
|
+
// exceeding GLES 3.0's `max_uniform_buffers_per_shader_stage = 11` so the
|
|
116
|
+
// WebGL2 fallback (mobile / WebKit — this feat's compat target) crashed
|
|
117
|
+
// pipeline-layout creation. Folding into the View UBO costs zero new bindings
|
|
118
|
+
// (the count drops back to 11). D-1 rejected the View UBO for the CASTER vertex
|
|
119
|
+
// channel (binding 7) because directional + spot casters both need a light-view
|
|
120
|
+
// matrix in the same frame (same-frame write contention); the FRAGMENT read
|
|
121
|
+
// channel has no such contention — the host writes all <=4 spot matrices once
|
|
122
|
+
// per frame before the forward pass, so the fold is safe. mat4 array align=16:
|
|
123
|
+
// the array lands at byte 528 (next 16 B-aligned offset after pcfKernelSize at
|
|
124
|
+
// byte 512..516), spanning bytes 528..784. The host allocates VIEW_UBO_BYTES =
|
|
125
|
+
// 960 (renderer-factory.ts) and writes the full 240 f32
|
|
126
|
+
// payload (render-system-record.ts). Lane N = the spot with `shadowAtlasTile
|
|
127
|
+
// === N` (cap = 4); `evalSpotShadowed` reads `view.spotLightViewProj[tile]`.
|
|
128
|
+
struct FogViewParams {
|
|
129
|
+
color : vec3<f32>,
|
|
130
|
+
density : f32,
|
|
131
|
+
heightFalloff : f32,
|
|
132
|
+
maxOpacity : f32,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
struct FogRay {
|
|
136
|
+
origin : vec3<f32>,
|
|
137
|
+
direction : vec3<f32>,
|
|
138
|
+
distance : f32,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
struct View {
|
|
142
|
+
worldViewProj : mat4x4<f32>,
|
|
143
|
+
lightDir : vec3<f32>,
|
|
144
|
+
lightColor : vec3<f32>,
|
|
145
|
+
cameraPos : vec3<f32>,
|
|
146
|
+
lightViewProj_A : mat4x4<f32>,
|
|
147
|
+
inverseViewProj : mat4x4<f32>,
|
|
148
|
+
lightViewProj_B : mat4x4<f32>,
|
|
149
|
+
lightViewProj_C : mat4x4<f32>,
|
|
150
|
+
lightViewProj_D : mat4x4<f32>,
|
|
151
|
+
splitPlanes : array<vec4<f32>,4>,
|
|
152
|
+
cascadeCount : f32,
|
|
153
|
+
cascadeBlend : f32,
|
|
154
|
+
depthBias : f32,
|
|
155
|
+
normalBias : f32,
|
|
156
|
+
pcfKernelSize : f32,
|
|
157
|
+
// feat-20260625-spot-light-shadow-mapping w25: per-spot perspective
|
|
158
|
+
// light-view-projection matrices, fragment-read. Auto-aligned to byte 528
|
|
159
|
+
// (mat4 array align=16, after pcfKernelSize at byte 512); spans bytes
|
|
160
|
+
// 528..784. Lane N = spot with shadowAtlasTile === N (cap = 4). Zeroed lanes
|
|
161
|
+
// are safe (sample gated on shadowAtlasTile >= 0 in default-standard-pbr.wgsl).
|
|
162
|
+
spotLightViewProj : array<mat4x4<f32>, 4>,
|
|
163
|
+
temporalCurrentViewProj : mat4x4<f32>,
|
|
164
|
+
temporalPreviousViewProj : mat4x4<f32>,
|
|
165
|
+
// x = near, y = far, z = 1 for orthographic and 0 for perspective.
|
|
166
|
+
temporalProjection : vec4<f32>,
|
|
167
|
+
fog : FogViewParams,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Per-instance mesh slot (feat-20260518-pbr-direct-lighting-mvp M2 / w8.5,
|
|
171
|
+
// plan-strategy D-5 + AC-08). `normalMatrix` is the host-precomputed
|
|
172
|
+
// transpose(inverse(mat3(worldFromLocal))) so the fragment stage avoids the
|
|
173
|
+
// 9-mul shader-side inverse path; per-frame per-entity CPU computation
|
|
174
|
+
// (single mat4 -> mat3 -> invert+transpose) is one-shot and feeds the
|
|
175
|
+
// world-space normal mapping path in pbr.wgsl. Storage layout: mat4 in
|
|
176
|
+
// [0, 64), mat3 columns in [64, 112) padded as three vec4 (16 B each = 48 B
|
|
177
|
+
// total), trailing slack inside the PER_ENTITY_STRIDE = 256 B slot.
|
|
178
|
+
struct Mesh {
|
|
179
|
+
worldFromLocal : mat4x4<f32>,
|
|
180
|
+
normalMatrix : mat3x3<f32>,
|
|
181
|
+
#if STORAGE_BUFFER_AVAILABLE == true
|
|
182
|
+
previousWorldFromLocal : mat4x4<f32>,
|
|
183
|
+
// x = conservative reactive mask. The remaining lanes are reserved and
|
|
184
|
+
// zero so one vec4 remains the sole host/shader metadata ABI.
|
|
185
|
+
temporal : vec4<f32>,
|
|
186
|
+
#endif
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// feat-20260519-light-casters-point-spot-pbr M4 / w21 (D-S1 + D-S2 +
|
|
190
|
+
// AC-04 b/c + AC-05 binding declaration). Punctual-light std430 storage
|
|
191
|
+
// types byte-for-byte mirror packPointLight / packSpotLight in
|
|
192
|
+
// `packages/runtime/src/light-buffer-layout.ts`:
|
|
193
|
+
//
|
|
194
|
+
// PointLight (32 B / 8 floats):
|
|
195
|
+
// [ 0..2 ] position vec3<f32>
|
|
196
|
+
// [ 3 ] invRangeSquared f32 (Bevy color_inverse_square_range.w; 0
|
|
197
|
+
// collapses range falloff to a pure 1/d^2 inverse-square law)
|
|
198
|
+
// [ 4..6 ] colorTimesIntensity vec3<f32> (host pre-multiplied
|
|
199
|
+
// color * intensity so the shader avoids the per-fragment mul)
|
|
200
|
+
// [ 7 ] pad f32 = 0
|
|
201
|
+
//
|
|
202
|
+
// SpotLight (64 B / 16 floats):
|
|
203
|
+
// [ 0..2 ] position vec3<f32>
|
|
204
|
+
// [ 3 ] invRangeSquared f32
|
|
205
|
+
// [ 4..6 ] colorTimesIntensity vec3<f32>
|
|
206
|
+
// [ 7 ] cosInner f32 (cos of half-angle inner cone; KHR
|
|
207
|
+
// smoothstep falloff anchor)
|
|
208
|
+
// [ 8..10] direction vec3<f32> (raw outgoing vector; shader reads
|
|
209
|
+
// via dot(L, -direction) for cone angle test)
|
|
210
|
+
// [ 11 ] cosOuter f32
|
|
211
|
+
// [12..14] spotPad vec3<f32> = 0 (std430 vec4-alignment padding)
|
|
212
|
+
// [ 15 ] shadowAtlasTile i32 (sentinel -1 = unassigned/clipped,
|
|
213
|
+
// 0..3 = spot atlas tile index; feat-20260625 M2/M3 D-4)
|
|
214
|
+
//
|
|
215
|
+
// WGSL std430 alignment audit: vec3<f32> alignof=16 / sizeof=12. The
|
|
216
|
+
// f32 lane wedged immediately after each vec3 fills the 4 B remainder
|
|
217
|
+
// (WGSL struct member rule: next.offset = roundUp(prev.offset +
|
|
218
|
+
// prev.size, this.alignof) - for f32 alignof=4 the round-up is a
|
|
219
|
+
// no-op, so position[3] / color[3] / direction[3] sit at byte
|
|
220
|
+
// offsets 12 / 28 / 44 with zero internal padding). PointLight stays
|
|
221
|
+
// 32 B; SpotLight grows to 64 B (feat-20260625 M2/M3 D-4): the trailing
|
|
222
|
+
// `spotPad: vec3<f32>` + `shadowAtlasTile: i32` form one vec4-aligned block
|
|
223
|
+
// at bytes 48..64, with `shadowAtlasTile` at byte 60. These struct sizes
|
|
224
|
+
// match the host packers (packPointLight 32 B / packSpotLight 64 B) exactly.
|
|
225
|
+
//
|
|
226
|
+
// Header `count: u32` lives in a wrapper struct with the trailing
|
|
227
|
+
// runtime-sized array. Array-of-vec4-aligned-struct alignof = 16, so
|
|
228
|
+
// `slots` starts at byte 16 (count u32 occupies 0..4; bytes 4..16 are
|
|
229
|
+
// implicit padding). This matches `packLightArrayHeader`'s 16 B
|
|
230
|
+
// (count u32 + 12 B pad) layout in light-buffer-layout.ts.
|
|
231
|
+
|
|
232
|
+
struct PointLight {
|
|
233
|
+
position : vec3<f32>,
|
|
234
|
+
invRangeSquared : f32,
|
|
235
|
+
colorTimesIntensity : vec3<f32>,
|
|
236
|
+
// feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-8 (plan-strategy §D-2):
|
|
237
|
+
// Repurposes the prior `pointPadW` (f32 zero-pad) lane as `shadowAtlasLayer`
|
|
238
|
+
// (i32 sentinel, -1 = no shadow, 0..3 = cube_array atlas layer index for
|
|
239
|
+
// shadow-casting point lights). Cap = 4 enforced by the PointLightShadow
|
|
240
|
+
// ECS component cardinality. The shader path samples the cube_array atlas
|
|
241
|
+
// (binding 5) only when `shadowAtlasLayer >= 0`. Field name is unique to
|
|
242
|
+
// this struct (no `pad0`/`pad1` collision with the pbr Material struct
|
|
243
|
+
// under naga_oil writeback substitution).
|
|
244
|
+
shadowAtlasLayer : i32,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
struct SpotLight {
|
|
248
|
+
position : vec3<f32>,
|
|
249
|
+
invRangeSquared : f32,
|
|
250
|
+
colorTimesIntensity : vec3<f32>,
|
|
251
|
+
cosInner : f32,
|
|
252
|
+
direction : vec3<f32>,
|
|
253
|
+
cosOuter : f32,
|
|
254
|
+
// feat-20260625-spot-light-shadow-mapping M3 / w13 (plan-strategy D-4):
|
|
255
|
+
// The prior 8-lane (48 B) SpotLight had no spare pad lane (unlike PointLight
|
|
256
|
+
// which repurposed `pointPadW`), so the clip-signal field needs a fresh
|
|
257
|
+
// vec4-aligned block. `spotPad` fills bytes 48..60 (host packSpotLight slots
|
|
258
|
+
// 12..14 stay zero); `shadowAtlasTile` (i32 sentinel, -1 = no shadow /
|
|
259
|
+
// clipped, 0..3 = spot atlas tile) lands at byte 60 (host slot 15, written
|
|
260
|
+
// through an Int32Array view). The fragment path gates on
|
|
261
|
+
// `shadowAtlasTile >= 0` (evalSpotShadowed vs evalSpot). Field names are
|
|
262
|
+
// unique across the composed module surface so naga_oil writeback
|
|
263
|
+
// substitution does not collide with prior `pad0` members. (naga_oil
|
|
264
|
+
// reserves the `__` identifier prefix, so the pad lane is `spotPad`, not
|
|
265
|
+
// `__pad`.)
|
|
266
|
+
spotPad : vec3<f32>,
|
|
267
|
+
shadowAtlasTile : i32,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
struct PointLightsArray {
|
|
271
|
+
count : u32,
|
|
272
|
+
slots : array<PointLight, 4>,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
struct SpotLightsArray {
|
|
276
|
+
count : u32,
|
|
277
|
+
slots : array<SpotLight, 4>,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
@group(0) @binding(0) var<uniform> view : View;
|
|
281
|
+
#if STORAGE_BUFFER_AVAILABLE == true
|
|
282
|
+
@group(0) @binding(1) var<storage, read> pointLightsBuffer : PointLightsArray;
|
|
283
|
+
@group(0) @binding(2) var<storage, read> spotLightsBuffer : SpotLightsArray;
|
|
284
|
+
#else
|
|
285
|
+
@group(0) @binding(1) var<uniform> pointLightsBuffer : PointLightsArray;
|
|
286
|
+
@group(0) @binding(2) var<uniform> spotLightsBuffer : SpotLightsArray;
|
|
287
|
+
#endif
|
|
288
|
+
// feat-20260520-directional-light-shadow-mapping M3 / w16 (D-1 / plan-strategy §8.1):
|
|
289
|
+
// shadowMap + shadowSampler consume @group(0) bindings 3/4 (smallest unused
|
|
290
|
+
// slots adjacent to view UBO at 0). M3 uses 3x3 PCF with textureLoad on
|
|
291
|
+
// shadowMap for 9-tap depth sampling; shadowSampler retained for GPU probe.
|
|
292
|
+
//
|
|
293
|
+
// feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-8 (plan-strategy §D-1 + §D-8):
|
|
294
|
+
// Binding 5 declares the cube_array shadow atlas (texture_depth_cube_array,
|
|
295
|
+
// layers=4, depth32float; one cube per shadow-casting point light, cap=4).
|
|
296
|
+
// Binding 6 declares the per-light shadow params buffer (URP only — proj
|
|
297
|
+
// constants for cube depth-ref reconstruction); HDRP rides the same constants
|
|
298
|
+
// on LightSlot pad lanes (.kind_and_pad.wyz) so binding 6 stays unbound on
|
|
299
|
+
// the HDRP path. Sample-time gating by PointLight.shadowAtlasLayer >= 0.
|
|
300
|
+
@group(0) @binding(3) var shadowMap : texture_depth_2d;
|
|
301
|
+
@group(0) @binding(4) var shadowSampler : sampler_comparison;
|
|
302
|
+
// feat-20260612-point-light-shadows-urp-hdrp M3 / T-M3-7 (plan-strategy §D-1 + §D-8):
|
|
303
|
+
// Bindings 5 + 6 carry the cube_array shadow atlas + per-light shadow params
|
|
304
|
+
// buffer. URP forward consumers (this file's `forgeax_view::common`) declare
|
|
305
|
+
// them only when the `POINT_SHADOW_AVAILABLE` naga_oil define is true so
|
|
306
|
+
// material shaders that share the URP BGL but do not yet require shadows
|
|
307
|
+
// keep validating with the lean BGL shape (binding 0..4) — the runtime
|
|
308
|
+
// `viewBindGroupLayout` in createRenderer.ts is the matching SSOT.
|
|
309
|
+
//
|
|
310
|
+
// Cube_array depth atlas (binding 5):
|
|
311
|
+
// `texture_depth_cube_array` with `layers = 4` (= PointLightShadow
|
|
312
|
+
// cardinality cap). Sampled via `sample_shadow_cube_hw2x2`
|
|
313
|
+
// (forgeax_pbr::shadow_pcf) in lighting-punctual.wgsl when
|
|
314
|
+
// `PointLight.shadowAtlasLayer >= 0`.
|
|
315
|
+
//
|
|
316
|
+
// Per-light shadow params (binding 6):
|
|
317
|
+
// `array<vec4<f32>, 4>` carrying the URP-side proj constants for cube
|
|
318
|
+
// depth-ref reconstruction (research L0.5 + L1.13). Each lane stores the
|
|
319
|
+
// shader-side reconstruction constants for one shadow-casting point light;
|
|
320
|
+
// slot N matches `PointLight.shadowAtlasLayer = N`. HDRP rides the same
|
|
321
|
+
// constants on `LightSlot.kind_and_pad.wyz` so binding 6 stays unbound on
|
|
322
|
+
// the HDRP path.
|
|
323
|
+
#ifdef POINT_SHADOW_AVAILABLE
|
|
324
|
+
@group(0) @binding(5) var shadowAtlas : texture_depth_cube_array;
|
|
325
|
+
@group(0) @binding(6) var<uniform> shadowParams : array<vec4<f32>, 4>;
|
|
326
|
+
#endif
|
|
327
|
+
|
|
328
|
+
// feat-20260613-csm-cascaded-shadow-maps M5 / w28: shadowCasterCascade is
|
|
329
|
+
// a per-pass uniform consumed exclusively by `shadow_caster.wgsl`. The
|
|
330
|
+
// host writes `index = i` (in 0..3) before each cascade's shadow pass so
|
|
331
|
+
// the shadow_caster vertex shader picks the matching `view.lightViewProj_X`
|
|
332
|
+
// for that pass. Forward material shaders declare the binding (it lives
|
|
333
|
+
// in the shared view BGL) but never reference it -- WGSL accepts unused
|
|
334
|
+
// bindings. The `index` is a u32; the three trailing u32 lanes keep the
|
|
335
|
+
// struct at the 16-byte uniform-buffer alignment WebGL2 requires.
|
|
336
|
+
//
|
|
337
|
+
// Binding 7 (not 5) on 2026-06-13: bindings 5/6 went to point-shadow
|
|
338
|
+
// (cube_array atlas + params UBO; FRAGMENT-only) so the cascade UBO —
|
|
339
|
+
// which needs VERTEX|FRAGMENT visibility — moved to the next free slot.
|
|
340
|
+
// `pbr-pipeline.ts buildPbrViewBglEntries` is the matching SSOT.
|
|
341
|
+
// feat-20260625-spot-light-shadow-mapping M2 / w10 (D-1): the cascade UBO
|
|
342
|
+
// gains a spot-specific perspective light-view-projection matrix + an `isSpot`
|
|
343
|
+
// discriminant. The spot shadow pass writes its own perspective matrix into
|
|
344
|
+
// `spotLightViewProj` + sets `isSpot = 1u` before each tile pass; the
|
|
345
|
+
// shadow_caster vertex shader then routes through the spot matrix instead of
|
|
346
|
+
// the directional `view.lightViewProj_A..D` cascade slots. This keeps the spot
|
|
347
|
+
// matrix OUT of the directional View UBO (no same-frame write contention —
|
|
348
|
+
// directional and spot both need light-view-proj matrices in one frame). The
|
|
349
|
+
// `index` lane is unused on the spot path (spot tile order is tracked
|
|
350
|
+
// host-side via depthLoadOp clear/load counting, decoupled from directional
|
|
351
|
+
// cascadeIndex per D-2).
|
|
352
|
+
struct ShadowCasterCascade {
|
|
353
|
+
index : u32,
|
|
354
|
+
// feat-20260625 M2 / w10 (D-1): spot-path discriminant (0 = directional
|
|
355
|
+
// cascade, 1 = spot perspective). Repurposes the former `shadowCasterPadA`
|
|
356
|
+
// lane. The shadow_caster vertex shader branches on this.
|
|
357
|
+
isSpot : u32,
|
|
358
|
+
// Two trailing u32 pad lanes (zero-initialised host-side) keep `index`/
|
|
359
|
+
// `isSpot` in the first 16 B vec4 so the `mat4x4<f32>` below starts at a
|
|
360
|
+
// 16 B-aligned offset (WebGL2 GLES 3.0 requirement). Field names are unique
|
|
361
|
+
// across the composed module surface so naga_oil's writeback substitution
|
|
362
|
+
// does not collide with prior `pad0` / `pad1` members in other shared
|
|
363
|
+
// structs (Material, SkylightUniforms).
|
|
364
|
+
shadowCasterPadB : u32,
|
|
365
|
+
shadowCasterPadC : u32,
|
|
366
|
+
// feat-20260625 M2 / w10 (D-1): spot perspective light-view-projection
|
|
367
|
+
// matrix (perspective(outerCone*2) x lookAt). Written per spot tile pass.
|
|
368
|
+
// Ignored on the directional path (isSpot = 0).
|
|
369
|
+
spotLightViewProj : mat4x4<f32>,
|
|
370
|
+
};
|
|
371
|
+
@group(0) @binding(7) var<uniform> shadowCasterCascade : ShadowCasterCascade;
|
|
372
|
+
|
|
373
|
+
// feat-20260625-spot-light-shadow-mapping M3 / w13 (plan-strategy D-5):
|
|
374
|
+
// Spot shadow atlas (single 2D depth texture; the host packs up to 4 spot
|
|
375
|
+
// shadows into a 2x2 tile grid — see urp-pipeline.ts spotShadowDepth). Bound
|
|
376
|
+
// at @group(0) binding 8 (smallest free slot after the cascade UBO at 7).
|
|
377
|
+
//
|
|
378
|
+
// ALWAYS-ON, no #ifdef gate (unlike the point cube_array atlas at binding 5,
|
|
379
|
+
// which rides POINT_SHADOW_AVAILABLE because cube_array is unavailable in the
|
|
380
|
+
// WebGPU compat profile). Spot shadows are `texture_depth_2d` only, which is
|
|
381
|
+
// compat-safe everywhere, so the binding is unconditionally declared — keeping
|
|
382
|
+
// the BGL <-> WGSL alignment surface minimal (D-5: a define axis here would be
|
|
383
|
+
// pure burden). The matching runtime SSOT is `buildPbrViewBglEntries`
|
|
384
|
+
// (pbr-pipeline.ts) which declares binding 8 unconditionally.
|
|
385
|
+
//
|
|
386
|
+
// The comparison sampler is REUSED from binding 4 (`shadowSampler`): spot
|
|
387
|
+
// perspective-depth sampling needs the exact same descriptor (clamp-to-edge +
|
|
388
|
+
// compare less) as directional/point, so no binding 9 sampler is introduced.
|
|
389
|
+
// The per-spot perspective lightViewProj matrices that the forward (fragment)
|
|
390
|
+
// shadow sample needs live in the View UBO (binding 0, `view.spotLightViewProj`)
|
|
391
|
+
// — folded there in feat-20260625 w25 (scope-amend) to keep the WebGL2 fallback
|
|
392
|
+
// fragment uniform-buffer count <= 11; binding 8 is the last view-BG binding.
|
|
393
|
+
@group(0) @binding(8) var spotShadowMap : texture_depth_2d;
|
|
394
|
+
|
|
395
|
+
#if STORAGE_BUFFER_AVAILABLE == true
|
|
396
|
+
@group(2) @binding(0) var<storage, read> meshes : array<Mesh>;
|
|
397
|
+
#else
|
|
398
|
+
@group(2) @binding(0) var<uniform> meshes : array<Mesh, 128>;
|
|
399
|
+
#endif
|
|
400
|
+
|
|
401
|
+
// feat-20260604-instances-per-instance-transform-shader-group3-bin M1 / w3:
|
|
402
|
+
// Per-instance local transform (column-major mat4, 64 B per entry).
|
|
403
|
+
// Byte-for-byte isomorphic to the record-stage packed mat4 write in
|
|
404
|
+
// render-system-record.ts (inst.transforms, stride-16 row-major 16-float
|
|
405
|
+
// per-instance) and the identity 16-float mat4 in createRenderer.ts.
|
|
406
|
+
// @group(3) is the per-instance storage buffer — already uploaded by
|
|
407
|
+
// the record stage at render-system-record.ts:2958, bound via
|
|
408
|
+
// setBindGroup(3, instancesBg) at :2985, and drawn with
|
|
409
|
+
// drawIndexed(indexCount, instanceCount, 0, 0) at :2898.
|
|
410
|
+
//
|
|
411
|
+
// Reuses the STORAGE_BUFFER_AVAILABLE axis (D-2) — the uniform fallback
|
|
412
|
+
// array<InstanceData, 128> caps at MAX_UNIFORM_INSTANCES=128, same as
|
|
413
|
+
// the meshes array. No new INSTANCE_STORAGE_AVAILABLE axis.
|
|
414
|
+
//
|
|
415
|
+
// feat-20260625-sprite-instances-and-tilemap-terrain-static-batch M2 / w7
|
|
416
|
+
// (plan-strategy §2 D-1 + §2 D-4 + research §Q-R-4.1-4.4): the
|
|
417
|
+
// PER_INSTANCE_REGION axis is declared in sprite.wgsl ONLY (D-4 keeps
|
|
418
|
+
// pbr / unlit at their existing 2-variant count). When the sprite
|
|
419
|
+
// pipeline composes this module with `PER_INSTANCE_REGION = true`, the
|
|
420
|
+
// InstanceData struct grows from 64 B (mat4) to 80 B (mat4 + vec4) so
|
|
421
|
+
// the record stage can interleave per-instance UV regions into the same
|
|
422
|
+
// single GPU buffer that already carries the per-instance mat4 (single
|
|
423
|
+
// binding slot, BGL zero-modification per D-R-4). The conditional sits
|
|
424
|
+
// at the tail of the struct so the pre-feat 64 B layout stays
|
|
425
|
+
// byte-identical when PER_INSTANCE_REGION is undefined (sprite-atlas
|
|
426
|
+
// + every non-sprite material).
|
|
427
|
+
//
|
|
428
|
+
// Uniform-fallback safety (R-6): worst case is
|
|
429
|
+
// 80 B × MAX_UNIFORM_INSTANCES (128) = 10240 B
|
|
430
|
+
// which is below the WebGL2 UBO floor of 16384 B; the 128 cap survives
|
|
431
|
+
// the stride bump.
|
|
432
|
+
struct InstanceData {
|
|
433
|
+
localFromInstance : mat4x4<f32>,
|
|
434
|
+
#if STORAGE_BUFFER_AVAILABLE == true
|
|
435
|
+
previousLocalFromInstance : mat4x4<f32>,
|
|
436
|
+
#endif
|
|
437
|
+
#if PER_INSTANCE_REGION == true
|
|
438
|
+
// Per-instance atlas region: .xy = (uMin, vMin), .zw = (uW, vH). Pairs
|
|
439
|
+
// with the legacy `material.region` UBO field (sprite.wgsl Material
|
|
440
|
+
// struct, 16 B at byte offset 16). Sprite vs_main picks between the
|
|
441
|
+
// material-level fallback (legacy, single-material-per-draw) and the
|
|
442
|
+
// per-instance value via the same `#if PER_INSTANCE_REGION == true`
|
|
443
|
+
// gate so a single sprite.wgsl source supports both shapes.
|
|
444
|
+
region : vec4<f32>,
|
|
445
|
+
#endif
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
fn sceneTemporalUv(clip : vec4<f32>) -> vec2<f32> {
|
|
449
|
+
let safeW = select(1e-6, clip.w, abs(clip.w) >= 1e-6);
|
|
450
|
+
let ndc = clip.xy / safeW;
|
|
451
|
+
return vec2<f32>(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
fn sceneTemporalViewDepth(clip : vec4<f32>) -> f32 {
|
|
455
|
+
let perspectiveDepth = max(clip.w, 0.0);
|
|
456
|
+
let ndcDepth = clip.z / max(abs(clip.w), 1e-6);
|
|
457
|
+
let orthographicDepth = view.temporalProjection.x +
|
|
458
|
+
ndcDepth * (view.temporalProjection.y - view.temporalProjection.x);
|
|
459
|
+
let viewDepth = select(
|
|
460
|
+
perspectiveDepth,
|
|
461
|
+
max(orthographicDepth, 0.0),
|
|
462
|
+
view.temporalProjection.z >= 0.5,
|
|
463
|
+
);
|
|
464
|
+
return log2(1.0 + viewDepth);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
fn packSceneTemporal(
|
|
468
|
+
currentClip : vec4<f32>,
|
|
469
|
+
previousClip : vec4<f32>,
|
|
470
|
+
reactive : f32,
|
|
471
|
+
) -> vec4<f32> {
|
|
472
|
+
return vec4<f32>(
|
|
473
|
+
sceneTemporalUv(currentClip) - sceneTemporalUv(previousClip),
|
|
474
|
+
sceneTemporalViewDepth(currentClip),
|
|
475
|
+
clamp(reactive, 0.0, 1.0),
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
#if STORAGE_BUFFER_AVAILABLE == true
|
|
479
|
+
@group(3) @binding(0) var<storage, read> instances : array<InstanceData>;
|
|
480
|
+
#else
|
|
481
|
+
@group(3) @binding(0) var<uniform> instances : array<InstanceData, 128>;
|
|
482
|
+
#endif
|
|
483
|
+
|
|
484
|
+
// Fullscreen large-triangle SSOT (feat-20260519-tonemap-reinhard-mvp / T-M2.1,
|
|
485
|
+
// research F3 section 2.3). 3 vertices in clip-space:
|
|
486
|
+
// index 0 -> (-1, -1) bottom-left
|
|
487
|
+
// index 1 -> ( 3, -1) beyond right edge
|
|
488
|
+
// index 2 -> (-1, 3) beyond top edge
|
|
489
|
+
// The triangle fully covers [-1, 1]^2 NDC; the rasterizer clips the
|
|
490
|
+
// out-of-range portion. UV is derived from xy with Y flipped so the
|
|
491
|
+
// downstream textureSample returns the right-side-up image (WebGPU
|
|
492
|
+
// convention: UV (0, 0) is top-left of the texture).
|
|
493
|
+
//
|
|
494
|
+
// Consumers (tonemap.wgsl and any future post-process pass) call this
|
|
495
|
+
// in their @vertex stage:
|
|
496
|
+
// @vertex fn vs(@builtin(vertex_index) i : u32) -> FullscreenOutput {
|
|
497
|
+
// return fullscreen_triangle(i);
|
|
498
|
+
// }
|
|
499
|
+
//
|
|
500
|
+
// Keep the TS port in
|
|
501
|
+
// `packages/shader/src/__tests__/fullscreen-triangle.test.ts` in lockstep.
|
|
502
|
+
|
|
503
|
+
struct FullscreenOutput {
|
|
504
|
+
@builtin(position) position : vec4<f32>,
|
|
505
|
+
@location(0) uv : vec2<f32>,
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
fn fullscreen_triangle(vertex_index : u32) -> FullscreenOutput {
|
|
509
|
+
var x : f32 = -1.0;
|
|
510
|
+
var y : f32 = -1.0;
|
|
511
|
+
if (vertex_index == 1u) {
|
|
512
|
+
x = 3.0;
|
|
513
|
+
}
|
|
514
|
+
if (vertex_index == 2u) {
|
|
515
|
+
y = 3.0;
|
|
516
|
+
}
|
|
517
|
+
let u : f32 = (x + 1.0) * 0.5;
|
|
518
|
+
let v : f32 = 1.0 - (y + 1.0) * 0.5;
|
|
519
|
+
var out : FullscreenOutput;
|
|
520
|
+
out.position = vec4<f32>(x, y, 0.0, 1.0);
|
|
521
|
+
out.uv = vec2<f32>(u, v);
|
|
522
|
+
return out;
|
|
523
|
+
}
|