@forgeax/engine-shader 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +35 -0
- package/README.md +39 -0
- package/dist/ShaderRegistry.d.ts +8 -0
- package/dist/ShaderRegistry.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +312 -71
- package/dist/index.mjs.map +1 -1
- package/dist/material/artifact-types.d.ts +12 -2
- package/dist/material/artifact-types.d.ts.map +1 -1
- package/dist/material-schemas.d.ts +5 -0
- package/dist/material-schemas.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/ShaderRegistry.ts +10 -0
- package/src/__tests__/auto-exposure-graph.unit.test.ts +59 -0
- package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +30 -15
- package/src/__tests__/builtin-texture-sampling-contract.test.ts +1 -1
- package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +2 -1
- package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +167 -6
- package/src/__tests__/direct-light-layout.unit.test.ts +1 -1
- package/src/__tests__/lighting-punctual.unit.test.ts +29 -1
- package/src/__tests__/ltc-provenance.unit.test.ts +13 -2
- package/src/__tests__/material-builtins.unit.test.ts +1 -0
- package/src/__tests__/material-contract.unit.test.ts +42 -9
- package/src/__tests__/material-derived-builtins.integration.test.ts +18 -2
- package/src/__tests__/probe-lighting-composition.unit.test.ts +1 -1
- package/src/__tests__/reflection-probe-sampling.unit.test.ts +23 -2
- package/src/__tests__/scene-temporal.unit.test.ts +10 -0
- package/src/__tests__/shader.unit.test.ts +1 -0
- package/src/__tests__/sprite-variants.unit.test.ts +1 -0
- package/src/__tests__/ssr-artifact.unit.test.ts +141 -0
- package/src/__tests__/ssr-bgl.integration.test.ts +185 -0
- package/src/__tests__/standard-output-domain.unit.test.ts +36 -0
- package/src/__tests__/standard-pbr-artifact-receipt.unit.test.ts +93 -0
- package/src/__tests__/standard-surface-pass-evaluation.integration.test.ts +16 -4
- package/src/__tests__/surface-v1.unit.test.ts +6 -0
- package/src/__tests__/taa-resolve.unit.test.ts +20 -9
- package/src/__tests__/transmission-thickness-scale.unit.test.ts +6 -1
- package/src/__tests__/transparent-pbr.unit.test.ts +1 -1
- package/src/__tests__/vertex-color-variant.unit.test.ts +4 -1
- package/src/atmosphere-background.wgsl +3 -3
- package/src/auto-exposure-meter.wgsl +179 -0
- package/src/color-lut.wgsl +17 -0
- package/src/common.wgsl +7 -5
- package/src/default-standard-pbr-skin.wgsl +142 -40
- package/src/default-standard-pbr.wgsl +198 -82
- package/src/default_standard_surface.wgsl +56 -19
- package/src/fxaa.wgsl +9 -5
- package/src/ibl-sampling.wgsl +30 -6
- package/src/index.ts +189 -0
- package/src/lighting-punctual.wgsl +5 -6
- package/src/material/artifact-types.ts +109 -63
- package/src/material-schemas.ts +84 -8
- package/src/output-encoding.wgsl +10 -0
- package/src/pbr-temporal.wgsl +9 -1
- package/src/scene-temporal.wgsl +2 -0
- package/src/shadow-pcf.wgsl +6 -8
- package/src/shadow-surface.wgsl +16 -0
- package/src/shadow_caster.wgsl +99 -61
- package/src/sprite-lit.wgsl +4 -4
- package/src/sprite.wgsl +3 -3
- package/src/ssr-compose.wgsl +129 -0
- package/src/ssr-hiz-reduce.wgsl +53 -0
- package/src/ssr-hiz.wgsl +66 -0
- package/src/ssr-temporal.wgsl +305 -0
- package/src/ssr-trace.wgsl +784 -0
- package/src/standard-cluster.wgsl +6 -4
- package/src/standard-surface.wgsl +696 -0
- package/src/surface_v1.wgsl +6 -0
- package/src/taa-resolve.wgsl +222 -28
- package/src/tbn.wgsl +1 -1
- package/src/tonemap.wgsl +37 -18
- package/src/unlit.wgsl +3 -3
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
#define_import_path forgeax_material::standard_surface
|
|
2
|
+
#import forgeax_view::common::{view, sampleMaterialTexture}
|
|
3
|
+
#import forgeax_pbr::brdf::{f_schlick}
|
|
4
|
+
#import forgeax_pbr::ibl_sampling::{sampleIblDiffuse, sampleIblSpecular, sampleReflectionProbeSpecular}
|
|
5
|
+
#import forgeax_pbr::lighting_probe::{evaluateProbeDiffuse}
|
|
6
|
+
#import forgeax_pbr::tbn::{decodeTangentSpaceNormalRg, scaleTangentSpaceNormal, applyTBN}
|
|
7
|
+
#import forgeax_pbr::lighting_directional::{evalDirectionalNoShadow, evalDirectionalShadowFactor}
|
|
8
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
9
|
+
#import forgeax_standard::cluster::{evaluateStandardClusterLights, get_ssao_intensity}
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
// Shared Standard material ABI and surface evaluation. Geometry adapters supply
|
|
13
|
+
// world-space inputs; Render owns View, material/environment, Cluster, and shadow
|
|
14
|
+
// resources. This module imports no scene mesh or instance transform table.
|
|
15
|
+
|
|
16
|
+
struct Material {
|
|
17
|
+
baseColor : vec4<f32>,
|
|
18
|
+
metallic : f32,
|
|
19
|
+
roughness : f32,
|
|
20
|
+
// Channel selectors (D-8): 4 independent f32 entries split out of the
|
|
21
|
+
// legacy `channelMap : vec4<u32>`. Each value is a small integer encoded
|
|
22
|
+
// as f32 in {0.0, 1.0, 2.0, 3.0} indexing into {r,g,b,a}. Default glTF 2.0
|
|
23
|
+
// packing = (metallicChannel=2, roughnessChannel=1, aoChannel=0,
|
|
24
|
+
// extraChannel=0). The fragment casts to u32 at the pick site -- f32 is
|
|
25
|
+
// chosen so the schema entry type aligns with the 14-tuple
|
|
26
|
+
// MaterialParamType numeric-run packing rule (4-byte stride, 16 B span).
|
|
27
|
+
metallicChannel : f32,
|
|
28
|
+
roughnessChannel : f32,
|
|
29
|
+
aoChannel : f32,
|
|
30
|
+
extraChannel : f32,
|
|
31
|
+
// vec3 align=16 inserts 8 implicit padding bytes after extraChannel
|
|
32
|
+
// (offsets 40..48) so emissive lands at offset 48.
|
|
33
|
+
emissive : vec3<f32>,
|
|
34
|
+
emissiveIntensity : f32,
|
|
35
|
+
occlusionStrength : f32,
|
|
36
|
+
alphaCutoff : f32,
|
|
37
|
+
clearcoat : f32,
|
|
38
|
+
clearcoatRoughness : f32,
|
|
39
|
+
specularTint : vec3<f32>,
|
|
40
|
+
normalScale : f32,
|
|
41
|
+
transmission : f32,
|
|
42
|
+
ior : f32,
|
|
43
|
+
thickness : f32,
|
|
44
|
+
attenuationColor : vec3<f32>,
|
|
45
|
+
attenuationDistance : f32,
|
|
46
|
+
baseColorTextureCoordinatesTransform : vec4<f32>,
|
|
47
|
+
baseColorTextureCoordinatesMetadata : vec4<f32>,
|
|
48
|
+
metallicRoughnessTextureCoordinatesTransform : vec4<f32>,
|
|
49
|
+
metallicRoughnessTextureCoordinatesMetadata : vec4<f32>,
|
|
50
|
+
normalTextureCoordinatesTransform : vec4<f32>,
|
|
51
|
+
normalTextureCoordinatesMetadata : vec4<f32>,
|
|
52
|
+
specularTintTextureCoordinatesTransform : vec4<f32>,
|
|
53
|
+
specularTintTextureCoordinatesMetadata : vec4<f32>,
|
|
54
|
+
emissiveTextureCoordinatesTransform : vec4<f32>,
|
|
55
|
+
emissiveTextureCoordinatesMetadata : vec4<f32>,
|
|
56
|
+
occlusionTextureCoordinatesTransform : vec4<f32>,
|
|
57
|
+
occlusionTextureCoordinatesMetadata : vec4<f32>,
|
|
58
|
+
transmissionTextureCoordinatesTransform : vec4<f32>,
|
|
59
|
+
transmissionTextureCoordinatesMetadata : vec4<f32>,
|
|
60
|
+
thicknessTextureCoordinatesTransform : vec4<f32>,
|
|
61
|
+
thicknessTextureCoordinatesMetadata : vec4<f32>,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
@group(1) @binding(0) var<uniform> material : Material;
|
|
65
|
+
@group(1) @binding(1) var baseColorTexture_sampler : sampler;
|
|
66
|
+
@group(1) @binding(2) var baseColorTexture : texture_2d<f32>;
|
|
67
|
+
@group(1) @binding(3) var metallicRoughnessTexture_sampler : sampler;
|
|
68
|
+
@group(1) @binding(4) var metallicRoughnessTexture : texture_2d<f32>;
|
|
69
|
+
@group(1) @binding(5) var normalTexture_sampler : sampler;
|
|
70
|
+
@group(1) @binding(6) var normalTexture : texture_2d<f32>;
|
|
71
|
+
@group(1) @binding(7) var specularTintSampler : sampler;
|
|
72
|
+
@group(1) @binding(8) var specularTintTexture : texture_2d<f32>;
|
|
73
|
+
@group(1) @binding(9) var emissiveTexture_sampler : sampler;
|
|
74
|
+
@group(1) @binding(10) var emissiveTexture : texture_2d<f32>;
|
|
75
|
+
@group(1) @binding(11) var occlusionTexture_sampler : sampler;
|
|
76
|
+
@group(1) @binding(12) var occlusionTexture : texture_2d<f32>;
|
|
77
|
+
@group(1) @binding(13) var transmissionSampler : sampler;
|
|
78
|
+
@group(1) @binding(14) var transmissionTexture : texture_2d<f32>;
|
|
79
|
+
@group(1) @binding(15) var thicknessSampler : sampler;
|
|
80
|
+
@group(1) @binding(16) var thicknessTexture : texture_2d<f32>;
|
|
81
|
+
#ifdef TRANSMISSION_AVAILABLE
|
|
82
|
+
@group(1) @binding(24) var transmissionBackdropSampler : sampler;
|
|
83
|
+
@group(1) @binding(25) var transmissionBackdropTexture : texture_2d<f32>;
|
|
84
|
+
#endif
|
|
85
|
+
|
|
86
|
+
// Naga reflection does not retain filtering usage through the generic shared
|
|
87
|
+
// helper. These compile-time-only witnesses retain the binding contract while
|
|
88
|
+
// every runtime material sample still goes through sampleMaterialTexture.
|
|
89
|
+
fn materialTextureFilteringWitness() {
|
|
90
|
+
let base = baseColorTexture;
|
|
91
|
+
let metallicRoughness = metallicRoughnessTexture;
|
|
92
|
+
let normal = normalTexture;
|
|
93
|
+
let specularTint = specularTintTexture;
|
|
94
|
+
let emissive = emissiveTexture;
|
|
95
|
+
let occlusion = occlusionTexture;
|
|
96
|
+
let baseWitness = textureSample(base, baseColorTexture_sampler, vec2<f32>(0.0));
|
|
97
|
+
let metallicRoughnessWitness = textureSample(metallicRoughness, metallicRoughnessTexture_sampler, vec2<f32>(0.0));
|
|
98
|
+
let normalWitness = textureSample(normal, normalTexture_sampler, vec2<f32>(0.0));
|
|
99
|
+
let specularTintWitness = textureSample(specularTint, specularTintSampler, vec2<f32>(0.0));
|
|
100
|
+
let emissiveWitness = textureSample(emissive, emissiveTexture_sampler, vec2<f32>(0.0));
|
|
101
|
+
let occlusionWitness = textureSample(occlusion, occlusionTexture_sampler, vec2<f32>(0.0));
|
|
102
|
+
let transmission = transmissionTexture;
|
|
103
|
+
let thickness = thicknessTexture;
|
|
104
|
+
let transmissionWitness = textureSample(transmission, transmissionSampler, vec2<f32>(0.0));
|
|
105
|
+
let thicknessWitness = textureSample(thickness, thicknessSampler, vec2<f32>(0.0));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Skylight bindings merged into the PBR material BGL
|
|
109
|
+
// (feat-20260520-skylight-ibl-cubemap M3 / t48 round-4 amend per D-5
|
|
110
|
+
// round-4 REVISED). The round-2 stand-alone group 4 Skylight BGL collided
|
|
111
|
+
// with WebGPU's default maxBindGroups=4 in chrome-beta and blocked pbr-pl
|
|
112
|
+
// pipeline-layout creation; round-4 appends the 7 Skylight entries to the
|
|
113
|
+
// PBR material BindGroupLayout after the eight Standard texture pairs. The material BG factory
|
|
114
|
+
// (mergeSkylightIntoMaterialBgl in
|
|
115
|
+
// packages/runtime/src/ibl/skylight-bind-group.ts) extends the layout to
|
|
116
|
+
// merged entries; render-system-record assembles a single merged material
|
|
117
|
+
// BindGroup (no extra setBindGroup(4) call). Identity (default) resources
|
|
118
|
+
// produce ambient = 0; with a real Skylight, ibl_sampling helpers project
|
|
119
|
+
// the IBL irradiance + split-sum specular into `ambient` below.
|
|
120
|
+
struct SkylightUniforms {
|
|
121
|
+
intensity : f32,
|
|
122
|
+
// The former pad0/1/2 lanes now carry the linear-space ambient `color` tint
|
|
123
|
+
// (downstream integration #4). Kept as three scalars (NOT vec3<f32>) so the
|
|
124
|
+
// struct stays exactly 16 B: a vec3 has 16-byte alignment in std140 and
|
|
125
|
+
// would push `color` to offset 16. The rotation vec4 follows at offset 16,
|
|
126
|
+
// so the host writes one 32 B payload.
|
|
127
|
+
colorR : f32,
|
|
128
|
+
colorG : f32,
|
|
129
|
+
colorB : f32,
|
|
130
|
+
rotation : vec4<f32>,
|
|
131
|
+
};
|
|
132
|
+
@group(1) @binding(17) var irradianceMap : texture_cube<f32>;
|
|
133
|
+
@group(1) @binding(18) var irradianceSampler : sampler;
|
|
134
|
+
@group(1) @binding(19) var prefilterMap : texture_cube<f32>;
|
|
135
|
+
@group(1) @binding(20) var prefilterSampler : sampler;
|
|
136
|
+
@group(1) @binding(21) var brdfLut : texture_2d<f32>;
|
|
137
|
+
@group(1) @binding(22) var brdfLutSampler : sampler;
|
|
138
|
+
@group(1) @binding(23) var<uniform> skylight : SkylightUniforms;
|
|
139
|
+
|
|
140
|
+
#ifdef PROBE_BLEND_AVAILABLE
|
|
141
|
+
// Optional consumer lane. The host must provide the retained 160B record
|
|
142
|
+
// buffer and matching BGL before enabling this capability.
|
|
143
|
+
@group(3) @binding(1) var<storage, read> probeBlendRecords : array<vec4<f32>>;
|
|
144
|
+
#endif
|
|
145
|
+
|
|
146
|
+
// feat-20260612-hdrp-ssao M7 (round 2) D-B + D-C, scope-amend-webgl2-ubo:
|
|
147
|
+
// SSAO sampling lives on the HDRP unified BGL @group(2) alongside the
|
|
148
|
+
// cluster bindings (binding 7 = ssao texture, binding 8 = sampler). The
|
|
149
|
+
// intensity scalar is folded into `cluster_uniform.near_far_log.w` (the
|
|
150
|
+
// previously-unused std140 pad lane on @binding(6)) — declaring a
|
|
151
|
+
// dedicated UBO at @binding(9) overflows WebGL2's
|
|
152
|
+
// `max_uniform_buffers_per_shader_stage = 11` budget on rhi-wgpu's
|
|
153
|
+
// fallback path. Disabled SSAO path binds 1x1 white at @binding(7) + the
|
|
154
|
+
// host writes intensity=0 into the cluster pad lane, so the synthesis
|
|
155
|
+
// collapses identically.
|
|
156
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
157
|
+
@group(2) @binding(7) var ssaoBlurredTexture : texture_2d<f32>;
|
|
158
|
+
@group(2) @binding(8) var ssaoBlurredSampler : sampler;
|
|
159
|
+
#endif
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
struct VsOut {
|
|
163
|
+
@builtin(position) clip : vec4<f32>,
|
|
164
|
+
@location(0) worldPos : vec3<f32>,
|
|
165
|
+
@location(1) worldNormal : vec3<f32>,
|
|
166
|
+
@location(2) uv : vec2<f32>,
|
|
167
|
+
@location(3) worldTangent : vec4<f32>,
|
|
168
|
+
#ifdef TRANSMISSION_AVAILABLE
|
|
169
|
+
// Keep mesh and instance storage vertex-only; transmission receives the
|
|
170
|
+
// transform basis from the vertex stage below.
|
|
171
|
+
@location(4) @interpolate(flat) transmissionBasisFirst : vec4<f32>,
|
|
172
|
+
#endif
|
|
173
|
+
// feat-city-glb multi-UV tiling: second UV set inter-stage varying. Uses the
|
|
174
|
+
// previously-vacant @location(5) so @location(6)/(7) stay byte-stable with
|
|
175
|
+
// the prior layout (CSM M5/w19).
|
|
176
|
+
@location(5) uvOne : vec2<f32>,
|
|
177
|
+
@location(8) uvTwo : vec2<f32>,
|
|
178
|
+
@location(9) uvThree : vec2<f32>,
|
|
179
|
+
@location(10) uvFour : vec2<f32>,
|
|
180
|
+
@location(11) uvFive : vec2<f32>,
|
|
181
|
+
@location(12) uvSix : vec2<f32>,
|
|
182
|
+
@location(13) uvSeven : vec2<f32>,
|
|
183
|
+
#ifdef VERTEX_COLOR_AVAILABLE
|
|
184
|
+
@location(14) color : vec4<f32>,
|
|
185
|
+
#endif
|
|
186
|
+
@location(6) ndc : vec4<f32>, // NDC for HDRP cluster lookup; .w = transmission basis tail
|
|
187
|
+
// feat-20260609-hdrp-cluster-fragment-ggx M4.5-followup: view-space z is
|
|
188
|
+
// needed by ndc_position_to_cluster (slice index uses log-z mapping that
|
|
189
|
+
// takes negative view_z, NOT NDC z which is [0,1]). Earlier `in.ndc.z`
|
|
190
|
+
// pass through view_z slot collapsed every fragment to slice 0, so cube
|
|
191
|
+
// surfaces -- whose cluster cells were unrelated to the floor's slice 0
|
|
192
|
+
// hot zone -- received zero light. Forward view_z explicitly. M5 / w19:
|
|
193
|
+
// also feeds CSM cascade selection in evalDirectional.
|
|
194
|
+
@location(7) viewZ : f32,
|
|
195
|
+
#ifdef TRANSMISSION_AVAILABLE
|
|
196
|
+
@location(15) @interpolate(flat) transmissionBasisSecond : vec4<f32>,
|
|
197
|
+
#endif
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
fn pick_channel(rgba : vec4<f32>, channelIndex : u32) -> f32 {
|
|
201
|
+
// Branch-free channel pick. WGSL has no array<f32, 4>(rgba) addressable
|
|
202
|
+
// helper; manual switch keeps the path uniform.
|
|
203
|
+
switch (channelIndex) {
|
|
204
|
+
case 0u: { return rgba.r; }
|
|
205
|
+
case 1u: { return rgba.g; }
|
|
206
|
+
case 2u: { return rgba.b; }
|
|
207
|
+
default: { return rgba.a; }
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Directional light evaluation lives in forgeax_pbr::lighting_directional.
|
|
212
|
+
// Local lights are evaluated by forgeax_standard::cluster when the clustered
|
|
213
|
+
// variant is selected; keeping that ownership in one module avoids a second
|
|
214
|
+
// View-group light-array ABI.
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
fn transformedMaterialUv(transform : vec4<f32>, metadata : vec4<f32>, in : VsOut) -> vec2<f32> {
|
|
219
|
+
var source = in.uv;
|
|
220
|
+
if (metadata.x >= 1.0) { source = in.uvOne; }
|
|
221
|
+
if (metadata.x >= 2.0) { source = in.uvTwo; }
|
|
222
|
+
if (metadata.x >= 3.0) { source = in.uvThree; }
|
|
223
|
+
if (metadata.x >= 4.0) { source = in.uvFour; }
|
|
224
|
+
if (metadata.x >= 5.0) { source = in.uvFive; }
|
|
225
|
+
if (metadata.x >= 6.0) { source = in.uvSix; }
|
|
226
|
+
if (metadata.x >= 7.0) { source = in.uvSeven; }
|
|
227
|
+
let scaled = source * transform.zw;
|
|
228
|
+
let angle = metadata.y;
|
|
229
|
+
let c = cos(angle);
|
|
230
|
+
let s = sin(angle);
|
|
231
|
+
return vec2<f32>(scaled.x * c - scaled.y * s, scaled.x * s + scaled.y * c) + transform.xy;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fn materialAlpha(baseSample : vec4<f32>) -> f32 {
|
|
235
|
+
return material.baseColor.a * baseSample.a;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
fn materialVertexColor(in : VsOut) -> vec4<f32> {
|
|
239
|
+
#ifdef VERTEX_COLOR_AVAILABLE
|
|
240
|
+
return in.color;
|
|
241
|
+
#else
|
|
242
|
+
return vec4<f32>(1.0);
|
|
243
|
+
#endif
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
fn finiteScalar(value : f32, fallback : f32) -> f32 {
|
|
247
|
+
let bounded = clamp(value, -65504.0, 65504.0);
|
|
248
|
+
return select(fallback, bounded, value == value);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
fn finiteColor(value : vec3<f32>, fallback : vec3<f32>) -> vec3<f32> {
|
|
252
|
+
return vec3<f32>(
|
|
253
|
+
finiteScalar(value.x, fallback.x),
|
|
254
|
+
finiteScalar(value.y, fallback.y),
|
|
255
|
+
finiteScalar(value.z, fallback.z),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// linearColorDomain: transparent source and destination values are blended
|
|
260
|
+
// before any display encoding. The fixed-function material blend state uses
|
|
261
|
+
// this same equation for the render target's declared linear domain.
|
|
262
|
+
fn blendLinearTransparent(
|
|
263
|
+
source : vec3<f32>,
|
|
264
|
+
destination : vec3<f32>,
|
|
265
|
+
alpha : f32,
|
|
266
|
+
) -> vec3<f32> {
|
|
267
|
+
return source * alpha + destination * (1.0 - alpha);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// linearHdrColorDomain: fs_main writes linear HDR when its target is HDR.
|
|
271
|
+
// toneStageInput: the value remains linear until the fullscreen tone stage.
|
|
272
|
+
|
|
273
|
+
fn alphaTest(alpha : f32) {
|
|
274
|
+
if (material.alphaCutoff > 0.0 && alpha <= material.alphaCutoff) {
|
|
275
|
+
discard;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// The renderer supplies the retained ProbeBlendRecord at the per-object
|
|
280
|
+
// consumer boundary. This helper deliberately accepts only diffuse inputs;
|
|
281
|
+
// Skylight/ReflectionProbe continue to own specular.
|
|
282
|
+
fn composeProbeDiffuse(
|
|
283
|
+
shPreblend : array<vec4<f32>, 9>,
|
|
284
|
+
localBlendFraction : f32,
|
|
285
|
+
normal : vec3<f32>,
|
|
286
|
+
skyIrradiance : vec3<f32>,
|
|
287
|
+
kD : vec3<f32>,
|
|
288
|
+
albedo : vec3<f32>,
|
|
289
|
+
metallic : f32,
|
|
290
|
+
) -> vec3<f32> {
|
|
291
|
+
return evaluateProbeDiffuse(shPreblend, localBlendFraction, normal, skyIrradiance, kD, albedo, metallic);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
struct StandardPbrOutput {
|
|
295
|
+
@location(0) color : vec4<f32>,
|
|
296
|
+
#ifdef REFLECTION_FALLBACK_AVAILABLE
|
|
297
|
+
// Linear HDR environment contribution from this same Standard BRDF callsite.
|
|
298
|
+
// The detached producer names this second target S_fallback.
|
|
299
|
+
@location(1) reflectionFallback : vec4<f32>,
|
|
300
|
+
#endif
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// Shared surface evaluator: vertex producers supply geometry and factor values;
|
|
304
|
+
// material textures, direct lights, IBL, and shadow sampling retain one owner.
|
|
305
|
+
struct StandardSurfaceFactors {
|
|
306
|
+
baseColor : vec4<f32>,
|
|
307
|
+
metallic : f32,
|
|
308
|
+
roughness : f32,
|
|
309
|
+
emissive : vec3<f32>,
|
|
310
|
+
emissiveIntensity : f32,
|
|
311
|
+
clearcoat : f32,
|
|
312
|
+
clearcoatRoughness : f32,
|
|
313
|
+
lighting : bool,
|
|
314
|
+
receiveShadows : bool,
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
fn evaluateStandardSurface(in : VsOut, factors : StandardSurfaceFactors) -> StandardPbrOutput {
|
|
320
|
+
let baseUv = transformedMaterialUv(material.baseColorTextureCoordinatesTransform, material.baseColorTextureCoordinatesMetadata, in);
|
|
321
|
+
let baseSample = sampleMaterialTexture(baseColorTexture, baseColorTexture_sampler, baseUv, material.baseColorTextureCoordinatesMetadata.zw);
|
|
322
|
+
let vertexColor = materialVertexColor(in);
|
|
323
|
+
alphaTest(factors.baseColor.a * baseSample.a * vertexColor.a);
|
|
324
|
+
let alpha = factors.baseColor.a * baseSample.a * vertexColor.a;
|
|
325
|
+
let albedo = factors.baseColor.rgb * baseSample.rgb * vertexColor.rgb;
|
|
326
|
+
|
|
327
|
+
// Metallic-roughness texture sampling with per-field channel selectors
|
|
328
|
+
// (D-8): glTF 2.0 default layout B=metallic, G=roughness, R=occlusion is
|
|
329
|
+
// encoded by the host as 4 independent f32 selectors in the merged UBO
|
|
330
|
+
// (metallicChannel/roughnessChannel/aoChannel/extraChannel). Cast to u32
|
|
331
|
+
// at the pick_channel call site; values stay in {0,1,2,3}.
|
|
332
|
+
let mrUv = transformedMaterialUv(material.metallicRoughnessTextureCoordinatesTransform, material.metallicRoughnessTextureCoordinatesMetadata, in);
|
|
333
|
+
let mrSample = sampleMaterialTexture(metallicRoughnessTexture, metallicRoughnessTexture_sampler, mrUv, material.metallicRoughnessTextureCoordinatesMetadata.zw);
|
|
334
|
+
let metallic = clamp(
|
|
335
|
+
finiteScalar(factors.metallic, 0.0) *
|
|
336
|
+
finiteScalar(pick_channel(mrSample, u32(material.metallicChannel)), 1.0),
|
|
337
|
+
0.0,
|
|
338
|
+
1.0,
|
|
339
|
+
);
|
|
340
|
+
let roughnessTex = clamp(
|
|
341
|
+
finiteScalar(pick_channel(mrSample, u32(material.roughnessChannel)), 1.0),
|
|
342
|
+
0.0,
|
|
343
|
+
1.0,
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Layer-2 fail-fast: shader internal clamp keeps D_GGX finite for
|
|
347
|
+
// roughness=0 even if the asset somehow bypasses layer-1 register
|
|
348
|
+
// fail-fast (plan-strategy section 5.3 + AC-02 (b)+(c)). Apply to the
|
|
349
|
+
// material scalar prior to the channelMap-extracted texture multiplier
|
|
350
|
+
// so the final roughness >= 0.04 floor is preserved (texture sample is
|
|
351
|
+
// a 1.0-default white when metallicRoughnessTexture is absent, so
|
|
352
|
+
// multiplying after the clamp keeps the floor intact).
|
|
353
|
+
var a = max(finiteScalar(factors.roughness, 0.5), 0.04);
|
|
354
|
+
a = a * roughnessTex;
|
|
355
|
+
a = a * a;
|
|
356
|
+
|
|
357
|
+
// TBN basis composed via forgeax_pbr::tbn helpers; default fallback
|
|
358
|
+
// (defaultNormalTextureView) RG=(128,128) -> tangent (0,0,1) -> world n
|
|
359
|
+
// unchanged.
|
|
360
|
+
let normalUv = transformedMaterialUv(material.normalTextureCoordinatesTransform, material.normalTextureCoordinatesMetadata, in);
|
|
361
|
+
let normSampleRg = sampleMaterialTexture(normalTexture, normalTexture_sampler, normalUv, material.normalTextureCoordinatesMetadata.zw).rg;
|
|
362
|
+
let normTangent = scaleTangentSpaceNormal(
|
|
363
|
+
decodeTangentSpaceNormalRg(normSampleRg), material.normalScale,
|
|
364
|
+
);
|
|
365
|
+
let n = applyTBN(in.worldNormal, in.worldTangent, normTangent);
|
|
366
|
+
|
|
367
|
+
let v = normalize(view.cameraPos - in.worldPos);
|
|
368
|
+
let specularUv = transformedMaterialUv(material.specularTintTextureCoordinatesTransform, material.specularTintTextureCoordinatesMetadata, in);
|
|
369
|
+
let specularTint = material.specularTint * sampleMaterialTexture(
|
|
370
|
+
specularTintTexture, specularTintSampler, specularUv, material.specularTintTextureCoordinatesMetadata.zw,
|
|
371
|
+
).rgb;
|
|
372
|
+
let safeIor = max(finiteScalar(material.ior, 1.5), 1.0);
|
|
373
|
+
let dielectricF0 = pow((safeIor - 1.0) / (safeIor + 1.0), 2.0);
|
|
374
|
+
let f0 = mix(vec3<f32>(dielectricF0) * specularTint, albedo, metallic);
|
|
375
|
+
var diffuseAlbedo = albedo;
|
|
376
|
+
#ifdef TRANSMISSION_AVAILABLE
|
|
377
|
+
let transmissionUv = transformedMaterialUv(
|
|
378
|
+
material.transmissionTextureCoordinatesTransform,
|
|
379
|
+
material.transmissionTextureCoordinatesMetadata,
|
|
380
|
+
in,
|
|
381
|
+
);
|
|
382
|
+
let thicknessUv = transformedMaterialUv(
|
|
383
|
+
material.thicknessTextureCoordinatesTransform,
|
|
384
|
+
material.thicknessTextureCoordinatesMetadata,
|
|
385
|
+
in,
|
|
386
|
+
);
|
|
387
|
+
let transmissionSample = sampleMaterialTexture(
|
|
388
|
+
transmissionTexture,
|
|
389
|
+
transmissionSampler,
|
|
390
|
+
transmissionUv,
|
|
391
|
+
material.transmissionTextureCoordinatesMetadata.zw,
|
|
392
|
+
).r;
|
|
393
|
+
let thicknessSample = sampleMaterialTexture(
|
|
394
|
+
thicknessTexture,
|
|
395
|
+
thicknessSampler,
|
|
396
|
+
thicknessUv,
|
|
397
|
+
material.thicknessTextureCoordinatesMetadata.zw,
|
|
398
|
+
).g;
|
|
399
|
+
let transmissionFactor = clamp(
|
|
400
|
+
finiteScalar(material.transmission, 0.0) * finiteScalar(transmissionSample, 1.0),
|
|
401
|
+
0.0,
|
|
402
|
+
1.0,
|
|
403
|
+
);
|
|
404
|
+
let viewDot = finiteScalar(dot(n, v), 0.0);
|
|
405
|
+
let refractionFromInside = viewDot < 0.0;
|
|
406
|
+
let refractionNormal = select(n, -n, refractionFromInside);
|
|
407
|
+
let refractionEta = select(1.0 / safeIor, safeIor, refractionFromInside);
|
|
408
|
+
let incident = -v;
|
|
409
|
+
let refracted = refract(incident, refractionNormal, refractionEta);
|
|
410
|
+
let refractedLengthSquared = dot(refracted, refracted);
|
|
411
|
+
let viewCos = clamp(abs(viewDot), 0.0, 1.0);
|
|
412
|
+
let fresnel = clamp(f_schlick(viewCos, vec3<f32>(dielectricF0)).x, 0.0, 1.0);
|
|
413
|
+
// `refract` returns the zero vector for total internal reflection. Keep the
|
|
414
|
+
// reflection/IBL term above authoritative and remove transmission energy
|
|
415
|
+
// instead of sampling and adding a second reflected environment.
|
|
416
|
+
let transmittedEnergy = select(
|
|
417
|
+
0.0,
|
|
418
|
+
transmissionFactor * (1.0 - metallic) * (1.0 - fresnel),
|
|
419
|
+
refractedLengthSquared > 1e-6,
|
|
420
|
+
);
|
|
421
|
+
diffuseAlbedo = albedo * (1.0 - transmittedEnergy);
|
|
422
|
+
#endif
|
|
423
|
+
let coatRoughness = max(factors.clearcoatRoughness, 0.04);
|
|
424
|
+
let coatAlpha = coatRoughness * coatRoughness;
|
|
425
|
+
let coatF = f_schlick(max(dot(n, v), 0.0), vec3<f32>(0.04)) * factors.clearcoat;
|
|
426
|
+
|
|
427
|
+
// Ambient (IBL) + 1 + N + N accumulation
|
|
428
|
+
// (feat-20260520-skylight-ibl-cubemap M3 / t48 +
|
|
429
|
+
// feat-20260520-directional-light-shadow-mapping +
|
|
430
|
+
// feat-20260518-pbr-direct-lighting-mvp Finding 4):
|
|
431
|
+
//
|
|
432
|
+
// color = ambient(IBL) + directional(shadowed) + sum(point) + sum(spot)
|
|
433
|
+
//
|
|
434
|
+
// When the host Skylight bind group provides default-zero resources the IBL
|
|
435
|
+
// helpers sample to vec3(0) and ambient = 0, so the shader falls through to
|
|
436
|
+
// direct lighting naturally (zero contribution, no branch, no #if guard).
|
|
437
|
+
//
|
|
438
|
+
// sampleIblDiffuse / sampleIblSpecular are imported from
|
|
439
|
+
// forgeax_pbr::ibl_sampling (ibl-sampling.wgsl) and take the
|
|
440
|
+
// @group(1) @binding(7..13) Skylight resources as function arguments
|
|
441
|
+
// -- the runtime helper module is zero-binding so the host owns the
|
|
442
|
+
// binding layout (round-4 amend: Skylight merged into material BGL).
|
|
443
|
+
// `factors.roughness` is the unsquared scalar; `a` above is the
|
|
444
|
+
// alpha*alpha form used by direct-light D_GGX, which is wrong for the
|
|
445
|
+
// split-sum mip lookup, so we re-derive the post-shader-clamp roughness
|
|
446
|
+
// here (matches sampleIblSpecular's `mip = roughness * 4.0` expectation).
|
|
447
|
+
//
|
|
448
|
+
// Direct lights (1 + N + N): one directional + N point + N spot, summed
|
|
449
|
+
// sequentially. LIGHT_ARRAY_MAX_SLOTS = 4 host-side bounds the loop trip
|
|
450
|
+
// counts. The directional path additionally projects worldPos through the
|
|
451
|
+
// per-cascade light-space matrix in the fragment stage for shadow-map PCF
|
|
452
|
+
// lookup (CSM, feat-20260613). Cluster owns punctual BRDF evaluation in the
|
|
453
|
+
// clustered variant above.
|
|
454
|
+
let kD = (vec3<f32>(1.0) - f_schlick(max(dot(n, v), 0.0), f0)) * (1.0 - metallic);
|
|
455
|
+
let iblRoughness = clamp(max(finiteScalar(factors.roughness, 0.5), 0.04) * roughnessTex, 0.04, 1.0);
|
|
456
|
+
var irradiance = vec3<f32>(0.0);
|
|
457
|
+
var specularIbl = vec3<f32>(0.0);
|
|
458
|
+
if (skylight.intensity < 0.0) {
|
|
459
|
+
// A negative intensity is the renderer-owned probe sentinel. The producer
|
|
460
|
+
// stores -(probeIntensity + 1), so intensity=0 remains distinguishable
|
|
461
|
+
// from the ordinary Skylight zero payload. The four color/rotation lanes
|
|
462
|
+
// carry probe center and half extents in this mode; Skylight resources
|
|
463
|
+
// remain unchanged and therefore preserve the ABI.
|
|
464
|
+
specularIbl = sampleReflectionProbeSpecular(
|
|
465
|
+
n, v, iblRoughness, f0, in.worldPos,
|
|
466
|
+
vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB),
|
|
467
|
+
skylight.rotation.xyz, vec4<f32>(0.0, 0.0, 0.0, 1.0),
|
|
468
|
+
prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
|
|
469
|
+
);
|
|
470
|
+
} else {
|
|
471
|
+
irradiance = sampleIblDiffuse(n, skylight.rotation, irradianceMap, irradianceSampler);
|
|
472
|
+
specularIbl = sampleIblSpecular(
|
|
473
|
+
n, v, iblRoughness, f0,
|
|
474
|
+
skylight.rotation,
|
|
475
|
+
prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
let occlusionUv = transformedMaterialUv(material.occlusionTextureCoordinatesTransform, material.occlusionTextureCoordinatesMetadata, in);
|
|
479
|
+
let aoSample = sampleMaterialTexture(occlusionTexture, occlusionTexture_sampler, occlusionUv, material.occlusionTextureCoordinatesMetadata.zw);
|
|
480
|
+
let ao = mix(1.0, aoSample.r, material.occlusionStrength);
|
|
481
|
+
// feat-20260612-hdrp-ssao M7 round-2: `var` (mutable) so the
|
|
482
|
+
// CLUSTER_FORWARD_AVAILABLE branch below can `ambient *=` the SSAO
|
|
483
|
+
// factor. The non-HDRP path leaves ambient untouched.
|
|
484
|
+
let skyColor = vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB);
|
|
485
|
+
let skyFactor = skyColor * skylight.intensity;
|
|
486
|
+
var reflectionFallback = specularIbl * (vec3<f32>(1.0) - coatF);
|
|
487
|
+
var ambient = (kD * irradiance * diffuseAlbedo + specularIbl) * (vec3<f32>(1.0) - coatF);
|
|
488
|
+
#ifdef PROBE_BLEND_AVAILABLE
|
|
489
|
+
// group(3) is the per-object bind group; record zero is the object selected
|
|
490
|
+
// by this draw, independent of instance count.
|
|
491
|
+
let probeShPreblend = array<vec4<f32>, 9>(
|
|
492
|
+
probeBlendRecords[1], probeBlendRecords[2], probeBlendRecords[3],
|
|
493
|
+
probeBlendRecords[4], probeBlendRecords[5], probeBlendRecords[6],
|
|
494
|
+
probeBlendRecords[7], probeBlendRecords[8], probeBlendRecords[9],
|
|
495
|
+
);
|
|
496
|
+
let probeLocalBlendFraction = probeBlendRecords[0].z;
|
|
497
|
+
let probeDiffuseK = kD / max(vec3<f32>(1.0 - metallic), vec3<f32>(0.0001));
|
|
498
|
+
let probeDiffuse = composeProbeDiffuse(probeShPreblend, probeLocalBlendFraction, n, irradiance * skyFactor, probeDiffuseK, albedo, metallic);
|
|
499
|
+
ambient = (probeDiffuse + specularIbl * skyFactor) * (vec3<f32>(1.0) - coatF);
|
|
500
|
+
#endif
|
|
501
|
+
if (factors.clearcoat != 0.0) {
|
|
502
|
+
// Keep the clearcoat lobe on the same selected environment as the base
|
|
503
|
+
// specular lobe. Probe mode reuses the packed center/extents lanes in the
|
|
504
|
+
// per-probe uniform; routing it through global Skylight IBL would make the
|
|
505
|
+
// fallback a mixed-source projection and break c=1 source equivalence.
|
|
506
|
+
var clearcoatIbl = vec3<f32>(0.0);
|
|
507
|
+
if (skylight.intensity < 0.0) {
|
|
508
|
+
clearcoatIbl = sampleReflectionProbeSpecular(
|
|
509
|
+
n, v, coatRoughness, vec3<f32>(0.04), in.worldPos,
|
|
510
|
+
vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB),
|
|
511
|
+
skylight.rotation.xyz, vec4<f32>(0.0, 0.0, 0.0, 1.0),
|
|
512
|
+
prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
|
|
513
|
+
);
|
|
514
|
+
} else {
|
|
515
|
+
clearcoatIbl = sampleIblSpecular(
|
|
516
|
+
n, v, coatRoughness, vec3<f32>(0.04),
|
|
517
|
+
skylight.rotation,
|
|
518
|
+
prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
let clearcoatContribution = clearcoatIbl * factors.clearcoat;
|
|
522
|
+
#ifdef PROBE_BLEND_AVAILABLE
|
|
523
|
+
if (skylight.intensity < 0.0) {
|
|
524
|
+
ambient = ambient + clearcoatContribution;
|
|
525
|
+
} else {
|
|
526
|
+
ambient = ambient + clearcoatIbl * factors.clearcoat * skyFactor;
|
|
527
|
+
}
|
|
528
|
+
#else
|
|
529
|
+
ambient = ambient + clearcoatContribution;
|
|
530
|
+
#endif
|
|
531
|
+
reflectionFallback = reflectionFallback + clearcoatContribution;
|
|
532
|
+
}
|
|
533
|
+
#ifdef PROBE_BLEND_AVAILABLE
|
|
534
|
+
ambient = ambient * ao;
|
|
535
|
+
#else
|
|
536
|
+
if (skylight.intensity < 0.0) {
|
|
537
|
+
let environmentScale = max(-skylight.intensity - 1.0, 0.0) * ao;
|
|
538
|
+
ambient = ambient * environmentScale;
|
|
539
|
+
reflectionFallback = reflectionFallback * environmentScale;
|
|
540
|
+
} else {
|
|
541
|
+
let environmentScale = skyColor * skylight.intensity * ao;
|
|
542
|
+
ambient = ambient * environmentScale;
|
|
543
|
+
reflectionFallback = reflectionFallback * environmentScale;
|
|
544
|
+
}
|
|
545
|
+
#endif
|
|
546
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
547
|
+
// feat-20260612-hdrp-ssao M2 round-1 + M7 round-2 (plan-strategy D-7 + D-B + D-C):
|
|
548
|
+
// SSAO ambient synthesis. Reads the half-res R8 `ssaoBlurredTexture` from
|
|
549
|
+
// the ssao-blur pass (HDRP unified BGL @group(2) @binding(7..9)). The host
|
|
550
|
+
// always binds those slots: when SSAO is disabled, binding 7 receives a
|
|
551
|
+
// 1x1 white fallback (AO=1.0) and binding 9 receives a zero-intensity
|
|
552
|
+
// uniform — `mix(1.0, ssao*ao, 0.0) = 1.0` so ambient collapses to the
|
|
553
|
+
// round-1 baseline (no PSO recompile across the toggle).
|
|
554
|
+
//
|
|
555
|
+
// Sampling uses the screen-space NDC -> [0,1] uv; we recover it from the
|
|
556
|
+
// clip-space xy that the vertex stage already emits via in.ndc.xy.
|
|
557
|
+
let ssaoUv = in.ndc.xy * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5, 0.5);
|
|
558
|
+
let ssaoFactor = textureSample(ssaoBlurredTexture, ssaoBlurredSampler, ssaoUv).r;
|
|
559
|
+
// scope-amend-webgl2-ubo: intensity packed into cluster_uniform.near_far_log.w.
|
|
560
|
+
let ssaoIntensity = get_ssao_intensity();
|
|
561
|
+
let ssaoScale = mix(1.0, ssaoFactor * ao, ssaoIntensity);
|
|
562
|
+
ambient *= mix(1.0, ssaoFactor * ao, ssaoIntensity);
|
|
563
|
+
reflectionFallback *= ssaoScale;
|
|
564
|
+
#endif
|
|
565
|
+
var color = ambient;
|
|
566
|
+
#ifdef TRANSMISSION_AVAILABLE
|
|
567
|
+
let screenUv = in.ndc.xy * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5);
|
|
568
|
+
// Read the same renderer-owned transform tables that the vertex stage uses
|
|
569
|
+
// instead of adding six more interstage varyings. WebGPU guarantees only 16
|
|
570
|
+
// user-defined vertex output locations; keeping the affine conversion in
|
|
571
|
+
// the fragment stage preserves that contract and keeps thickness in the
|
|
572
|
+
// glTF unit-scale mesh space before measuring the refracted segment in world
|
|
573
|
+
// metres.
|
|
574
|
+
let localToWorld0 = in.transmissionBasisFirst.xyz;
|
|
575
|
+
let localToWorld1 = vec3<f32>(
|
|
576
|
+
in.transmissionBasisFirst.w,
|
|
577
|
+
in.transmissionBasisSecond.x,
|
|
578
|
+
in.transmissionBasisSecond.y,
|
|
579
|
+
);
|
|
580
|
+
let localToWorld2 = vec3<f32>(
|
|
581
|
+
in.transmissionBasisSecond.z,
|
|
582
|
+
in.transmissionBasisSecond.w,
|
|
583
|
+
in.ndc.w,
|
|
584
|
+
);
|
|
585
|
+
let inverseCofactor0 = cross(localToWorld1, localToWorld2);
|
|
586
|
+
let inverseCofactor1 = cross(localToWorld2, localToWorld0);
|
|
587
|
+
let inverseCofactor2 = cross(localToWorld0, localToWorld1);
|
|
588
|
+
let localToWorldDet = dot(localToWorld0, inverseCofactor0);
|
|
589
|
+
let safeLocalToWorldDet = select(1.0, localToWorldDet, abs(localToWorldDet) >= 1e-6);
|
|
590
|
+
let worldToLocal0 = vec3<f32>(
|
|
591
|
+
inverseCofactor0.x,
|
|
592
|
+
inverseCofactor1.x,
|
|
593
|
+
inverseCofactor2.x,
|
|
594
|
+
) / safeLocalToWorldDet;
|
|
595
|
+
let worldToLocal1 = vec3<f32>(
|
|
596
|
+
inverseCofactor0.y,
|
|
597
|
+
inverseCofactor1.y,
|
|
598
|
+
inverseCofactor2.y,
|
|
599
|
+
) / safeLocalToWorldDet;
|
|
600
|
+
let worldToLocal2 = vec3<f32>(
|
|
601
|
+
inverseCofactor0.z,
|
|
602
|
+
inverseCofactor1.z,
|
|
603
|
+
inverseCofactor2.z,
|
|
604
|
+
) / safeLocalToWorldDet;
|
|
605
|
+
let refractedLocal = normalize(
|
|
606
|
+
worldToLocal0 * refracted.x +
|
|
607
|
+
worldToLocal1 * refracted.y +
|
|
608
|
+
worldToLocal2 * refracted.z,
|
|
609
|
+
);
|
|
610
|
+
let worldRefractedDirection =
|
|
611
|
+
localToWorld0 * refractedLocal.x +
|
|
612
|
+
localToWorld1 * refractedLocal.y +
|
|
613
|
+
localToWorld2 * refractedLocal.z;
|
|
614
|
+
let worldThickness = max(
|
|
615
|
+
finiteScalar(material.thickness, 0.0) * length(worldRefractedDirection) *
|
|
616
|
+
finiteScalar(thicknessSample, 1.0),
|
|
617
|
+
0.0,
|
|
618
|
+
);
|
|
619
|
+
let refractedUv = screenUv + (refracted.xy - incident.xy) * worldThickness * 0.25;
|
|
620
|
+
let guardBand = 0.02;
|
|
621
|
+
let insideGuardBand = all(refractedUv >= vec2<f32>(guardBand)) &&
|
|
622
|
+
all(refractedUv <= vec2<f32>(1.0 - guardBand));
|
|
623
|
+
let backdropMipCount = textureNumLevels(transmissionBackdropTexture);
|
|
624
|
+
let backdropMaxLod = max(f32(backdropMipCount) - 1.0, 0.0);
|
|
625
|
+
let backdropLod = clamp(iblRoughness * iblRoughness * backdropMaxLod, 0.0, backdropMaxLod);
|
|
626
|
+
let unrefractedBackdrop = textureSampleLevel(
|
|
627
|
+
transmissionBackdropTexture,
|
|
628
|
+
transmissionBackdropSampler,
|
|
629
|
+
clamp(screenUv, vec2<f32>(0.0), vec2<f32>(1.0)),
|
|
630
|
+
backdropLod,
|
|
631
|
+
).rgb;
|
|
632
|
+
var transmittedBackdrop = unrefractedBackdrop;
|
|
633
|
+
if (refractedLengthSquared > 1e-6 && insideGuardBand) {
|
|
634
|
+
transmittedBackdrop = textureSampleLevel(
|
|
635
|
+
transmissionBackdropTexture,
|
|
636
|
+
transmissionBackdropSampler,
|
|
637
|
+
clamp(refractedUv, vec2<f32>(0.0), vec2<f32>(1.0)),
|
|
638
|
+
backdropLod,
|
|
639
|
+
).rgb;
|
|
640
|
+
}
|
|
641
|
+
let safeAttenuationColor = clamp(
|
|
642
|
+
finiteColor(material.attenuationColor, vec3<f32>(1.0)),
|
|
643
|
+
vec3<f32>(0.0),
|
|
644
|
+
vec3<f32>(1.0),
|
|
645
|
+
);
|
|
646
|
+
let safeAttenuationDistance = max(finiteScalar(material.attenuationDistance, 0.0), 0.0);
|
|
647
|
+
let attenuationExponent = worldThickness / max(safeAttenuationDistance, 1e-6);
|
|
648
|
+
let beerAttenuation = select(
|
|
649
|
+
vec3<f32>(1.0),
|
|
650
|
+
pow(safeAttenuationColor, vec3<f32>(attenuationExponent)),
|
|
651
|
+
safeAttenuationDistance > 1e-6 && worldThickness > 0.0,
|
|
652
|
+
);
|
|
653
|
+
color = color + finiteColor(transmittedBackdrop, vec3<f32>(0.0)) *
|
|
654
|
+
transmittedEnergy * beerAttenuation;
|
|
655
|
+
#endif
|
|
656
|
+
let directionalShadow = select(1.0, evalDirectionalShadowFactor(n, in.worldPos, in.viewZ), factors.receiveShadows);
|
|
657
|
+
let directionalBase = evalDirectionalNoShadow(n, v, diffuseAlbedo, metallic, a, f0);
|
|
658
|
+
color = color + directionalShadow * directionalBase;
|
|
659
|
+
if (factors.clearcoat != 0.0) {
|
|
660
|
+
let directionalClearcoat = evalDirectionalNoShadow(
|
|
661
|
+
n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
|
|
662
|
+
);
|
|
663
|
+
color = color + directionalShadow * factors.clearcoat * directionalClearcoat;
|
|
664
|
+
}
|
|
665
|
+
#ifdef CLUSTER_FORWARD_AVAILABLE
|
|
666
|
+
// NDC from vertex shader (perspective-divided clip-space, interpolated).
|
|
667
|
+
// view_z: NDC depth for cluster Z-slice lookup.
|
|
668
|
+
color = color + evaluateStandardClusterLights(
|
|
669
|
+
in.ndc.xyz, in.viewZ, in.worldPos, n, v, diffuseAlbedo, metallic, a, f0, false, factors.receiveShadows,
|
|
670
|
+
);
|
|
671
|
+
if (factors.clearcoat != 0.0) {
|
|
672
|
+
color = color + factors.clearcoat * evaluateStandardClusterLights(
|
|
673
|
+
in.ndc.xyz, in.viewZ, in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04), false, factors.receiveShadows,
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
#else
|
|
677
|
+
// Local lights are cluster-owned in the current render topology. The
|
|
678
|
+
// no-cluster variant intentionally remains directional + IBL only; this
|
|
679
|
+
// keeps its group(0) contract aligned with the latest Standard pipeline,
|
|
680
|
+
// where point/spot data no longer lives in the View bind group.
|
|
681
|
+
#endif // CLUSTER_FORWARD_AVAILABLE
|
|
682
|
+
let emissiveUv = transformedMaterialUv(material.emissiveTextureCoordinatesTransform, material.emissiveTextureCoordinatesMetadata, in);
|
|
683
|
+
let emissiveSample = sampleMaterialTexture(emissiveTexture, emissiveTexture_sampler, emissiveUv, material.emissiveTextureCoordinatesMetadata.zw).rgb;
|
|
684
|
+
// Evaluate texture derivatives uniformly; the per-instance lighting switch
|
|
685
|
+
// selects the result, never a divergent implicit-derivative sampling path.
|
|
686
|
+
color = select(albedo, color, factors.lighting) + factors.emissive * factors.emissiveIntensity * emissiveSample;
|
|
687
|
+
var output : StandardPbrOutput;
|
|
688
|
+
output.color = vec4<f32>(color, alpha);
|
|
689
|
+
#ifdef REFLECTION_FALLBACK_AVAILABLE
|
|
690
|
+
output.reflectionFallback = vec4<f32>(select(vec3<f32>(0.0), reflectionFallback, factors.lighting), 1.0);
|
|
691
|
+
#endif
|
|
692
|
+
return output;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// ── G-buffer output struct (feat-20260612-hdrp-deferred-shading M2 / w12) ──
|
|
696
|
+
//
|