@forgeax/engine-shader 0.1.20 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -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 +33070 -8
- package/dist/index.mjs.map +1 -1
- package/dist/ltc/provenance.d.ts +17 -0
- package/dist/ltc/provenance.d.ts.map +1 -0
- package/dist/ltc/tables.d.ts +22 -0
- package/dist/ltc/tables.d.ts.map +1 -0
- package/dist/material/artifact-types.d.ts +52 -0
- package/dist/material/artifact-types.d.ts.map +1 -1
- package/dist/material-schemas.d.ts +5 -0
- package/dist/material-schemas.d.ts.map +1 -1
- package/dist/register-default-sprite-lit.d.ts +3 -6
- package/dist/register-default-sprite-lit.d.ts.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/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 +138 -30
- package/src/__tests__/direct-light-layout.unit.test.ts +40 -0
- package/src/__tests__/ibl-irradiance.unit.test.ts +7 -4
- package/src/__tests__/ibl-sampling.unit.test.ts +3 -4
- package/src/__tests__/lighting-point-spot-volume.unit.test.ts +16 -2
- package/src/__tests__/lighting-punctual.unit.test.ts +61 -8
- package/src/__tests__/lighting-spot-volume-attenuation.unit.test.ts +1 -1
- package/src/__tests__/ltc-provenance.unit.test.ts +33 -0
- package/src/__tests__/manifest.fixture.json +33 -0
- package/src/__tests__/material-contract.unit.test.ts +61 -1
- package/src/__tests__/material-derived-builtins.integration.test.ts +27 -0
- package/src/__tests__/probe-lighting-composition.unit.test.ts +38 -0
- package/src/__tests__/rect-area-brdf.unit.test.ts +36 -0
- package/src/__tests__/reflection-probe-sampling.unit.test.ts +45 -0
- package/src/__tests__/scene-temporal.unit.test.ts +20 -0
- package/src/__tests__/shader.unit.test.ts +40 -15
- package/src/__tests__/shadow-pcss.unit.test.ts +131 -0
- package/src/__tests__/spot-cookie-composition.unit.test.ts +57 -0
- package/src/__tests__/spot-modifier-composition.unit.test.ts +19 -0
- package/src/__tests__/sprite-lit-shader.test.ts +24 -11
- package/src/__tests__/sprite-variants.unit.test.ts +5 -1
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +7 -1
- package/src/__tests__/transparent-pbr.unit.test.ts +7 -0
- package/src/brdf.wgsl +115 -1
- package/src/common.wgsl +95 -91
- package/src/default-standard-pbr-skin.wgsl +108 -92
- package/src/default-standard-pbr.wgsl +189 -191
- package/src/fxaa.wgsl +27 -6
- package/src/ibl-irradiance.wgsl +4 -1
- package/src/ibl-sampling.wgsl +56 -2
- package/src/index.ts +23 -0
- package/src/lighting-directional.wgsl +161 -132
- package/src/lighting-probe.wgsl +51 -0
- package/src/lighting-punctual.wgsl +69 -29
- package/src/lighting-rect-area.wgsl +162 -0
- package/src/lighting-spot-modifiers.wgsl +93 -0
- package/src/ltc/provenance.ts +19 -0
- package/src/ltc/tables.ts +2831 -0
- package/src/material/artifact-types.ts +137 -0
- package/src/material-schemas.ts +12 -0
- package/src/register-default-sprite-lit.ts +3 -6
- package/src/scene-temporal.wgsl +14 -8
- package/src/shadow-pcf.wgsl +44 -1
- package/src/sprite-lit.wgsl +37 -53
- package/src/sprite.wgsl +1 -1
- package/src/standard-cluster.wgsl +274 -0
- package/src/tonemap.wgsl +8 -3
- package/src/types.ts +2 -1
- package/src/volume/volume-inject.wgsl +48 -38
- package/src/volume/volume-integrate.wgsl +47 -42
- package/dist/.tsbuildinfo +0 -1
- package/src/hdrp-cluster-forward.wgsl +0 -224
package/src/common.wgsl
CHANGED
|
@@ -36,6 +36,36 @@ fn linearToSrgbOetf(color : vec3<f32>) -> vec3<f32> {
|
|
|
36
36
|
return select(high, low, safe <= vec3<f32>(0.0031308));
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Shared final-surface dither. The output transform and FXAA are both
|
|
40
|
+
// display-encoded fullscreen writers, so they must use one owner-level
|
|
41
|
+
// algorithm rather than carrying subtly different copies in each pass.
|
|
42
|
+
// The bounded shift is ±0.5/255 per channel, matching Three.js's optional
|
|
43
|
+
// material dithering amplitude while the screen-space hash remains stable for
|
|
44
|
+
// a still frame. Callers decide whether this writer is the final 8-bit
|
|
45
|
+
// boundary; intermediate float targets must leave it disabled.
|
|
46
|
+
fn hash32(value : u32) -> u32 {
|
|
47
|
+
var hash = value;
|
|
48
|
+
hash = (hash ^ 61u) ^ (hash >> 16u);
|
|
49
|
+
hash = hash + (hash << 3u);
|
|
50
|
+
hash = hash ^ (hash >> 4u);
|
|
51
|
+
hash = hash * 668265261u;
|
|
52
|
+
hash = hash ^ (hash >> 15u);
|
|
53
|
+
return hash;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fn ditherNoise(pixelPosition : vec2<f32>) -> f32 {
|
|
57
|
+
let pixel = vec2<u32>(pixelPosition);
|
|
58
|
+
let seed = (pixel.x * 1973u) ^ (pixel.y * 9277u) ^ 89173u;
|
|
59
|
+
return f32(hash32(seed) & 1023u) / 1023.0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fn ditherUnorm8(value : vec3<f32>, pixelPosition : vec2<f32>) -> vec3<f32> {
|
|
63
|
+
let noise = ditherNoise(pixelPosition);
|
|
64
|
+
var shift = vec3<f32>(0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0);
|
|
65
|
+
shift = mix(2.0 * shift, -2.0 * shift, noise);
|
|
66
|
+
return clamp(value + shift, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
67
|
+
}
|
|
68
|
+
|
|
39
69
|
// @forgeax/engine-shader - common.wgsl (M5 T-18 feat-20260512-naga-oil-composition-hmr).
|
|
40
70
|
//
|
|
41
71
|
// Shared view + mesh structs extracted from pbr.wgsl + unlit.wgsl via
|
|
@@ -50,7 +80,7 @@ fn linearToSrgbOetf(color : vec3<f32>) -> vec3<f32> {
|
|
|
50
80
|
// aligned with Bevy's bevy_view::common and matches charter proposition 5
|
|
51
81
|
// (consistent abstraction - one View struct everywhere).
|
|
52
82
|
|
|
53
|
-
// View UBO byte layout (
|
|
83
|
+
// View UBO byte layout (960 B payload, 1024 B slot):
|
|
54
84
|
// [ 0.. 64) worldViewProj mat4x4<f32> (align 16, size 64)
|
|
55
85
|
// [ 64.. 80) lightDir vec3<f32> (align 16, 12+4 pad)
|
|
56
86
|
// [ 80.. 96) lightColor vec3<f32> (align 16, 12+4 pad)
|
|
@@ -68,8 +98,7 @@ fn linearToSrgbOetf(color : vec3<f32>) -> vec3<f32> {
|
|
|
68
98
|
// [500..504) cascadeBlend f32 (align 4, size 4)
|
|
69
99
|
// [504..508) depthBias f32 (align 4, size 4)
|
|
70
100
|
// [508..512) normalBias f32 (align 4, size 4)
|
|
71
|
-
// [512..
|
|
72
|
-
// [516..528) _align_pad — (mat4 array align=16, 12 B)
|
|
101
|
+
// [512..528) directional shadow carrier f32[4] (profile, radius, penumbra, reserved)
|
|
73
102
|
// [528..784) spotLightViewProj array<mat4x4<f32>,4> (align 16, size 256)
|
|
74
103
|
// WGSL prefix = 784 B. Host UBO = VIEW_UBO_BYTES = 960 B (renderer-factory.ts);
|
|
75
104
|
// view-ubo.ts writes the full 240 f32 payload. The spot matrix
|
|
@@ -99,30 +128,28 @@ fn linearToSrgbOetf(color : vec3<f32>) -> vec3<f32> {
|
|
|
99
128
|
// shadow_caster.wgsl indexes the 4 fields via `shadowCasterCascade.index`
|
|
100
129
|
// (binding 5, written per shadow pass).
|
|
101
130
|
//
|
|
102
|
-
// feat-
|
|
103
|
-
// the merged DirectionalLight's shadow bias +
|
|
104
|
-
// tail (depthBias / normalBias
|
|
105
|
-
// 126/127/128). Tail
|
|
131
|
+
// feat-20260827-directional-csm-pcss-quality M1:
|
|
132
|
+
// the merged DirectionalLight's shadow bias + accepted filter carrier occupy
|
|
133
|
+
// the existing tail (depthBias / normalBias at bytes 504/508, carrier at
|
|
134
|
+
// bytes 512..528, floats 126/127/128..131). Tail reuse only -- field order is
|
|
135
|
+
// byte-for-byte stable
|
|
106
136
|
// (charter P4); the prior 88 B host tail pad shrinks to 64 B, total stays
|
|
107
137
|
// View slot remains within the existing 1024 B stride. lighting-directional.wgsl drives the
|
|
108
|
-
// directional shadow bias (D-1: bias = max(normalBias*(1-N.L), depthBias))
|
|
109
|
-
//
|
|
138
|
+
// directional shadow bias (D-1: bias = max(normalBias*(1-N.L), depthBias));
|
|
139
|
+
// directional shadow carrier is the receiver-facing profile/radius/penumbra POD.
|
|
110
140
|
//
|
|
111
141
|
// feat-20260625-spot-light-shadow-mapping w25 (scope-amend webkit-fallback):
|
|
112
142
|
// the per-spot perspective `spotLightViewProj` matrices (4 lanes, fragment-read)
|
|
113
143
|
// fold into the View UBO tail instead of a standalone @group(0) binding 9
|
|
114
144
|
// uniform buffer. The standalone binding pushed the fragment-stage uniform
|
|
115
|
-
// buffer count to 12
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
// pipeline-layout creation. Folding into the View UBO costs zero new bindings
|
|
120
|
-
// (the count drops back to 11). D-1 rejected the View UBO for the CASTER vertex
|
|
121
|
-
// channel (binding 7) because directional + spot casters both need a light-view
|
|
145
|
+
// buffer count to 12 before the local-light arrays were removed. The current
|
|
146
|
+
// view declares no point/spot light arrays; the spot matrices remain folded
|
|
147
|
+
// into the View UBO with no standalone binding. D-1 rejected the View UBO for
|
|
148
|
+
// the CASTER vertex channel (binding 7) because directional + spot casters both need a light-view
|
|
122
149
|
// matrix in the same frame (same-frame write contention); the FRAGMENT read
|
|
123
150
|
// channel has no such contention — the host writes all <=4 spot matrices once
|
|
124
151
|
// per frame before the forward pass, so the fold is safe. mat4 array align=16:
|
|
125
|
-
// the array lands at byte 528 (next 16 B-aligned offset after
|
|
152
|
+
// the array lands at byte 528 (next 16 B-aligned offset after the filter carrier at
|
|
126
153
|
// byte 512..516), spanning bytes 528..784. The host allocates VIEW_UBO_BYTES =
|
|
127
154
|
// 960 (renderer-factory.ts) and writes the full 240 f32
|
|
128
155
|
// payload (render-system-record.ts). Lane N = the spot with `shadowAtlasTile
|
|
@@ -142,10 +169,10 @@ struct View {
|
|
|
142
169
|
cascadeBlend : f32,
|
|
143
170
|
depthBias : f32,
|
|
144
171
|
normalBias : f32,
|
|
145
|
-
|
|
172
|
+
directionalShadowFilter : vec4<f32>,
|
|
146
173
|
// feat-20260625-spot-light-shadow-mapping w25: per-spot perspective
|
|
147
174
|
// light-view-projection matrices, fragment-read. Auto-aligned to byte 528
|
|
148
|
-
// (mat4 array align=16, after
|
|
175
|
+
// (mat4 array align=16, after the four directional carrier lanes at byte 512); spans bytes
|
|
149
176
|
// 528..784. Lane N = spot with shadowAtlasTile === N (cap = 4). Zeroed lanes
|
|
150
177
|
// are safe (sample gated on shadowAtlasTile >= 0 in default-standard-pbr.wgsl).
|
|
151
178
|
spotLightViewProj : array<mat4x4<f32>, 4>,
|
|
@@ -177,12 +204,22 @@ struct Mesh {
|
|
|
177
204
|
#endif
|
|
178
205
|
};
|
|
179
206
|
|
|
207
|
+
// Retained CPU ProbeBlendRecord ABI: 4 scalar lanes plus 9 vec4 lanes = 160 B.
|
|
208
|
+
// The last lane carries padding for the 27 f32 SH coefficients. Sky residual
|
|
209
|
+
// is derived in lighting-probe.wgsl, never stored as a second fraction.
|
|
210
|
+
struct ProbeBlendRecord {
|
|
211
|
+
objectKey : f32,
|
|
212
|
+
generation : f32,
|
|
213
|
+
localBlendFraction : f32,
|
|
214
|
+
flags : f32,
|
|
215
|
+
shPreblend : array<vec4<f32>, 9>,
|
|
216
|
+
};
|
|
217
|
+
|
|
180
218
|
// feat-20260519-light-casters-point-spot-pbr M4 / w21 (D-S1 + D-S2 +
|
|
181
219
|
// AC-04 b/c + AC-05 binding declaration). Punctual-light std430 storage
|
|
182
|
-
// types
|
|
183
|
-
// `packages/runtime/src/light-buffer-layout.ts`:
|
|
220
|
+
// types use the shared five-row host ABI in `light-buffer-layout.ts`:
|
|
184
221
|
//
|
|
185
|
-
//
|
|
222
|
+
// Point/Spot/Rect direct slot (80 B / 20 u32-or-float lanes):
|
|
186
223
|
// [ 0..2 ] position vec3<f32>
|
|
187
224
|
// [ 3 ] invRangeSquared f32 (Bevy color_inverse_square_range.w; 0
|
|
188
225
|
// collapses range falloff to a pure 1/d^2 inverse-square law)
|
|
@@ -190,7 +227,7 @@ struct Mesh {
|
|
|
190
227
|
// color * intensity so the shader avoids the per-fragment mul)
|
|
191
228
|
// [ 7 ] pad f32 = 0
|
|
192
229
|
//
|
|
193
|
-
//
|
|
230
|
+
// The metadata row carries kind, shadow, IES, and Cookie identities.
|
|
194
231
|
// [ 0..2 ] position vec3<f32>
|
|
195
232
|
// [ 3 ] invRangeSquared f32
|
|
196
233
|
// [ 4..6 ] colorTimesIntensity vec3<f32>
|
|
@@ -199,81 +236,31 @@ struct Mesh {
|
|
|
199
236
|
// [ 8..10] direction vec3<f32> (raw outgoing vector; shader reads
|
|
200
237
|
// via dot(L, -direction) for cone angle test)
|
|
201
238
|
// [ 11 ] cosOuter f32
|
|
202
|
-
// [12] depthBias
|
|
203
|
-
// [
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
239
|
+
// Spot row 3: [12] depthBias, [13] normalBias, [14] shadowIntensity,
|
|
240
|
+
// [15] rollDeg. The unified slot has no separate pcf lane; the current
|
|
241
|
+
// surface and volume receivers use the stable PCF3 profile.
|
|
242
|
+
// Rect row 1/2/3: halfWidth, axisY + halfHeight, axisX + zero pad. The
|
|
243
|
+
// final metadata row carries the kind,
|
|
244
|
+
// shadow, IES, and Cookie identities.
|
|
207
245
|
//
|
|
208
246
|
// WGSL std430 alignment audit: vec3<f32> alignof=16 / sizeof=12. The
|
|
209
247
|
// f32 lane wedged immediately after each vec3 fills the 4 B remainder
|
|
210
248
|
// (WGSL struct member rule: next.offset = roundUp(prev.offset +
|
|
211
249
|
// prev.size, this.alignof) - for f32 alignof=4 the round-up is a
|
|
212
250
|
// no-op, so position[3] / color[3] / direction[3] sit at byte
|
|
213
|
-
// offsets 12 / 28 / 44 with zero internal padding).
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
// parity-only `shadowIntensity` lane at byte 64. These struct sizes match the
|
|
217
|
-
// host packers (packPointLight 32 B / packSpotLight 80 B) exactly.
|
|
251
|
+
// offsets 12 / 28 / 44 with zero internal padding). Point, Spot, and Rect all
|
|
252
|
+
// use the unified 80 B host packer; this shared slot is the sole local-light
|
|
253
|
+
// carrier.
|
|
218
254
|
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
struct PointLight {
|
|
226
|
-
position : vec3<f32>,
|
|
227
|
-
invRangeSquared : f32,
|
|
228
|
-
colorTimesIntensity : vec3<f32>,
|
|
229
|
-
// feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-8 (plan-strategy §D-2):
|
|
230
|
-
// Repurposes the prior `pointPadW` (f32 zero-pad) lane as `shadowAtlasLayer`
|
|
231
|
-
// (i32 sentinel, -1 = no shadow, 0..3 = cube_array atlas layer index for
|
|
232
|
-
// shadow-casting point lights). Cap = 4 enforced by the PointLightShadow
|
|
233
|
-
// ECS component cardinality. The shader path samples the cube_array atlas
|
|
234
|
-
// (binding 5) only when `shadowAtlasLayer >= 0`. Field name is unique to
|
|
235
|
-
// this struct (no `pad0`/`pad1` collision with the pbr Material struct
|
|
236
|
-
// under naga_oil writeback substitution).
|
|
237
|
-
shadowAtlasLayer : i32,
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
struct SpotLight {
|
|
241
|
-
position : vec3<f32>,
|
|
242
|
-
invRangeSquared : f32,
|
|
243
|
-
colorTimesIntensity : vec3<f32>,
|
|
244
|
-
cosInner : f32,
|
|
245
|
-
direction : vec3<f32>,
|
|
246
|
-
cosOuter : f32,
|
|
247
|
-
// feat-20260625-spot-light-shadow-mapping M3 / w13 (plan-strategy D-4):
|
|
248
|
-
// The trailing vec4 transports receiver controls and the tile identity.
|
|
249
|
-
// Host packSpotLight writes slots 12..14 from the same snapshot; slot 15 is
|
|
250
|
-
// the i32 shadow tile sentinel and slot 16 carries shadowIntensity.
|
|
251
|
-
depthBias : f32,
|
|
252
|
-
normalBias : f32,
|
|
253
|
-
pcfKernelSize : f32,
|
|
254
|
-
shadowAtlasTile : i32,
|
|
255
|
-
shadowIntensity : f32,
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
struct PointLightsArray {
|
|
259
|
-
count : u32,
|
|
260
|
-
slots : array<PointLight, 4>,
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
struct SpotLightsArray {
|
|
264
|
-
count : u32,
|
|
265
|
-
slots : array<SpotLight, 4>,
|
|
255
|
+
struct DirectLightSlot {
|
|
256
|
+
position : vec4<f32>, // row0: position + inverse range
|
|
257
|
+
colorTimesIntensity : vec4<f32>, // row1: color + first angular fact
|
|
258
|
+
direction : vec4<f32>, // row2: primary axis + second angular fact
|
|
259
|
+
auxiliary : vec4<f32>, // row3: auxiliary Rect axis
|
|
260
|
+
metadata : vec4<u32>,
|
|
266
261
|
};
|
|
267
262
|
|
|
268
263
|
@group(0) @binding(0) var<uniform> view : View;
|
|
269
|
-
|
|
270
|
-
#if STORAGE_BUFFER_AVAILABLE == true
|
|
271
|
-
@group(0) @binding(1) var<storage, read> pointLightsBuffer : PointLightsArray;
|
|
272
|
-
@group(0) @binding(2) var<storage, read> spotLightsBuffer : SpotLightsArray;
|
|
273
|
-
#else
|
|
274
|
-
@group(0) @binding(1) var<uniform> pointLightsBuffer : PointLightsArray;
|
|
275
|
-
@group(0) @binding(2) var<uniform> spotLightsBuffer : SpotLightsArray;
|
|
276
|
-
#endif
|
|
277
264
|
// feat-20260520-directional-light-shadow-mapping M3 / w16 (D-1 / plan-strategy §8.1):
|
|
278
265
|
// shadowMap + shadowSampler consume @group(0) bindings 3/4 (smallest unused
|
|
279
266
|
// slots adjacent to view UBO at 0). M3 uses 3x3 PCF with textureLoad on
|
|
@@ -284,7 +271,7 @@ struct SpotLightsArray {
|
|
|
284
271
|
// layers=4, depth32float; one cube per shadow-casting point light, cap=4).
|
|
285
272
|
// Binding 6 declares the per-light shadow params buffer (URP only — proj
|
|
286
273
|
// constants for cube depth-ref reconstruction); HDRP rides the same constants
|
|
287
|
-
// on
|
|
274
|
+
// on direct-light metadata so binding 6 stays unbound on
|
|
288
275
|
// the HDRP path. Sample-time gating by PointLight.shadowAtlasLayer >= 0.
|
|
289
276
|
@group(0) @binding(3) var shadowMap : texture_depth_2d;
|
|
290
277
|
@group(0) @binding(4) var shadowSampler : sampler_comparison;
|
|
@@ -307,7 +294,7 @@ struct SpotLightsArray {
|
|
|
307
294
|
// depth-ref reconstruction (research L0.5 + L1.13). Each lane stores the
|
|
308
295
|
// shader-side reconstruction constants for one shadow-casting point light;
|
|
309
296
|
// slot N matches `PointLight.shadowAtlasLayer = N`. HDRP rides the same
|
|
310
|
-
// constants on
|
|
297
|
+
// constants on direct-light metadata so binding 6 stays unbound on
|
|
311
298
|
// the HDRP path.
|
|
312
299
|
#ifdef POINT_SHADOW_AVAILABLE
|
|
313
300
|
@group(0) @binding(5) var shadowAtlas : texture_depth_cube_array;
|
|
@@ -380,6 +367,23 @@ struct ShadowCasterCascade {
|
|
|
380
367
|
// — folded there in feat-20260625 w25 (scope-amend) to keep the WebGL2 fallback
|
|
381
368
|
// fragment uniform-buffer count <= 11; binding 8 is the last view-BG binding.
|
|
382
369
|
@group(0) @binding(8) var spotShadowMap : texture_depth_2d;
|
|
370
|
+
// Extended-lighting resources are a real optional view topology. The
|
|
371
|
+
// renderer selects the matching material variant and BGL together; keeping
|
|
372
|
+
// these declarations behind the same axis prevents a no-extension variant
|
|
373
|
+
// from consuming texture budget or requiring absent bindings.
|
|
374
|
+
#ifdef EXTENDED_LIGHTING_AVAILABLE
|
|
375
|
+
@group(0) @binding(9) var spotModifierSampler : sampler;
|
|
376
|
+
@group(0) @binding(11) var iesProfileTexture : texture_2d_array<f32>;
|
|
377
|
+
@group(0) @binding(12) var cookieTexture : texture_2d_array<f32>;
|
|
378
|
+
@group(0) @binding(13) var ltcLambertTexture : texture_2d<f32>;
|
|
379
|
+
@group(0) @binding(14) var ltcGgxTexture : texture_2d<f32>;
|
|
380
|
+
// One aspect-correction matrix per Cookie array slice. The matrix is kept in
|
|
381
|
+
// the shared view group so the same projected Cookie semantics are available
|
|
382
|
+
// to both the ordinary and clustered Standard PBR consumers.
|
|
383
|
+
@group(0) @binding(15) var<uniform> cookieMatrices : array<mat4x4<f32>, 32>;
|
|
384
|
+
#endif
|
|
385
|
+
#ifdef PROJECTOR_AVAILABLE
|
|
386
|
+
#ifndef EXTENDED_LIGHTING_AVAILABLE
|
|
383
387
|
// Optional authored SpotLight projector. Surface and volume bind the same
|
|
384
388
|
// accepted TextureAsset view and linear-clamp sampler; an unprojected spot
|
|
385
389
|
// receives the renderer-owned white fallback. The declaration is capability
|
|
@@ -387,10 +391,10 @@ struct ShadowCasterCascade {
|
|
|
387
391
|
// minimum of 16 sampled textures without this optional cookie. Devices that
|
|
388
392
|
// cannot expose the 17th sampled texture select the matching white-projector
|
|
389
393
|
// shader variant and omit these resources from the view BGL.
|
|
390
|
-
#ifdef PROJECTOR_AVAILABLE
|
|
391
394
|
@group(0) @binding(11) var projectorTexture : texture_2d<f32>;
|
|
392
395
|
@group(0) @binding(12) var projectorSampler : sampler;
|
|
393
396
|
#endif
|
|
397
|
+
#endif
|
|
394
398
|
#if STORAGE_BUFFER_AVAILABLE == true
|
|
395
399
|
@group(2) @binding(0) var<storage, read> meshes : array<Mesh>;
|
|
396
400
|
#else
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
#import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances,
|
|
1
|
+
#import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, shadowMap, shadowSampler, sampleMaterialTexture}
|
|
2
|
+
#import forgeax_scene_temporal::{sceneViewZ}
|
|
2
3
|
#import forgeax_pbr::temporal::{projectPbrSceneTemporal}
|
|
3
4
|
#import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx}
|
|
4
5
|
#import forgeax_pbr::ibl_sampling::{sampleIblDiffuse, sampleIblSpecular}
|
|
6
|
+
#import forgeax_pbr::lighting_probe::{evaluateProbeDiffuse}
|
|
5
7
|
#import forgeax_pbr::tbn::{decodeTangentSpaceNormalRg, scaleTangentSpaceNormal, applyTBN}
|
|
6
8
|
#import forgeax_pbr::lighting_directional::{evalDirectionalNoShadow, evalDirectionalShadowFactor}
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#import forgeax_pbr::lighting_punctual::{evalPointShadowed}
|
|
10
|
-
#import forgeax_view::common::{shadowParams}
|
|
9
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
10
|
+
#import forgeax_standard::cluster::{evaluateStandardClusterLights}
|
|
11
11
|
#endif
|
|
12
12
|
|
|
13
13
|
#define_import_path forgeax_material::pbr-skin
|
|
14
14
|
#pragma variant_axis STORAGE_BUFFER_AVAILABLE
|
|
15
|
+
#pragma variant_axis CLUSTER_FORWARD_AVAILABLE
|
|
15
16
|
#pragma variant_axis VERTEX_COLOR_AVAILABLE
|
|
17
|
+
#pragma variant_axis PROBE_BLEND_AVAILABLE
|
|
18
|
+
#pragma variant_axis EXTENDED_LIGHTING_AVAILABLE
|
|
19
|
+
|
|
20
|
+
#pragma variant_axis DIRECTIONAL_PCSS_AVAILABLE
|
|
16
21
|
|
|
17
22
|
// @forgeax/engine-shader - default-standard-pbr-skin.wgsl
|
|
18
23
|
// (feat-20260523-skin-skeleton-animation M3 / T-29).
|
|
@@ -170,13 +175,35 @@ struct SkylightUniforms {
|
|
|
170
175
|
@group(1) @binding(22) var brdfLutSampler : sampler;
|
|
171
176
|
@group(1) @binding(23) var<uniform> skylight : SkylightUniforms;
|
|
172
177
|
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
#ifdef PROBE_BLEND_AVAILABLE
|
|
179
|
+
// Optional consumer lane. The host must provide the retained 160B record
|
|
180
|
+
// buffer and matching BGL before enabling this capability.
|
|
181
|
+
@group(3) @binding(1) var<storage, read> probeBlendRecords : array<vec4<f32>>;
|
|
182
|
+
#endif
|
|
183
|
+
|
|
184
|
+
#ifdef STORAGE_BUFFER_AVAILABLE
|
|
185
|
+
@group(2) @binding(1) var<storage, read> paletteStorage : array<mat4x4<f32>>;
|
|
186
|
+
@group(2) @binding(2) var<storage, read> previousPaletteStorage : array<mat4x4<f32>>;
|
|
187
|
+
#else
|
|
188
|
+
@group(2) @binding(1) var<uniform> paletteUniform : array<mat4x4<f32>, 255>;
|
|
189
|
+
@group(2) @binding(2) var<uniform> previousPaletteUniform : array<mat4x4<f32>, 255>;
|
|
190
|
+
#endif
|
|
191
|
+
|
|
192
|
+
fn skinPalette(index : u32) -> mat4x4<f32> {
|
|
193
|
+
#ifdef STORAGE_BUFFER_AVAILABLE
|
|
194
|
+
return paletteStorage[index];
|
|
176
195
|
#else
|
|
177
|
-
|
|
178
|
-
@group(2) @binding(2) var<uniform> previousPalette : array<mat4x4<f32>, 255>;
|
|
196
|
+
return paletteUniform[index];
|
|
179
197
|
#endif
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
fn previousSkinPalette(index : u32) -> mat4x4<f32> {
|
|
201
|
+
#ifdef STORAGE_BUFFER_AVAILABLE
|
|
202
|
+
return previousPaletteStorage[index];
|
|
203
|
+
#else
|
|
204
|
+
return previousPaletteUniform[index];
|
|
205
|
+
#endif
|
|
206
|
+
}
|
|
180
207
|
|
|
181
208
|
struct VsIn {
|
|
182
209
|
@location(0) pos : vec3<f32>,
|
|
@@ -204,7 +231,6 @@ struct VsOut {
|
|
|
204
231
|
@location(1) worldNormal : vec3<f32>,
|
|
205
232
|
@location(2) uv : vec2<f32>,
|
|
206
233
|
@location(3) worldTangent : vec4<f32>,
|
|
207
|
-
@location(4) @interpolate(flat) instanceIdx : u32,
|
|
208
234
|
// feat-city-glb multi-UV tiling: second UV set varying at location 5
|
|
209
235
|
// (parity with default-standard-pbr.wgsl).
|
|
210
236
|
@location(5) uv1 : vec2<f32>,
|
|
@@ -217,6 +243,9 @@ struct VsOut {
|
|
|
217
243
|
#ifdef VERTEX_COLOR_AVAILABLE
|
|
218
244
|
@location(14) color : vec4<f32>,
|
|
219
245
|
#endif
|
|
246
|
+
// Vertex-produced Standard cluster coordinates. Fragment builtin position
|
|
247
|
+
// is framebuffer-space and cannot recover the original clip-space NDC.
|
|
248
|
+
@location(6) ndc : vec3<f32>,
|
|
220
249
|
@location(7) viewZ : f32,
|
|
221
250
|
};
|
|
222
251
|
|
|
@@ -229,8 +258,7 @@ fn pick_channel(rgba : vec4<f32>, channelIndex : u32) -> f32 {
|
|
|
229
258
|
}
|
|
230
259
|
}
|
|
231
260
|
|
|
232
|
-
|
|
233
|
-
fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
261
|
+
fn vs_main_impl(in : VsIn, idx : u32) -> VsOut {
|
|
234
262
|
// 4-bone weighted skinning (plan-strategy D-3 / D-3a).
|
|
235
263
|
// The host pre-computes each joint matrix as worldFromJoint * inverseBindMatrix
|
|
236
264
|
// (CPU-side pre-multiplication, per D-4) and writes them into the palette
|
|
@@ -242,10 +270,10 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
|
242
270
|
// The host sets the BindGroup dynamic offset to (byteOffset / 64) so the
|
|
243
271
|
// first palette entry visible to this draw is palette[0]. The shader
|
|
244
272
|
// indexes into palette directly using the per-vertex skinIndex values.
|
|
245
|
-
let skinMatrix =
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
273
|
+
let skinMatrix = skinPalette(in.skinIndex.x) * in.skinWeight.x +
|
|
274
|
+
skinPalette(in.skinIndex.y) * in.skinWeight.y +
|
|
275
|
+
skinPalette(in.skinIndex.z) * in.skinWeight.z +
|
|
276
|
+
skinPalette(in.skinIndex.w) * in.skinWeight.w;
|
|
249
277
|
|
|
250
278
|
// glTF 2.0 sec.Skins Implementation Note: when a mesh node has a skin
|
|
251
279
|
// property, the joint matrices already encode the global transform of each
|
|
@@ -257,7 +285,7 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
|
257
285
|
// skinnedLocal IS the world position -- no additional left-multiply by
|
|
258
286
|
// meshes[0].worldFromLocal or instanceLocal is needed.
|
|
259
287
|
//
|
|
260
|
-
// This removes the implicit contract "Skin entity
|
|
288
|
+
// This removes the implicit contract "Skin entity GlobalTransform.world must be
|
|
261
289
|
// identity" -- a skin entity can be parented under any Transform chain and
|
|
262
290
|
// the skinned mesh will rigidly follow via joint propagation alone.
|
|
263
291
|
let skinnedLocal = skinMatrix * vec4<f32>(in.pos, 1.0);
|
|
@@ -298,14 +326,27 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
|
298
326
|
#ifdef VERTEX_COLOR_AVAILABLE
|
|
299
327
|
out.color = in.color;
|
|
300
328
|
#endif
|
|
301
|
-
out.instanceIdx = idx;
|
|
302
329
|
// feat-20260613-csm-cascaded-shadow-maps M5 / w19: viewZ replaces the
|
|
303
330
|
// prior light-space-position varying; evalDirectional picks the cascade
|
|
304
331
|
// matrix per fragment from viewZ + worldPos.
|
|
305
|
-
|
|
332
|
+
let clipPos = out.clip;
|
|
333
|
+
out.ndc = vec3(clipPos.xy / clipPos.w, clipPos.z / clipPos.w);
|
|
334
|
+
// Keep the cluster depth exactly aligned with the CPU binner for both
|
|
335
|
+
// perspective and off-axis orthographic projections.
|
|
336
|
+
out.viewZ = sceneViewZ(out.clip, view.temporalProjection);
|
|
306
337
|
return out;
|
|
307
338
|
}
|
|
308
339
|
|
|
340
|
+
@vertex
|
|
341
|
+
fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
342
|
+
return vs_main_impl(in, idx);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
@vertex
|
|
346
|
+
fn vs_scene_index(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
|
|
347
|
+
return vs_main_impl(in, idx);
|
|
348
|
+
}
|
|
349
|
+
|
|
309
350
|
fn transformedMaterialUv(transform : vec4<f32>, metadata : vec4<f32>, in : VsOut) -> vec2<f32> {
|
|
310
351
|
var source = in.uv;
|
|
311
352
|
if (metadata.x >= 1.0) { source = in.uv1; }
|
|
@@ -377,16 +418,34 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
377
418
|
prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
|
|
378
419
|
);
|
|
379
420
|
let skyColor = vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB);
|
|
421
|
+
let skyFactor = skyColor * skylight.intensity;
|
|
380
422
|
var ambient = (kD * irradiance * albedo + specularIbl) * (vec3<f32>(1.0) - coatF);
|
|
423
|
+
#ifdef PROBE_BLEND_AVAILABLE
|
|
424
|
+
// group(3) is the per-object bind group; record zero is the object selected
|
|
425
|
+
// by this draw, independent of instance count.
|
|
426
|
+
let probeShPreblend = array<vec4<f32>, 9>(
|
|
427
|
+
probeBlendRecords[1], probeBlendRecords[2], probeBlendRecords[3],
|
|
428
|
+
probeBlendRecords[4], probeBlendRecords[5], probeBlendRecords[6],
|
|
429
|
+
probeBlendRecords[7], probeBlendRecords[8], probeBlendRecords[9],
|
|
430
|
+
);
|
|
431
|
+
let probeLocalBlendFraction = probeBlendRecords[0].z;
|
|
432
|
+
let probeDiffuseK = kD / max(vec3<f32>(1.0 - metallic), vec3<f32>(0.0001));
|
|
433
|
+
let probeDiffuse = evaluateProbeDiffuse(probeShPreblend, probeLocalBlendFraction, n, irradiance * skyFactor, probeDiffuseK, albedo, metallic);
|
|
434
|
+
ambient = (probeDiffuse + specularIbl * skyFactor) * (vec3<f32>(1.0) - coatF);
|
|
435
|
+
#endif
|
|
381
436
|
if (material.clearcoat != 0.0) {
|
|
382
437
|
let clearcoatIbl = sampleIblSpecular(
|
|
383
438
|
n, v, coatRoughness, vec3<f32>(0.04),
|
|
384
439
|
skylight.rotation,
|
|
385
440
|
prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
|
|
386
441
|
);
|
|
387
|
-
ambient = ambient + clearcoatIbl * material.clearcoat;
|
|
442
|
+
ambient = ambient + clearcoatIbl * material.clearcoat * skyFactor;
|
|
388
443
|
}
|
|
389
|
-
|
|
444
|
+
#ifdef PROBE_BLEND_AVAILABLE
|
|
445
|
+
ambient = ambient;
|
|
446
|
+
#else
|
|
447
|
+
ambient = ambient * skyFactor;
|
|
448
|
+
#endif
|
|
390
449
|
var color = ambient;
|
|
391
450
|
let directionalShadow = evalDirectionalShadowFactor(n, in.worldPos, in.viewZ);
|
|
392
451
|
let directionalBase = evalDirectionalNoShadow(n, v, albedo, metallic, a, f0);
|
|
@@ -397,65 +456,17 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
397
456
|
);
|
|
398
457
|
color = color + directionalShadow * material.clearcoat * directionalClearcoat;
|
|
399
458
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
in.worldPos, n, v, albedo, metallic, a, f0,
|
|
409
|
-
p.shadowAtlasLayer, lane.x, lane.y, 0.005, 0.05,
|
|
410
|
-
);
|
|
411
|
-
if (material.clearcoat != 0.0) {
|
|
412
|
-
color = color + material.clearcoat * evalPointShadowed(
|
|
413
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
414
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
415
|
-
p.shadowAtlasLayer, lane.x, lane.y, 0.005, 0.05,
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
} else {
|
|
419
|
-
color = color + evalPoint(
|
|
420
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
421
|
-
in.worldPos, n, v, albedo, metallic, a, f0,
|
|
422
|
-
);
|
|
423
|
-
if (material.clearcoat != 0.0) {
|
|
424
|
-
color = color + material.clearcoat * evalPoint(
|
|
425
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
426
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
#else
|
|
431
|
-
color = color + evalPoint(
|
|
432
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
433
|
-
in.worldPos, n, v, albedo, metallic, a, f0,
|
|
434
|
-
);
|
|
435
|
-
if (material.clearcoat != 0.0) {
|
|
436
|
-
color = color + material.clearcoat * evalPoint(
|
|
437
|
-
p.position, p.colorTimesIntensity, p.invRangeSquared,
|
|
438
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
#endif
|
|
442
|
-
}
|
|
443
|
-
let spotCount = spotLightsBuffer.count;
|
|
444
|
-
for (var i: u32 = 0u; i < spotCount; i = i + 1u) {
|
|
445
|
-
let s = spotLightsBuffer.slots[i];
|
|
446
|
-
color = color + evalSpot(
|
|
447
|
-
s.position, s.direction, s.colorTimesIntensity,
|
|
448
|
-
s.cosInner, s.cosOuter, s.invRangeSquared,
|
|
449
|
-
in.worldPos, n, v, albedo, metallic, a, f0,
|
|
459
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
460
|
+
color = color + evaluateStandardClusterLights(
|
|
461
|
+
in.ndc, in.viewZ, in.worldPos, n, v, albedo, metallic, a, f0, false,
|
|
462
|
+
);
|
|
463
|
+
if (material.clearcoat != 0.0) {
|
|
464
|
+
color = color + material.clearcoat * evaluateStandardClusterLights(
|
|
465
|
+
in.ndc, in.viewZ, in.worldPos, n, v,
|
|
466
|
+
vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04), false,
|
|
450
467
|
);
|
|
451
|
-
if (material.clearcoat != 0.0) {
|
|
452
|
-
color = color + material.clearcoat * evalSpot(
|
|
453
|
-
s.position, s.direction, s.colorTimesIntensity,
|
|
454
|
-
s.cosInner, s.cosOuter, s.invRangeSquared,
|
|
455
|
-
in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
468
|
}
|
|
469
|
+
#endif
|
|
459
470
|
return vec4<f32>(color, alpha);
|
|
460
471
|
}
|
|
461
472
|
|
|
@@ -471,6 +482,7 @@ struct TemporalVsOut {
|
|
|
471
482
|
@location(7) uv7 : vec2<f32>,
|
|
472
483
|
@location(8) @interpolate(perspective) currentClip : vec4<f32>,
|
|
473
484
|
@location(9) @interpolate(perspective) previousClip : vec4<f32>,
|
|
485
|
+
@location(10) @interpolate(flat) reactive : f32,
|
|
474
486
|
#ifdef VERTEX_COLOR_AVAILABLE
|
|
475
487
|
@location(14) color : vec4<f32>,
|
|
476
488
|
#endif
|
|
@@ -478,20 +490,25 @@ struct TemporalVsOut {
|
|
|
478
490
|
|
|
479
491
|
@vertex
|
|
480
492
|
fn vs_temporal(in : VsIn, @builtin(instance_index) idx : u32) -> TemporalVsOut {
|
|
481
|
-
let currentSkin =
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
let previousSkin =
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
493
|
+
let currentSkin = skinPalette(in.skinIndex.x) * in.skinWeight.x +
|
|
494
|
+
skinPalette(in.skinIndex.y) * in.skinWeight.y +
|
|
495
|
+
skinPalette(in.skinIndex.z) * in.skinWeight.z +
|
|
496
|
+
skinPalette(in.skinIndex.w) * in.skinWeight.w;
|
|
497
|
+
let previousSkin = previousSkinPalette(in.skinIndex.x) * in.skinWeight.x +
|
|
498
|
+
previousSkinPalette(in.skinIndex.y) * in.skinWeight.y +
|
|
499
|
+
previousSkinPalette(in.skinIndex.z) * in.skinWeight.z +
|
|
500
|
+
previousSkinPalette(in.skinIndex.w) * in.skinWeight.w;
|
|
489
501
|
let currentWorld = currentSkin * vec4<f32>(in.pos, 1.0);
|
|
490
502
|
let previousWorld = previousSkin * vec4<f32>(in.pos, 1.0);
|
|
491
503
|
var out : TemporalVsOut;
|
|
492
504
|
out.currentClip = view.temporalCurrentViewProj * currentWorld;
|
|
493
505
|
out.clip = out.currentClip;
|
|
494
506
|
out.previousClip = view.temporalPreviousViewProj * previousWorld;
|
|
507
|
+
#if STORAGE_BUFFER_AVAILABLE == true
|
|
508
|
+
out.reactive = meshes[0].temporal.x;
|
|
509
|
+
#else
|
|
510
|
+
out.reactive = 1.0;
|
|
511
|
+
#endif
|
|
495
512
|
out.uv = in.uv;
|
|
496
513
|
out.uv1 = in.uv1;
|
|
497
514
|
out.uv2 = in.uv2;
|
|
@@ -517,10 +534,9 @@ fn temporalVertexAlpha(in : TemporalVsOut) -> f32 {
|
|
|
517
534
|
|
|
518
535
|
@fragment
|
|
519
536
|
fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
let reactive = 1.0;
|
|
537
|
+
var reactive = 0.0;
|
|
538
|
+
#ifdef STORAGE_BUFFER_AVAILABLE
|
|
539
|
+
reactive = meshes[0].temporal.x;
|
|
524
540
|
#endif
|
|
525
541
|
return projectPbrSceneTemporal(
|
|
526
542
|
material.baseColor.a * temporalVertexAlpha(in),
|
|
@@ -532,7 +548,7 @@ fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
|
|
|
532
548
|
in.currentClip,
|
|
533
549
|
in.previousClip,
|
|
534
550
|
view.temporalProjection,
|
|
535
|
-
reactive,
|
|
551
|
+
in.reactive,
|
|
536
552
|
in.uv, in.uv1, in.uv2, in.uv3,
|
|
537
553
|
in.uv4, in.uv5, in.uv6, in.uv7,
|
|
538
554
|
);
|