babylonjs-editor-tools 5.5.0 → 5.5.1-alpha.0
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/build/index.node.js +2330 -1
- package/build/src/index.js +4 -0
- package/build/src/index.js.map +1 -1
- package/build/src/loading/loader.js +7 -2
- package/build/src/loading/loader.js.map +1 -1
- package/build/src/loading/mesh.js +22 -0
- package/build/src/loading/mesh.js.map +1 -0
- package/build/src/loading/rendering.js +4 -0
- package/build/src/loading/rendering.js.map +1 -1
- package/build/src/loading/transform-node.js +4 -1
- package/build/src/loading/transform-node.js.map +1 -1
- package/build/src/rendering/tools.js +7 -0
- package/build/src/rendering/tools.js.map +1 -1
- package/build/src/rendering/volumetric/pipeline.js +915 -0
- package/build/src/rendering/volumetric/pipeline.js.map +1 -0
- package/build/src/rendering/volumetric/selector.js +525 -0
- package/build/src/rendering/volumetric/selector.js.map +1 -0
- package/build/src/rendering/volumetric/shaders-wgsl.js +1207 -0
- package/build/src/rendering/volumetric/shaders-wgsl.js.map +1 -0
- package/build/src/rendering/volumetric/shaders.js +1293 -0
- package/build/src/rendering/volumetric/shaders.js.map +1 -0
- package/build/src/rendering/volumetric/types.js +301 -0
- package/build/src/rendering/volumetric/types.js.map +1 -0
- package/build/src/rendering/volumetric-lighting.js +72 -0
- package/build/src/rendering/volumetric-lighting.js.map +1 -0
- package/build/src/tools/guards.js +14 -0
- package/build/src/tools/guards.js.map +1 -1
- package/declaration/src/index.d.ts +4 -0
- package/declaration/src/loading/mesh.d.ts +1 -0
- package/declaration/src/rendering/tools.d.ts +1 -0
- package/declaration/src/rendering/volumetric/pipeline.d.ts +231 -0
- package/declaration/src/rendering/volumetric/selector.d.ts +263 -0
- package/declaration/src/rendering/volumetric/shaders-wgsl.d.ts +21 -0
- package/declaration/src/rendering/volumetric/shaders.d.ts +60 -0
- package/declaration/src/rendering/volumetric/types.d.ts +362 -0
- package/declaration/src/rendering/volumetric-lighting.d.ts +28 -0
- package/declaration/src/tools/guards.d.ts +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1207 @@
|
|
|
1
|
+
import { maxVolumetricShadowSlots } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* WGSL version of the four passes of the volumetric lighting rendering pipeline, for WebGPU.
|
|
4
|
+
*
|
|
5
|
+
* This is a hand written port of the GLSL in "shaders.ts" rather than a transpilation: Babylon.js can only
|
|
6
|
+
* turn GLSL into WGSL by downloading twgsl from its CDN, which an offline Electron application can't rely on.
|
|
7
|
+
* Both versions are driven by exactly the same "#define" set, so the pipeline, the light selection and the
|
|
8
|
+
* shader shape key are shared and only the source of the passes differs.
|
|
9
|
+
*
|
|
10
|
+
* Babylon.js emulates the conventions of WebGL on WebGPU: render targets are stored bottom-up and
|
|
11
|
+
* "fragmentInputs.position" is flipped like "gl_FragCoord" when needed, so the integer texel fetches below
|
|
12
|
+
* address the textures exactly like their GLSL counterparts.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Unpacks a depth packed in the four channels of an 8 bits RGBA texture, the same way Babylon.js does.
|
|
16
|
+
*/
|
|
17
|
+
const packingHelpers = /* wgsl */ `
|
|
18
|
+
fn volUnpack(color: vec4f) -> f32 {
|
|
19
|
+
let bitShift = vec4f(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0);
|
|
20
|
+
return dot(color, bitShift);
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
/**
|
|
24
|
+
* Reads the full resolution depth map of the depth renderer, bound as "depthSampler". Requires the
|
|
25
|
+
* "volCameraMinMaxZ" and "volDepthUnpack" uniforms.
|
|
26
|
+
*/
|
|
27
|
+
const depthMapHelpers = /* wgsl */ `
|
|
28
|
+
fn volSampleDepth(uv: vec2f) -> f32 {
|
|
29
|
+
#ifdef VOL_DEPTH_PACKED
|
|
30
|
+
return volUnpack(textureSampleLevel(depthSampler, depthSamplerSampler, uv, 0.0));
|
|
31
|
+
#else
|
|
32
|
+
return textureSampleLevel(depthSampler, depthSamplerSampler, uv, 0.0).r;
|
|
33
|
+
#endif
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Converts the raw value stored in the depth map into a distance along the view axis, in scene units.
|
|
37
|
+
fn volLinearDepth(d: f32) -> f32 {
|
|
38
|
+
#ifdef VOL_DEPTH_VIEWZ
|
|
39
|
+
// The depth renderer stores the view space Z directly. The sky is cleared to 0.
|
|
40
|
+
return select(d, uniforms.volCameraMinMaxZ.y, d <= 0.0);
|
|
41
|
+
#else
|
|
42
|
+
// The depth renderer stores "(clipZ + minZ) / (minZ + maxZ)", which is affine in the view space Z
|
|
43
|
+
// for both the perspective and the orthographic projections. "volDepthUnpack" holds the two
|
|
44
|
+
// coefficients of the exact inverse, computed on the CPU from the projection matrix of the scene.
|
|
45
|
+
return uniforms.volDepthUnpack.x * d + uniforms.volDepthUnpack.y;
|
|
46
|
+
#endif
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
/**
|
|
50
|
+
* Reads the linear depth written by the linear depth pass. Requires the "volCameraMinMaxZ" uniform.
|
|
51
|
+
* @param texture defines the name of the texture holding the linear depth.
|
|
52
|
+
*/
|
|
53
|
+
function buildLinearDepthHelpers(texture) {
|
|
54
|
+
return /* wgsl */ `
|
|
55
|
+
// Returns the distance along the view axis, in scene units, of the texel at the given coordinates.
|
|
56
|
+
fn volReadLinearDepth(coordinates: vec2i) -> f32 {
|
|
57
|
+
#ifdef VOL_LINEAR_DEPTH_PACKED
|
|
58
|
+
return volUnpack(textureLoad(${texture}, coordinates, 0)) * uniforms.volCameraMinMaxZ.y;
|
|
59
|
+
#else
|
|
60
|
+
return textureLoad(${texture}, coordinates, 0).r;
|
|
61
|
+
#endif
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Returns the texel of the linear depth under the center of the given texel of the scattering buffer.
|
|
65
|
+
// @see the GLSL version.
|
|
66
|
+
fn volScatterToDepthTexel(scatterTexel: vec2i, depthSize: vec2i) -> vec2i {
|
|
67
|
+
return min(vec2i((vec2f(scatterTexel) + 0.5) * uniforms.volDepthRatio), depthSize - 1);
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The linear depth pass. @see buildVolumetricLightingLinearDepthShader.
|
|
73
|
+
*/
|
|
74
|
+
export function buildVolumetricLightingLinearDepthShaderWGSL() {
|
|
75
|
+
return /* wgsl */ `
|
|
76
|
+
varying vUV: vec2f;
|
|
77
|
+
|
|
78
|
+
var textureSamplerSampler: sampler;
|
|
79
|
+
var textureSampler: texture_2d<f32>;
|
|
80
|
+
var depthSamplerSampler: sampler;
|
|
81
|
+
var depthSampler: texture_2d<f32>;
|
|
82
|
+
|
|
83
|
+
uniform volCameraMinMaxZ: vec2f;
|
|
84
|
+
uniform volDepthUnpack: vec2f;
|
|
85
|
+
|
|
86
|
+
${packingHelpers}
|
|
87
|
+
${depthMapHelpers}
|
|
88
|
+
|
|
89
|
+
fn volPack(depth: f32) -> vec4f {
|
|
90
|
+
let bitShift = vec4f(255.0 * 255.0 * 255.0, 255.0 * 255.0, 255.0, 1.0);
|
|
91
|
+
let bitMask = vec4f(0.0, 1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0);
|
|
92
|
+
|
|
93
|
+
var result = fract(depth * bitShift);
|
|
94
|
+
result -= result.xxyz * bitMask;
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@fragment
|
|
100
|
+
fn main(input: FragmentInputs) -> FragmentOutputs {
|
|
101
|
+
let viewZ = volLinearDepth(volSampleDepth(input.vUV));
|
|
102
|
+
|
|
103
|
+
#ifdef VOL_LINEAR_DEPTH_PACKED
|
|
104
|
+
fragmentOutputs.color = volPack(clamp(viewZ / uniforms.volCameraMinMaxZ.y, 0.0, 0.9999999));
|
|
105
|
+
#else
|
|
106
|
+
fragmentOutputs.color = vec4f(viewZ, 0.0, 0.0, 1.0);
|
|
107
|
+
#endif
|
|
108
|
+
}
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Generates the loop integrating the in-scattering of a point or a spot light over the part of the view ray
|
|
113
|
+
* crossing its volume. @see the GLSL version in "shaders.ts".
|
|
114
|
+
* @param seed defines the expression decorrelating the samples of this light from the ones of the other lights.
|
|
115
|
+
* @param occlusion defines the statement applying the occlusion of the light to "contribution".
|
|
116
|
+
*/
|
|
117
|
+
function buildLocalLightIntegrationWGSL(seed, occlusion) {
|
|
118
|
+
return /* wgsl */ `
|
|
119
|
+
let chordLength = t1 - t0;
|
|
120
|
+
|
|
121
|
+
// The point of the view ray closest to the light splits the chord in two halves over which both the
|
|
122
|
+
// attenuation and the phase function are monotonic, which is where stratified samples do best.
|
|
123
|
+
var closest = clamp(dot(data.xyz, volRayDir), t0, t1);
|
|
124
|
+
if (closest - t0 < 0.02 * chordLength) {
|
|
125
|
+
closest = t0;
|
|
126
|
+
} else if (t1 - closest < 0.02 * chordLength) {
|
|
127
|
+
closest = t1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let count = volLocalSampleCount(chordLength, falloff.x, tEnd - tStart);
|
|
131
|
+
|
|
132
|
+
var firstCount = clamp(i32(f32(count) * (closest - t0) / chordLength + 0.5), 1, count - 1);
|
|
133
|
+
if (closest <= t0) {
|
|
134
|
+
firstCount = 0;
|
|
135
|
+
} else if (closest >= t1) {
|
|
136
|
+
firstCount = count;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let firstWidth = (closest - t0) / f32(max(firstCount, 1));
|
|
140
|
+
let secondWidth = (t1 - closest) / f32(max(count - firstCount, 1));
|
|
141
|
+
let stratum = volStratumOffset(${seed});
|
|
142
|
+
|
|
143
|
+
var result = vec3f(0.0);
|
|
144
|
+
|
|
145
|
+
for (var k: i32 = 0; k < VOL_LOCAL_MAX_STEPS; k++) {
|
|
146
|
+
if (k >= count) {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let inFirstHalf = k < firstCount;
|
|
151
|
+
let width = select(secondWidth, firstWidth, inFirstHalf);
|
|
152
|
+
let t = select(closest + width * (f32(k - firstCount) + stratum), t0 + width * (f32(k) + stratum), inFirstHalf);
|
|
153
|
+
|
|
154
|
+
let position = volRayDir * t;
|
|
155
|
+
let sigmaT = volExtinctionAt(uniforms.volCameraPosition.y + position.y, t);
|
|
156
|
+
|
|
157
|
+
let lightSample = volEvalLocalLight(data, diffuse, direction, falloff, position);
|
|
158
|
+
var contribution = lightSample.xyz;
|
|
159
|
+
let distanceToLight = lightSample.w;
|
|
160
|
+
|
|
161
|
+
if (contribution.r + contribution.g + contribution.b > 0.0) {
|
|
162
|
+
${occlusion}
|
|
163
|
+
result += contribution * (volCombinedTransmittance(t, sigmaT, distanceToLight) * sigmaT * width);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return vec4f(result, 1.0);
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The raymarching pass. @see buildVolumetricLightingScatteringShader for what it computes.
|
|
172
|
+
* @param shadowSlotCount defines the number of shadowed light slots the shader is generated for.
|
|
173
|
+
*/
|
|
174
|
+
export function buildVolumetricLightingScatteringShaderWGSL(shadowSlotCount) {
|
|
175
|
+
const slots = [];
|
|
176
|
+
const globalSlotContributions = [];
|
|
177
|
+
const localSlotContributions = [];
|
|
178
|
+
for (let i = 0; i < shadowSlotCount; ++i) {
|
|
179
|
+
slots.push(
|
|
180
|
+
/* wgsl */ `
|
|
181
|
+
#if VOL_SHADOW_SLOT_COUNT > {X}
|
|
182
|
+
#if VOL_SHADOW_KIND{X} == 2
|
|
183
|
+
var volShadowSampler{X}Sampler: sampler;
|
|
184
|
+
var volShadowSampler{X}: texture_cube<f32>;
|
|
185
|
+
#elif VOL_SHADOW_KIND{X} == 1
|
|
186
|
+
var volShadowSampler{X}Sampler: sampler_comparison;
|
|
187
|
+
var volShadowSampler{X}: texture_depth_2d;
|
|
188
|
+
#else
|
|
189
|
+
var volShadowSampler{X}Sampler: sampler;
|
|
190
|
+
var volShadowSampler{X}: texture_2d<f32>;
|
|
191
|
+
#endif
|
|
192
|
+
|
|
193
|
+
// Returns the shadowing of a point given its position relative to the camera and, for the 2d shadow maps, its
|
|
194
|
+
// position in the clip space of the light. @see the GLSL version.
|
|
195
|
+
fn volShadow{X}(position: vec3f, clip: vec4f) -> f32 {
|
|
196
|
+
let info = uniforms.volShadowInfo[{X}];
|
|
197
|
+
let edge = uniforms.volShadowLightFalloff[{X}].z;
|
|
198
|
+
|
|
199
|
+
#if VOL_SHADOW_KIND{X} == 2
|
|
200
|
+
// Cube shadow map of a point light. It stores the radial distance to the light.
|
|
201
|
+
var toFragment = position - uniforms.volShadowLightData[{X}].xyz;
|
|
202
|
+
let depth = clamp((length(toFragment) + info.z) / info.w, 0.0, 1.0);
|
|
203
|
+
|
|
204
|
+
toFragment = normalize(toFragment);
|
|
205
|
+
toFragment.y = -toFragment.y;
|
|
206
|
+
|
|
207
|
+
#ifdef VOL_SHADOW_PACKED{X}
|
|
208
|
+
let shadowMapSample = volUnpack(textureSampleLevel(volShadowSampler{X}, volShadowSampler{X}Sampler, toFragment, 0.0));
|
|
209
|
+
#else
|
|
210
|
+
let shadowMapSample = textureSampleLevel(volShadowSampler{X}, volShadowSampler{X}Sampler, toFragment, 0.0).x;
|
|
211
|
+
#endif
|
|
212
|
+
|
|
213
|
+
#if VOL_SHADOW_MODE{X} == 1
|
|
214
|
+
// Exponential shadow map, same encoding as the 2d case. "info.y" holds the depth scale.
|
|
215
|
+
return 1.0 - clamp(exp(min(87.0, info.y * depth)) * shadowMapSample, 0.0, 1.0 - info.x);
|
|
216
|
+
#elif VOL_SHADOW_MODE{X} == 2
|
|
217
|
+
return clamp(exp(min(87.0, -info.y * (depth - shadowMapSample))), info.x, 1.0);
|
|
218
|
+
#else
|
|
219
|
+
return select(1.0, info.x, depth > shadowMapSample);
|
|
220
|
+
#endif
|
|
221
|
+
#else
|
|
222
|
+
let clipSpace = clip.xyz / clip.w;
|
|
223
|
+
|
|
224
|
+
// "getTransformMatrix" gives the world to light clip space matrix without the [-1, 1] -> [0, 1] bias.
|
|
225
|
+
#if VOL_SHADOW_KIND{X} == 1
|
|
226
|
+
var uvDepth = 0.5 * clipSpace + vec3f(0.5);
|
|
227
|
+
#ifdef VOL_NDC_HALF_Z
|
|
228
|
+
uvDepth.z = clipSpace.z;
|
|
229
|
+
#endif
|
|
230
|
+
|
|
231
|
+
// Same guard as "computeShadowWithPCF1": past the far plane of the shadow map the hardware
|
|
232
|
+
// comparison would fail against every texel a caster wrote, reporting the sample as occluded
|
|
233
|
+
// while the surfaces at the same distance are lit by the material path.
|
|
234
|
+
let pcfDepthMetric = volDepthMetric(clip, info.zw);
|
|
235
|
+
if (pcfDepthMetric < 0.0 || pcfDepthMetric > 1.0 || uvDepth.x < 0.0 || uvDepth.x > 1.0 || uvDepth.y < 0.0 || uvDepth.y > 1.0) {
|
|
236
|
+
return 1.0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Hardware comparison sampling. "info.y" holds the inverse of the size of the shadow map.
|
|
240
|
+
#if VOL_PCF_TAPS == 4
|
|
241
|
+
let shadow = 0.25 * (
|
|
242
|
+
textureSampleCompareLevel(volShadowSampler{X}, volShadowSampler{X}Sampler, uvDepth.xy + vec2f(-0.5 * info.y, -0.5 * info.y), uvDepth.z) +
|
|
243
|
+
textureSampleCompareLevel(volShadowSampler{X}, volShadowSampler{X}Sampler, uvDepth.xy + vec2f( 0.5 * info.y, -0.5 * info.y), uvDepth.z) +
|
|
244
|
+
textureSampleCompareLevel(volShadowSampler{X}, volShadowSampler{X}Sampler, uvDepth.xy + vec2f(-0.5 * info.y, 0.5 * info.y), uvDepth.z) +
|
|
245
|
+
textureSampleCompareLevel(volShadowSampler{X}, volShadowSampler{X}Sampler, uvDepth.xy + vec2f( 0.5 * info.y, 0.5 * info.y), uvDepth.z)
|
|
246
|
+
);
|
|
247
|
+
#else
|
|
248
|
+
let shadow = textureSampleCompareLevel(volShadowSampler{X}, volShadowSampler{X}Sampler, uvDepth.xy, uvDepth.z);
|
|
249
|
+
#endif
|
|
250
|
+
|
|
251
|
+
return volFallOff(mix(info.x, 1.0, shadow), clipSpace.xy, edge);
|
|
252
|
+
#else
|
|
253
|
+
let uv = 0.5 * clipSpace.xy + vec2f(0.5);
|
|
254
|
+
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
|
|
255
|
+
return 1.0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let depthMetric = clamp(volDepthMetric(clip, info.zw), 0.0, 1.0);
|
|
259
|
+
|
|
260
|
+
#ifdef VOL_SHADOW_PACKED{X}
|
|
261
|
+
let shadowMapSample = volUnpack(textureSampleLevel(volShadowSampler{X}, volShadowSampler{X}Sampler, uv, 0.0));
|
|
262
|
+
#else
|
|
263
|
+
let shadowMapSample = textureSampleLevel(volShadowSampler{X}, volShadowSampler{X}Sampler, uv, 0.0).x;
|
|
264
|
+
#endif
|
|
265
|
+
|
|
266
|
+
#if VOL_SHADOW_MODE{X} == 1
|
|
267
|
+
// Exponential shadow map. "info.y" holds the depth scale of the generator.
|
|
268
|
+
return volFallOff(1.0 - clamp(exp(min(87.0, info.y * depthMetric)) * shadowMapSample, 0.0, 1.0 - info.x), clipSpace.xy, edge);
|
|
269
|
+
#elif VOL_SHADOW_MODE{X} == 2
|
|
270
|
+
// Close exponential shadow map.
|
|
271
|
+
return volFallOff(clamp(exp(min(87.0, -info.y * (depthMetric - shadowMapSample))), info.x, 1.0), clipSpace.xy, edge);
|
|
272
|
+
#else
|
|
273
|
+
return select(1.0, volFallOff(info.x, clipSpace.xy, edge), depthMetric > shadowMapSample);
|
|
274
|
+
#endif
|
|
275
|
+
#endif
|
|
276
|
+
#endif
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
#if VOL_SHADOW_LOCAL{X} == 1
|
|
280
|
+
// Point or spot light casting volumetric shadows: integrated over its own volume, like the others.
|
|
281
|
+
fn volIntegrateShadowSlot{X}(tStart: f32, tEnd: f32) -> vec4f {
|
|
282
|
+
let data = uniforms.volShadowLightData[{X}];
|
|
283
|
+
let direction = uniforms.volShadowLightDirection[{X}];
|
|
284
|
+
let falloff = uniforms.volShadowLightFalloff[{X}];
|
|
285
|
+
|
|
286
|
+
let chord = volLightChord(data, direction, falloff.x);
|
|
287
|
+
let t0 = max(chord.x, tStart);
|
|
288
|
+
let t1 = min(chord.y, tEnd);
|
|
289
|
+
|
|
290
|
+
if (t1 <= t0) {
|
|
291
|
+
return vec4f(0.0);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let diffuse = uniforms.volShadowLightDiffuse[{X}];
|
|
295
|
+
|
|
296
|
+
// The light clip space position of the points of the view ray is affine in their distance to the camera.
|
|
297
|
+
let clipOrigin = uniforms.volShadowMatrix[{X}] * vec4f(uniforms.volCameraPosition, 1.0);
|
|
298
|
+
let clipDirection = uniforms.volShadowMatrix[{X}] * vec4f(volRayDir, 0.0);
|
|
299
|
+
${buildLocalLightIntegrationWGSL("f32({X}) + 0.5", "contribution *= mix(1.0, volShadow{X}(position, clipOrigin + clipDirection * t), falloff.w);")}
|
|
300
|
+
}
|
|
301
|
+
#endif
|
|
302
|
+
#endif
|
|
303
|
+
`.replace(/\{X\}/g, i.toString()));
|
|
304
|
+
globalSlotContributions.push(
|
|
305
|
+
/* wgsl */ `
|
|
306
|
+
#if VOL_SHADOW_SLOT_COUNT > {X}
|
|
307
|
+
#if VOL_SHADOW_LOCAL{X} == 0
|
|
308
|
+
{
|
|
309
|
+
// Recomputed rather than kept affine to keep fewer registers alive across the march. @see the GLSL version.
|
|
310
|
+
let shadow = volShadow{X}(position, uniforms.volShadowMatrix[{X}] * vec4f(uniforms.volCameraPosition + position, 1.0));
|
|
311
|
+
scattering += volEvalDirectionalLight(uniforms.volShadowLightData[{X}], uniforms.volShadowLightDiffuse[{X}]) * mix(1.0, shadow, uniforms.volShadowLightFalloff[{X}].w);
|
|
312
|
+
stepLights += 1.0;
|
|
313
|
+
}
|
|
314
|
+
#endif
|
|
315
|
+
#endif
|
|
316
|
+
`.replace(/\{X\}/g, i.toString()));
|
|
317
|
+
localSlotContributions.push(
|
|
318
|
+
/* wgsl */ `
|
|
319
|
+
#if VOL_SHADOW_SLOT_COUNT > {X}
|
|
320
|
+
#if VOL_SHADOW_LOCAL{X} == 1
|
|
321
|
+
lightResult = volIntegrateShadowSlot{X}(tStart, tEnd);
|
|
322
|
+
accumulated += lightResult.xyz;
|
|
323
|
+
evaluatedLights += lightResult.w;
|
|
324
|
+
#endif
|
|
325
|
+
#endif
|
|
326
|
+
`.replace(/\{X\}/g, i.toString()));
|
|
327
|
+
}
|
|
328
|
+
return /* wgsl */ `
|
|
329
|
+
varying vUV: vec2f;
|
|
330
|
+
|
|
331
|
+
// Full resolution linear depth of the scene, written by the linear depth pass.
|
|
332
|
+
var textureSampler: texture_2d<f32>;
|
|
333
|
+
|
|
334
|
+
// Ratio between the resolution of the linear depth and the resolution of the scattering buffer.
|
|
335
|
+
uniform volDepthRatio: vec2f;
|
|
336
|
+
|
|
337
|
+
uniform volInverseViewProjection: mat4x4f;
|
|
338
|
+
uniform volCameraPosition: vec3f;
|
|
339
|
+
uniform volCameraForward: vec3f;
|
|
340
|
+
uniform volCameraMinMaxZ: vec2f;
|
|
341
|
+
uniform volFrameIndex: f32;
|
|
342
|
+
|
|
343
|
+
uniform volFogInfos: vec4f;
|
|
344
|
+
uniform volFogColor: vec3f;
|
|
345
|
+
uniform volLinearFogEps: f32;
|
|
346
|
+
uniform volMedium: vec4f;
|
|
347
|
+
|
|
348
|
+
#ifdef VOL_SCREEN_SHADOWS
|
|
349
|
+
uniform volViewProjection: mat4x4f;
|
|
350
|
+
uniform volScreenShadowParams: vec4f;
|
|
351
|
+
#endif
|
|
352
|
+
uniform volAmbient: vec3f;
|
|
353
|
+
|
|
354
|
+
// (maximum distance, dithering strength, light extinction clamp, number of point and spot lights in the arrays)
|
|
355
|
+
uniform volParams: vec4f;
|
|
356
|
+
|
|
357
|
+
#if VOL_MAX_ARRAY_LIGHTS > 0
|
|
358
|
+
uniform volLightData: array<vec4f, VOL_MAX_ARRAY_LIGHTS>;
|
|
359
|
+
uniform volLightDiffuse: array<vec4f, VOL_MAX_ARRAY_LIGHTS>;
|
|
360
|
+
uniform volLightDirection: array<vec4f, VOL_MAX_ARRAY_LIGHTS>;
|
|
361
|
+
uniform volLightFalloff: array<vec4f, VOL_MAX_ARRAY_LIGHTS>;
|
|
362
|
+
#endif
|
|
363
|
+
|
|
364
|
+
#if VOL_SHADOW_SLOT_COUNT > 0
|
|
365
|
+
uniform volShadowLightData: array<vec4f, VOL_SHADOW_SLOT_COUNT>;
|
|
366
|
+
uniform volShadowLightDiffuse: array<vec4f, VOL_SHADOW_SLOT_COUNT>;
|
|
367
|
+
uniform volShadowLightDirection: array<vec4f, VOL_SHADOW_SLOT_COUNT>;
|
|
368
|
+
uniform volShadowLightFalloff: array<vec4f, VOL_SHADOW_SLOT_COUNT>;
|
|
369
|
+
uniform volShadowInfo: array<vec4f, VOL_SHADOW_SLOT_COUNT>;
|
|
370
|
+
uniform volShadowMatrix: array<mat4x4f, VOL_SHADOW_SLOT_COUNT>;
|
|
371
|
+
#endif
|
|
372
|
+
|
|
373
|
+
#ifdef VOL_CSM
|
|
374
|
+
uniform volCsmLightData: vec4f;
|
|
375
|
+
uniform volCsmLightDiffuse: vec4f;
|
|
376
|
+
uniform volCsmLightDirection: vec4f;
|
|
377
|
+
uniform volCsmLightFalloff: vec4f;
|
|
378
|
+
uniform volCsmInfo: vec4f;
|
|
379
|
+
uniform volCsmMatrices: array<mat4x4f, VOL_CSM_CASCADES>;
|
|
380
|
+
|
|
381
|
+
#if VOL_CSM_KIND == 1
|
|
382
|
+
var volCsmSamplerSampler: sampler_comparison;
|
|
383
|
+
var volCsmSampler: texture_depth_2d_array;
|
|
384
|
+
#else
|
|
385
|
+
var volCsmSamplerSampler: sampler;
|
|
386
|
+
var volCsmSampler: texture_2d_array<f32>;
|
|
387
|
+
#endif
|
|
388
|
+
#endif
|
|
389
|
+
|
|
390
|
+
// Values shared by every function of the pass, computed once per pixel. Every position the raymarching
|
|
391
|
+
// manipulates is relative to the camera, which keeps the precision of the lights far from the origin.
|
|
392
|
+
var<private> volRayDir: vec3f;
|
|
393
|
+
var<private> volCosForward: f32;
|
|
394
|
+
var<private> volNoise: f32;
|
|
395
|
+
var<private> volJitter: f32;
|
|
396
|
+
|
|
397
|
+
#ifdef VOL_SCREEN_SHADOWS
|
|
398
|
+
var<private> volDepthSize: vec2i;
|
|
399
|
+
var<private> volClipOrigin: vec4f;
|
|
400
|
+
var<private> volClipDirection: vec4f;
|
|
401
|
+
#endif
|
|
402
|
+
|
|
403
|
+
${packingHelpers}
|
|
404
|
+
${buildLinearDepthHelpers("textureSampler")}
|
|
405
|
+
|
|
406
|
+
fn volMod(x: f32, y: f32) -> f32 {
|
|
407
|
+
return x - y * floor(x / y);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
fn volFallOff(value: f32, clipSpaceXY: vec2f, frustumEdgeFalloff: f32) -> f32 {
|
|
411
|
+
let mask = smoothstep(1.0 - frustumEdgeFalloff, 1.00000012, clamp(dot(clipSpaceXY, clipSpaceXY), 0.0, 1.0));
|
|
412
|
+
return mix(value, 1.0, mask);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Reproduces the depth metric computed by the "shadowsVertex" include of Babylon.js: the raw clip space
|
|
416
|
+
// Z is used, without the perspective divide, so the comparison matches what the shadow map stores.
|
|
417
|
+
fn volDepthMetric(clip: vec4f, depthValues: vec2f) -> f32 {
|
|
418
|
+
#ifdef VOL_REVERSE_DEPTH
|
|
419
|
+
return (-clip.z + depthValues.x) / depthValues.y;
|
|
420
|
+
#else
|
|
421
|
+
return (clip.z + depthValues.x) / depthValues.y;
|
|
422
|
+
#endif
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Henyey-Greenstein phase function, normalised to 1 for an isotropic medium. @see the GLSL version.
|
|
426
|
+
fn volPhaseHG(c: f32, g: f32) -> f32 {
|
|
427
|
+
let g2 = g * g;
|
|
428
|
+
let d = max(1.0 + g2 - 2.0 * g * c, 1e-4);
|
|
429
|
+
return (1.0 - g2) / (d * sqrt(d));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
fn volBayer4(p: vec2f) -> f32 {
|
|
433
|
+
let p1 = vec2f(volMod(floor(p.x), 2.0), volMod(floor(p.y), 2.0));
|
|
434
|
+
let p2 = vec2f(volMod(floor(p.x * 0.5), 2.0), volMod(floor(p.y * 0.5), 2.0));
|
|
435
|
+
|
|
436
|
+
let b1 = 2.0 * p1.x + 3.0 * p1.y - 4.0 * p1.x * p1.y;
|
|
437
|
+
let b2 = 2.0 * p2.x + 3.0 * p2.y - 4.0 * p2.x * p2.y;
|
|
438
|
+
|
|
439
|
+
return (b1 * 4.0 + b2) / 16.0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
fn volInterleavedGradientNoise(p: vec2f) -> f32 {
|
|
443
|
+
return fract(52.9829189 * fract(dot(p, vec2f(0.06711056, 0.00583715))));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Returns the offset of the samples of a light inside their strata. @see the GLSL version.
|
|
447
|
+
fn volStratumOffset(seed: f32) -> f32 {
|
|
448
|
+
#if VOL_DITHER == 0
|
|
449
|
+
return 0.5;
|
|
450
|
+
#else
|
|
451
|
+
return mix(0.5, fract(volNoise + seed * 0.618034), uniforms.volParams.y);
|
|
452
|
+
#endif
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// sigma(t) = -d/dt ln(T(t)), the exact analytic derivative of the optical depth. @see the GLSL version.
|
|
456
|
+
fn volFogExtinction(t: f32) -> f32 {
|
|
457
|
+
#if VOL_FOG_MODE == 2
|
|
458
|
+
return select(1.0 / max(uniforms.volFogInfos.z - t, uniforms.volLinearFogEps), 0.0, t < uniforms.volFogInfos.y);
|
|
459
|
+
#elif VOL_FOG_MODE == 1
|
|
460
|
+
return 2.0 * uniforms.volFogInfos.w * uniforms.volFogInfos.w * t;
|
|
461
|
+
#else
|
|
462
|
+
return uniforms.volFogInfos.w;
|
|
463
|
+
#endif
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// "height" is the absolute altitude of the sample, "t" its distance to the camera.
|
|
467
|
+
fn volExtinctionAt(height: f32, t: f32) -> f32 {
|
|
468
|
+
var sigma = volFogExtinction(t);
|
|
469
|
+
|
|
470
|
+
#ifdef VOL_HEIGHT_FOG
|
|
471
|
+
sigma *= exp(-max(0.0, height - uniforms.volMedium.y) * uniforms.volMedium.z);
|
|
472
|
+
#endif
|
|
473
|
+
|
|
474
|
+
return sigma;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Transmittance of the medium between the camera and the point of the view ray at the distance "t".
|
|
478
|
+
// @see the GLSL version.
|
|
479
|
+
#if VOL_TRANSMITTANCE == 1
|
|
480
|
+
fn volHeightSegment(s1: f32, s2: f32) -> f32 {
|
|
481
|
+
let y0 = uniforms.volCameraPosition.y - uniforms.volMedium.y;
|
|
482
|
+
let h1 = y0 + s1 * volRayDir.y;
|
|
483
|
+
let h2 = y0 + s2 * volRayDir.y;
|
|
484
|
+
|
|
485
|
+
if (h1 + h2 <= 0.0) {
|
|
486
|
+
return s2 - s1;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
let e1 = exp(-uniforms.volMedium.z * max(h1, 0.0));
|
|
490
|
+
let e2 = exp(-uniforms.volMedium.z * max(h2, 0.0));
|
|
491
|
+
let slope = uniforms.volMedium.z * volRayDir.y;
|
|
492
|
+
|
|
493
|
+
return select((e1 - e2) / slope, 0.5 * (e1 + e2) * (s2 - s1), abs(slope * (s2 - s1)) < 1e-4);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
fn volOpticalDepth(t: f32) -> f32 {
|
|
497
|
+
let crossing = select(-1.0, (uniforms.volMedium.y - uniforms.volCameraPosition.y) / volRayDir.y, abs(volRayDir.y) > 1e-6);
|
|
498
|
+
|
|
499
|
+
var opticalLength = 0.0;
|
|
500
|
+
if (crossing > 0.0 && crossing < t) {
|
|
501
|
+
opticalLength = volHeightSegment(0.0, crossing) + volHeightSegment(crossing, t);
|
|
502
|
+
} else {
|
|
503
|
+
opticalLength = volHeightSegment(0.0, t);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return uniforms.volFogInfos.w * opticalLength;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
fn volTransmittance(t: f32) -> f32 {
|
|
510
|
+
return exp(-volOpticalDepth(t));
|
|
511
|
+
}
|
|
512
|
+
#elif VOL_TRANSMITTANCE == 2
|
|
513
|
+
var<private> volOpticalDepths: array<f32, VOL_TRANSMITTANCE_SAMPLES + 1>;
|
|
514
|
+
var<private> volOpticalDepthStart: f32;
|
|
515
|
+
var<private> volOpticalDepthScale: f32;
|
|
516
|
+
|
|
517
|
+
fn volBuildOpticalDepths(tStart: f32, tEnd: f32) {
|
|
518
|
+
let stepSize = (tEnd - tStart) / f32(VOL_TRANSMITTANCE_SAMPLES);
|
|
519
|
+
|
|
520
|
+
volOpticalDepthStart = tStart;
|
|
521
|
+
volOpticalDepthScale = 1.0 / stepSize;
|
|
522
|
+
|
|
523
|
+
var previous = volExtinctionAt(uniforms.volCameraPosition.y + volRayDir.y * tStart, tStart);
|
|
524
|
+
var opticalDepth = previous * tStart;
|
|
525
|
+
|
|
526
|
+
volOpticalDepths[0] = opticalDepth;
|
|
527
|
+
|
|
528
|
+
for (var i: i32 = 1; i <= VOL_TRANSMITTANCE_SAMPLES; i++) {
|
|
529
|
+
let t = tStart + stepSize * f32(i);
|
|
530
|
+
let sigma = volExtinctionAt(uniforms.volCameraPosition.y + volRayDir.y * t, t);
|
|
531
|
+
|
|
532
|
+
opticalDepth += 0.5 * (previous + sigma) * stepSize;
|
|
533
|
+
volOpticalDepths[i] = opticalDepth;
|
|
534
|
+
previous = sigma;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
fn volOpticalDepth(t: f32) -> f32 {
|
|
539
|
+
let x = clamp((t - volOpticalDepthStart) * volOpticalDepthScale, 0.0, f32(VOL_TRANSMITTANCE_SAMPLES));
|
|
540
|
+
let i = min(i32(x), VOL_TRANSMITTANCE_SAMPLES - 1);
|
|
541
|
+
|
|
542
|
+
return mix(volOpticalDepths[i], volOpticalDepths[i + 1], x - f32(i));
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
fn volTransmittance(t: f32) -> f32 {
|
|
546
|
+
return exp(-volOpticalDepth(t));
|
|
547
|
+
}
|
|
548
|
+
#elif VOL_FOG_MODE == 2
|
|
549
|
+
fn volTransmittance(t: f32) -> f32 {
|
|
550
|
+
return clamp((uniforms.volFogInfos.z - t) / max(uniforms.volFogInfos.z - uniforms.volFogInfos.y, uniforms.volLinearFogEps), 0.0, 1.0);
|
|
551
|
+
}
|
|
552
|
+
#else
|
|
553
|
+
fn volOpticalDepth(t: f32) -> f32 {
|
|
554
|
+
#if VOL_FOG_MODE == 1
|
|
555
|
+
let x = t * uniforms.volFogInfos.w;
|
|
556
|
+
return x * x;
|
|
557
|
+
#else
|
|
558
|
+
return t * uniforms.volFogInfos.w;
|
|
559
|
+
#endif
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
fn volTransmittance(t: f32) -> f32 {
|
|
563
|
+
return exp(-volOpticalDepth(t));
|
|
564
|
+
}
|
|
565
|
+
#endif
|
|
566
|
+
|
|
567
|
+
// Transmittance of the medium to the point at the distance "t", combined with the attenuation of the light on
|
|
568
|
+
// its way to that point. @see the GLSL version.
|
|
569
|
+
fn volCombinedTransmittance(t: f32, sigmaT: f32, distanceToLight: f32) -> f32 {
|
|
570
|
+
#ifdef VOL_LIGHT_EXTINCTION
|
|
571
|
+
let lightOpticalDepth = sigmaT * min(distanceToLight, uniforms.volParams.z);
|
|
572
|
+
|
|
573
|
+
#ifdef VOL_OPTICAL_DEPTH
|
|
574
|
+
return exp(-(volOpticalDepth(t) + lightOpticalDepth));
|
|
575
|
+
#else
|
|
576
|
+
return volTransmittance(t) * exp(-lightOpticalDepth);
|
|
577
|
+
#endif
|
|
578
|
+
#else
|
|
579
|
+
return volTransmittance(t);
|
|
580
|
+
#endif
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
#ifdef VOL_SCREEN_SHADOWS
|
|
584
|
+
// Walks a segment going from a point of the medium towards a light through the depth buffer. The segment is
|
|
585
|
+
// given in clip space, where it is affine. @see the GLSL version.
|
|
586
|
+
fn volScreenShadow(clipStart: vec4f, clipDelta: vec4f, depthStart: f32, depthDelta: f32) -> f32 {
|
|
587
|
+
for (var i: i32 = 1; i <= VOL_SCREEN_SHADOW_STEPS; i++) {
|
|
588
|
+
let u = (f32(i) + volJitter) * (1.0 / f32(VOL_SCREEN_SHADOW_STEPS + 1));
|
|
589
|
+
|
|
590
|
+
let clip = clipStart + clipDelta * u;
|
|
591
|
+
if (clip.w <= 0.0) {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
let uv = (clip.xy / clip.w) * 0.5 + 0.5;
|
|
596
|
+
if (uv.x < 0.0 || uv.x >= 1.0 || uv.y < 0.0 || uv.y >= 1.0) {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
let sceneDepth = volReadLinearDepth(vec2i(uv * vec2f(volDepthSize)));
|
|
601
|
+
|
|
602
|
+
let depthDifference = depthStart + depthDelta * u - sceneDepth;
|
|
603
|
+
let bias = max(uniforms.volScreenShadowParams.y * sceneDepth, uniforms.volCameraMinMaxZ.x);
|
|
604
|
+
|
|
605
|
+
if (depthDifference > bias && depthDifference < uniforms.volScreenShadowParams.z * sceneDepth) {
|
|
606
|
+
return 0.0;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return 1.0;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Occlusion of a point or a spot light, seen from the point of the view ray at the distance "t".
|
|
614
|
+
fn volScreenShadowToLight(t: f32, lightClip: vec4f, lightDepth: f32, distanceToLight: f32) -> f32 {
|
|
615
|
+
let clipStart = volClipOrigin + volClipDirection * t;
|
|
616
|
+
let depthStart = t * volCosForward;
|
|
617
|
+
let fraction = min(1.0, uniforms.volScreenShadowParams.x / max(distanceToLight, 1e-4));
|
|
618
|
+
|
|
619
|
+
return volScreenShadow(clipStart, (lightClip - clipStart) * fraction, depthStart, (lightDepth - depthStart) * fraction);
|
|
620
|
+
}
|
|
621
|
+
#endif
|
|
622
|
+
|
|
623
|
+
// Evaluates the in-scattered radiance coming from a directional light, without any shadowing.
|
|
624
|
+
fn volEvalDirectionalLight(data: vec4f, diffuse: vec4f) -> vec3f {
|
|
625
|
+
return diffuse.rgb * volPhaseHG(dot(volRayDir, data.xyz), diffuse.a);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Evaluates the in-scattered radiance coming from a point or a spot light at the given position, relative to
|
|
629
|
+
// the camera, without any shadowing nor extinction. Returns the radiance in "xyz" and the distance to the light in "w".
|
|
630
|
+
fn volEvalLocalLight(data: vec4f, diffuse: vec4f, direction: vec4f, falloff: vec4f, position: vec3f) -> vec4f {
|
|
631
|
+
let toLight = data.xyz - position;
|
|
632
|
+
let distanceToLight = max(length(toLight), 1e-4);
|
|
633
|
+
let lightVector = toLight / distanceToLight;
|
|
634
|
+
|
|
635
|
+
// Same linear attenuation as "computeLighting" in the "lightsFragmentFunctions" include.
|
|
636
|
+
var attenuation = max(0.0, 1.0 - distanceToLight / falloff.x);
|
|
637
|
+
|
|
638
|
+
if (data.w == 2.0) {
|
|
639
|
+
let cosAngle = max(0.0, dot(direction.xyz, -lightVector));
|
|
640
|
+
attenuation *= select(0.0, max(0.0, pow(cosAngle, falloff.y)), cosAngle >= direction.w);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (attenuation <= 0.0) {
|
|
644
|
+
return vec4f(0.0, 0.0, 0.0, distanceToLight);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return vec4f(diffuse.rgb * (attenuation * volPhaseHG(dot(volRayDir, lightVector), diffuse.a)), distanceToLight);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Returns the part of the view ray crossing the cone of a spot light, cut by the sphere of its range.
|
|
651
|
+
// @see the GLSL version.
|
|
652
|
+
fn volConeChord(apex: vec3f, axis: vec3f, cosAngle: f32, range: f32, sphere: vec2f) -> vec2f {
|
|
653
|
+
if (cosAngle <= 1e-3) {
|
|
654
|
+
return sphere;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
let origin = apex * (-1.0 / range);
|
|
658
|
+
let dv = dot(volRayDir, axis);
|
|
659
|
+
let ov = dot(origin, axis);
|
|
660
|
+
let cos2 = cosAngle * cosAngle;
|
|
661
|
+
|
|
662
|
+
let a = dv * dv - cos2;
|
|
663
|
+
let b = dv * ov - cos2 * dot(volRayDir, origin);
|
|
664
|
+
let c = ov * ov - cos2 * dot(origin, origin);
|
|
665
|
+
|
|
666
|
+
if (abs(a) < 1e-4) {
|
|
667
|
+
return sphere;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
let discriminant = b * b - a * c;
|
|
671
|
+
var cone = vec2f(0.0);
|
|
672
|
+
|
|
673
|
+
if (a < 0.0) {
|
|
674
|
+
if (discriminant <= 0.0) {
|
|
675
|
+
return vec2f(1.0, -1.0);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
let s = sqrt(discriminant);
|
|
679
|
+
cone = vec2f((-b + s) / a, (-b - s) / a);
|
|
680
|
+
|
|
681
|
+
if (dv * 0.5 * (cone.x + cone.y) + ov < 0.0) {
|
|
682
|
+
return vec2f(1.0, -1.0);
|
|
683
|
+
}
|
|
684
|
+
} else {
|
|
685
|
+
let s = sqrt(max(discriminant, 0.0));
|
|
686
|
+
cone = select(vec2f(-1e30, (-b - s) / a), vec2f((-b + s) / a, 1e30), dv > 0.0);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return vec2f(max(sphere.x, (cone.x - 0.005) * range), min(sphere.y, (cone.y + 0.005) * range));
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Returns the part of the view ray, as distances to the camera, crossing the volume a point or a spot light
|
|
693
|
+
// reaches. @see the GLSL version.
|
|
694
|
+
fn volLightChord(data: vec4f, direction: vec4f, range: f32) -> vec2f {
|
|
695
|
+
let closest = dot(data.xyz, volRayDir);
|
|
696
|
+
let offset = data.xyz - volRayDir * closest;
|
|
697
|
+
let halfChord2 = range * range - dot(offset, offset);
|
|
698
|
+
|
|
699
|
+
if (halfChord2 <= 0.0) {
|
|
700
|
+
return vec2f(1.0, -1.0);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
let halfChord = sqrt(halfChord2);
|
|
704
|
+
var chord = vec2f(closest - halfChord, closest + halfChord);
|
|
705
|
+
|
|
706
|
+
if (data.w == 2.0) {
|
|
707
|
+
chord = volConeChord(data.xyz, direction.xyz, direction.w, range, chord);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return chord;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Returns the number of samples taken over the given chord of a light. @see the GLSL version.
|
|
714
|
+
fn volLocalSampleCount(chordLength: f32, range: f32, rayLength: f32) -> i32 {
|
|
715
|
+
let count = max(f32(VOL_LIGHT_STEPS) * chordLength / (2.0 * range), f32(VOL_STEPS) * chordLength / rayLength);
|
|
716
|
+
return clamp(i32(ceil(count)), 2, VOL_LOCAL_MAX_STEPS);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
${slots.join("\n")}
|
|
720
|
+
|
|
721
|
+
#ifdef VOL_CSM
|
|
722
|
+
// The parts of the view ray inside the box of each cascade, and the clip space position of the view ray in the
|
|
723
|
+
// current cascade. @see the GLSL version.
|
|
724
|
+
var<private> volCsmEntries: vec4f;
|
|
725
|
+
var<private> volCsmExits: vec4f;
|
|
726
|
+
var<private> volCsmCurrent: i32;
|
|
727
|
+
var<private> volCsmClipOrigin: vec4f;
|
|
728
|
+
var<private> volCsmClipDirection: vec4f;
|
|
729
|
+
|
|
730
|
+
// Returns the part of the view ray inside the box of a cascade. @see the GLSL version.
|
|
731
|
+
fn volCsmBoxInterval(origin: vec4f, direction: vec4f) -> vec2f {
|
|
732
|
+
#ifdef VOL_NDC_HALF_Z
|
|
733
|
+
let boxMin = vec3f(-0.96, -0.96, 0.0);
|
|
734
|
+
#else
|
|
735
|
+
let boxMin = vec3f(-0.96, -0.96, -1.0);
|
|
736
|
+
#endif
|
|
737
|
+
let boxMax = vec3f(0.96, 0.96, 1.0);
|
|
738
|
+
|
|
739
|
+
let safeDirection = select(vec3f(1e-9), direction.xyz, abs(direction.xyz) > vec3f(1e-9));
|
|
740
|
+
let inverseDirection = 1.0 / safeDirection;
|
|
741
|
+
let t0 = (boxMin - origin.xyz) * inverseDirection;
|
|
742
|
+
let t1 = (boxMax - origin.xyz) * inverseDirection;
|
|
743
|
+
let tMin = min(t0, t1);
|
|
744
|
+
let tMax = max(t0, t1);
|
|
745
|
+
|
|
746
|
+
return vec2f(max(max(tMin.x, tMin.y), tMin.z), min(min(tMax.x, tMax.y), tMax.z));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
fn volPrepareCsm() {
|
|
750
|
+
volCsmEntries = vec4f(1e30);
|
|
751
|
+
volCsmExits = vec4f(-1e30);
|
|
752
|
+
|
|
753
|
+
for (var cascade: i32 = 0; cascade < VOL_CSM_CASCADES; cascade++) {
|
|
754
|
+
let interval = volCsmBoxInterval(uniforms.volCsmMatrices[cascade] * vec4f(uniforms.volCameraPosition, 1.0), uniforms.volCsmMatrices[cascade] * vec4f(volRayDir, 0.0));
|
|
755
|
+
volCsmEntries[cascade] = interval.x;
|
|
756
|
+
volCsmExits[cascade] = interval.y;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
volCsmCurrent = -1;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Returns the shadowing of the point of the view ray at the distance "t" from the camera.
|
|
763
|
+
fn volCsmShadow(t: f32) -> f32 {
|
|
764
|
+
// The first cascade containing the sample is the most detailed one.
|
|
765
|
+
var cascade: i32 = -1;
|
|
766
|
+
for (var i: i32 = VOL_CSM_CASCADES - 1; i >= 0; i--) {
|
|
767
|
+
if (t >= volCsmEntries[i] && t <= volCsmExits[i]) {
|
|
768
|
+
cascade = i;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (cascade < 0) {
|
|
773
|
+
return 1.0;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (cascade != volCsmCurrent) {
|
|
777
|
+
volCsmCurrent = cascade;
|
|
778
|
+
volCsmClipOrigin = uniforms.volCsmMatrices[cascade] * vec4f(uniforms.volCameraPosition, 1.0);
|
|
779
|
+
volCsmClipDirection = uniforms.volCsmMatrices[cascade] * vec4f(volRayDir, 0.0);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// The projection of a directional light is orthographic, "w" is always 1.
|
|
783
|
+
let clip = volCsmClipOrigin + volCsmClipDirection * t;
|
|
784
|
+
let clipSpace = clip.xyz;
|
|
785
|
+
|
|
786
|
+
#if VOL_CSM_KIND == 1
|
|
787
|
+
var uvDepth = 0.5 * clipSpace + vec3f(0.5);
|
|
788
|
+
#ifdef VOL_NDC_HALF_Z
|
|
789
|
+
uvDepth.z = clipSpace.z;
|
|
790
|
+
#endif
|
|
791
|
+
|
|
792
|
+
let shadow = textureSampleCompareLevel(volCsmSampler, volCsmSamplerSampler, uvDepth.xy, cascade, uvDepth.z);
|
|
793
|
+
return volFallOff(mix(uniforms.volCsmInfo.x, 1.0, shadow), clipSpace.xy, uniforms.volCsmLightFalloff.z);
|
|
794
|
+
#else
|
|
795
|
+
let uv = 0.5 * clipSpace.xy + vec2f(0.5);
|
|
796
|
+
let depthMetric = clamp(volDepthMetric(clip, uniforms.volCsmInfo.zw), 0.0, 1.0);
|
|
797
|
+
|
|
798
|
+
#ifdef VOL_CSM_PACKED
|
|
799
|
+
let shadowMapSample = volUnpack(textureSampleLevel(volCsmSampler, volCsmSamplerSampler, uv, cascade, 0.0));
|
|
800
|
+
#else
|
|
801
|
+
let shadowMapSample = textureSampleLevel(volCsmSampler, volCsmSamplerSampler, uv, cascade, 0.0).x;
|
|
802
|
+
#endif
|
|
803
|
+
|
|
804
|
+
return select(1.0, volFallOff(uniforms.volCsmInfo.x, clipSpace.xy, uniforms.volCsmLightFalloff.z), depthMetric > shadowMapSample);
|
|
805
|
+
#endif
|
|
806
|
+
}
|
|
807
|
+
#endif
|
|
808
|
+
|
|
809
|
+
#if VOL_GLOBAL_LIGHT_COUNT > 0
|
|
810
|
+
// Marches the whole view ray for the lights that reach all of it: the directional lights. Returns the
|
|
811
|
+
// in-scattered radiance in "xyz" and the number of lights evaluated in "w".
|
|
812
|
+
fn volMarchGlobalLights(tStart: f32, tEnd: f32) -> vec4f {
|
|
813
|
+
#if VOL_DIRECTIONAL_LIGHT_COUNT > 0
|
|
814
|
+
#ifdef VOL_SCREEN_SHADOWS
|
|
815
|
+
// The occlusion search of a directional light always walks the same direction over the same distance,
|
|
816
|
+
// only its starting point moves along the view ray.
|
|
817
|
+
var directionClip: array<vec4f, VOL_DIRECTIONAL_LIGHT_COUNT>;
|
|
818
|
+
var directionDepth: array<f32, VOL_DIRECTIONAL_LIGHT_COUNT>;
|
|
819
|
+
|
|
820
|
+
for (var li: i32 = 0; li < VOL_DIRECTIONAL_LIGHT_COUNT; li++) {
|
|
821
|
+
let traced = uniforms.volLightData[li].xyz * uniforms.volScreenShadowParams.x;
|
|
822
|
+
directionClip[li] = uniforms.volViewProjection * vec4f(traced, 0.0);
|
|
823
|
+
directionDepth[li] = dot(traced, uniforms.volCameraForward);
|
|
824
|
+
}
|
|
825
|
+
#endif
|
|
826
|
+
#endif
|
|
827
|
+
|
|
828
|
+
#if VOL_DISTRIBUTION == 1
|
|
829
|
+
let ratio = pow(tEnd / tStart, 1.0 / f32(VOL_STEPS));
|
|
830
|
+
#else
|
|
831
|
+
let uniformStep = (tEnd - tStart) / f32(VOL_STEPS);
|
|
832
|
+
#endif
|
|
833
|
+
|
|
834
|
+
var result = vec3f(0.0);
|
|
835
|
+
var evaluatedLights = 0.0;
|
|
836
|
+
var segmentStart = tStart;
|
|
837
|
+
|
|
838
|
+
for (var i: i32 = 0; i < VOL_STEPS; i++) {
|
|
839
|
+
#if VOL_DISTRIBUTION == 1
|
|
840
|
+
let segmentEnd = segmentStart * ratio;
|
|
841
|
+
#else
|
|
842
|
+
let segmentEnd = segmentStart + uniformStep;
|
|
843
|
+
#endif
|
|
844
|
+
|
|
845
|
+
let segmentBegin = segmentStart;
|
|
846
|
+
let dt = segmentEnd - segmentBegin;
|
|
847
|
+
|
|
848
|
+
// The jitter picks the sample inside its own segment, so the marched interval stays exactly
|
|
849
|
+
// [tStart, tEnd] whatever the dithering does.
|
|
850
|
+
let t = segmentBegin + dt * volJitter;
|
|
851
|
+
segmentStart = segmentEnd;
|
|
852
|
+
|
|
853
|
+
let position = volRayDir * t;
|
|
854
|
+
let sigmaT = volExtinctionAt(uniforms.volCameraPosition.y + position.y, t);
|
|
855
|
+
|
|
856
|
+
if (sigmaT <= 1e-6) {
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// The transmittance at the START of the segment: the factor below accounts for the extinction
|
|
861
|
+
// across the segment itself.
|
|
862
|
+
let viewTransmittance = volTransmittance(segmentBegin);
|
|
863
|
+
if (viewTransmittance < 0.003) {
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
var scattering = vec3f(0.0);
|
|
868
|
+
var stepLights = 0.0;
|
|
869
|
+
|
|
870
|
+
${globalSlotContributions.join("")}
|
|
871
|
+
#ifdef VOL_CSM
|
|
872
|
+
{
|
|
873
|
+
scattering += volEvalDirectionalLight(uniforms.volCsmLightData, uniforms.volCsmLightDiffuse) * mix(1.0, volCsmShadow(t), uniforms.volCsmLightFalloff.w);
|
|
874
|
+
stepLights += 1.0;
|
|
875
|
+
}
|
|
876
|
+
#endif
|
|
877
|
+
|
|
878
|
+
#if VOL_DIRECTIONAL_LIGHT_COUNT > 0
|
|
879
|
+
for (var li: i32 = 0; li < VOL_DIRECTIONAL_LIGHT_COUNT; li++) {
|
|
880
|
+
var contribution = volEvalDirectionalLight(uniforms.volLightData[li], uniforms.volLightDiffuse[li]);
|
|
881
|
+
|
|
882
|
+
#ifdef VOL_SCREEN_SHADOWS
|
|
883
|
+
// "volLightFalloff[li].z" carries whether this light asked to be occluded by the geometry.
|
|
884
|
+
if (uniforms.volLightFalloff[li].z > 0.5) {
|
|
885
|
+
contribution *= volScreenShadow(volClipOrigin + volClipDirection * t, directionClip[li], t * volCosForward, directionDepth[li]);
|
|
886
|
+
}
|
|
887
|
+
#endif
|
|
888
|
+
|
|
889
|
+
scattering += contribution;
|
|
890
|
+
stepLights += 1.0;
|
|
891
|
+
}
|
|
892
|
+
#endif
|
|
893
|
+
|
|
894
|
+
evaluatedLights = max(evaluatedLights, stepLights);
|
|
895
|
+
|
|
896
|
+
// Analytic integral of the in-scattering over the segment. @see the GLSL version.
|
|
897
|
+
result += scattering * (viewTransmittance * (1.0 - exp(-sigmaT * dt)));
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
return vec4f(result, evaluatedLights);
|
|
901
|
+
}
|
|
902
|
+
#endif
|
|
903
|
+
|
|
904
|
+
#if VOL_LOCAL_ARRAY_LIGHTS == 1
|
|
905
|
+
// Integrates a point or a spot light of the arrays over the part of the view ray crossing its volume.
|
|
906
|
+
// Returns the in-scattered radiance in "xyz" and whether the light was evaluated in "w".
|
|
907
|
+
fn volIntegrateArrayLight(index: i32, tStart: f32, tEnd: f32) -> vec4f {
|
|
908
|
+
let data = uniforms.volLightData[index];
|
|
909
|
+
let direction = uniforms.volLightDirection[index];
|
|
910
|
+
let falloff = uniforms.volLightFalloff[index];
|
|
911
|
+
|
|
912
|
+
let chord = volLightChord(data, direction, falloff.x);
|
|
913
|
+
let t0 = max(chord.x, tStart);
|
|
914
|
+
let t1 = min(chord.y, tEnd);
|
|
915
|
+
|
|
916
|
+
if (t1 <= t0) {
|
|
917
|
+
return vec4f(0.0);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
let diffuse = uniforms.volLightDiffuse[index];
|
|
921
|
+
|
|
922
|
+
#ifdef VOL_SCREEN_SHADOWS
|
|
923
|
+
// "falloff.z" carries whether this light asked to be occluded by the geometry.
|
|
924
|
+
let occluded = falloff.z > 0.5;
|
|
925
|
+
let lightClip = volClipOrigin + uniforms.volViewProjection * vec4f(data.xyz, 0.0);
|
|
926
|
+
let lightDepth = dot(data.xyz, uniforms.volCameraForward);
|
|
927
|
+
#endif
|
|
928
|
+
|
|
929
|
+
${buildLocalLightIntegrationWGSL("f32(index)", `#ifdef VOL_SCREEN_SHADOWS
|
|
930
|
+
if (occluded) {
|
|
931
|
+
contribution *= volScreenShadowToLight(t, lightClip, lightDepth, distanceToLight);
|
|
932
|
+
}
|
|
933
|
+
#endif`)}
|
|
934
|
+
}
|
|
935
|
+
#endif
|
|
936
|
+
|
|
937
|
+
@fragment
|
|
938
|
+
fn main(input: FragmentInputs) -> FragmentOutputs {
|
|
939
|
+
// Reconstruct the world space direction of the view ray from the UV of the pixel.
|
|
940
|
+
let farPoint = uniforms.volInverseViewProjection * vec4f(input.vUV * 2.0 - 1.0, 1.0, 1.0);
|
|
941
|
+
volRayDir = normalize(farPoint.xyz / farPoint.w - uniforms.volCameraPosition);
|
|
942
|
+
|
|
943
|
+
// The depth map stores the distance along the view axis, the ray is not aligned with it.
|
|
944
|
+
volCosForward = max(dot(volRayDir, uniforms.volCameraForward), 1e-4);
|
|
945
|
+
let pixel = fragmentInputs.position.xy;
|
|
946
|
+
let depthSize = vec2i(textureDimensions(textureSampler));
|
|
947
|
+
let viewZ = volReadLinearDepth(volScatterToDepthTexel(vec2i(pixel), depthSize));
|
|
948
|
+
|
|
949
|
+
let tEnd = min(viewZ / volCosForward, uniforms.volParams.x);
|
|
950
|
+
let tStart = max(uniforms.volCameraMinMaxZ.x / volCosForward, 1e-3);
|
|
951
|
+
|
|
952
|
+
var result = vec3f(0.0);
|
|
953
|
+
var finalTransmittance = 1.0;
|
|
954
|
+
var evaluatedLights = 0.0;
|
|
955
|
+
|
|
956
|
+
// WGSL has no early return from the fragment entry point, the whole integration is guarded instead.
|
|
957
|
+
if (tEnd > tStart) {
|
|
958
|
+
// Offsets the samples inside their segments to trade the banding produced by a low sample count for noise.
|
|
959
|
+
volNoise = 0.0;
|
|
960
|
+
#if VOL_DITHER == 1
|
|
961
|
+
volNoise = volBayer4(pixel);
|
|
962
|
+
#elif VOL_DITHER == 2
|
|
963
|
+
var ditherPosition = pixel;
|
|
964
|
+
#ifdef VOL_TEMPORAL_JITTER
|
|
965
|
+
ditherPosition += uniforms.volFrameIndex * 5.588238;
|
|
966
|
+
#endif
|
|
967
|
+
volNoise = volInterleavedGradientNoise(ditherPosition);
|
|
968
|
+
#endif
|
|
969
|
+
volJitter = volNoise * uniforms.volParams.y;
|
|
970
|
+
|
|
971
|
+
#ifdef VOL_SCREEN_SHADOWS
|
|
972
|
+
volDepthSize = depthSize;
|
|
973
|
+
volClipOrigin = uniforms.volViewProjection * vec4f(uniforms.volCameraPosition, 1.0);
|
|
974
|
+
volClipDirection = uniforms.volViewProjection * vec4f(volRayDir, 0.0);
|
|
975
|
+
#endif
|
|
976
|
+
|
|
977
|
+
#if VOL_TRANSMITTANCE == 2
|
|
978
|
+
volBuildOpticalDepths(tStart, tEnd);
|
|
979
|
+
#endif
|
|
980
|
+
|
|
981
|
+
#ifdef VOL_CSM
|
|
982
|
+
volPrepareCsm();
|
|
983
|
+
#endif
|
|
984
|
+
|
|
985
|
+
// The in-scattering of the constant ambient light has a closed form whatever the medium. @see the GLSL version.
|
|
986
|
+
var accumulated = uniforms.volAmbient * (volTransmittance(tStart) - volTransmittance(tEnd));
|
|
987
|
+
var lightResult = vec4f(0.0);
|
|
988
|
+
|
|
989
|
+
#if VOL_GLOBAL_LIGHT_COUNT > 0
|
|
990
|
+
lightResult = volMarchGlobalLights(tStart, tEnd);
|
|
991
|
+
accumulated += lightResult.xyz;
|
|
992
|
+
evaluatedLights += lightResult.w;
|
|
993
|
+
#endif
|
|
994
|
+
|
|
995
|
+
${localSlotContributions.join("")}
|
|
996
|
+
|
|
997
|
+
#if VOL_LOCAL_ARRAY_LIGHTS == 1
|
|
998
|
+
let localEnd = VOL_DIRECTIONAL_LIGHT_COUNT + i32(uniforms.volParams.w + 0.5);
|
|
999
|
+
|
|
1000
|
+
for (var i: i32 = VOL_DIRECTIONAL_LIGHT_COUNT; i < VOL_MAX_ARRAY_LIGHTS; i++) {
|
|
1001
|
+
if (i >= localEnd) {
|
|
1002
|
+
break;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
lightResult = volIntegrateArrayLight(i, tStart, tEnd);
|
|
1006
|
+
accumulated += lightResult.xyz;
|
|
1007
|
+
evaluatedLights += lightResult.w;
|
|
1008
|
+
}
|
|
1009
|
+
#endif
|
|
1010
|
+
|
|
1011
|
+
// The scattering coefficient is a fraction of the extinction, which guarantees energy conservation.
|
|
1012
|
+
result = accumulated * uniforms.volMedium.x * uniforms.volFogColor;
|
|
1013
|
+
finalTransmittance = volTransmittance(tEnd);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
#if VOL_DEBUG == 3
|
|
1017
|
+
// From blue for no light to red for 16 lights or more evaluated by the pixel.
|
|
1018
|
+
let ratioOfBudget = clamp(evaluatedLights / 16.0, 0.0, 1.0);
|
|
1019
|
+
result = vec3f(ratioOfBudget, 1.0 - abs(ratioOfBudget * 2.0 - 1.0), 1.0 - ratioOfBudget);
|
|
1020
|
+
#endif
|
|
1021
|
+
|
|
1022
|
+
#ifdef VOL_LDR_ENCODE
|
|
1023
|
+
result = result / (1.0 + result);
|
|
1024
|
+
#endif
|
|
1025
|
+
|
|
1026
|
+
fragmentOutputs.color = vec4f(result, finalTransmittance);
|
|
1027
|
+
}
|
|
1028
|
+
`;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* The separable bilateral blur pass. @see buildVolumetricLightingBlurShader.
|
|
1032
|
+
*/
|
|
1033
|
+
export function buildVolumetricLightingBlurShaderWGSL() {
|
|
1034
|
+
return /* wgsl */ `
|
|
1035
|
+
varying vUV: vec2f;
|
|
1036
|
+
|
|
1037
|
+
var textureSampler: texture_2d<f32>;
|
|
1038
|
+
var volLinearDepthSampler: texture_2d<f32>;
|
|
1039
|
+
|
|
1040
|
+
uniform volCameraMinMaxZ: vec2f;
|
|
1041
|
+
uniform volDepthRatio: vec2f;
|
|
1042
|
+
uniform volBlurDirection: vec2f;
|
|
1043
|
+
uniform volBlurParams: vec2f;
|
|
1044
|
+
|
|
1045
|
+
${packingHelpers}
|
|
1046
|
+
${buildLinearDepthHelpers("volLinearDepthSampler")}
|
|
1047
|
+
|
|
1048
|
+
@fragment
|
|
1049
|
+
fn main(input: FragmentInputs) -> FragmentOutputs {
|
|
1050
|
+
// Every tap is an exact texel compared using the depth the raymarching used for it. @see the GLSL version.
|
|
1051
|
+
let size = vec2i(textureDimensions(textureSampler));
|
|
1052
|
+
let depthSize = vec2i(textureDimensions(volLinearDepthSampler));
|
|
1053
|
+
let center = vec2i(fragmentInputs.position.xy);
|
|
1054
|
+
let direction = vec2i(uniforms.volBlurDirection);
|
|
1055
|
+
|
|
1056
|
+
let centerDepth = volReadLinearDepth(volScatterToDepthTexel(center, depthSize));
|
|
1057
|
+
let inverseDepthThreshold = 1.0 / max(uniforms.volBlurParams.y * max(centerDepth, uniforms.volCameraMinMaxZ.x), 1e-4);
|
|
1058
|
+
let sigma = max(uniforms.volBlurParams.x, 1e-4);
|
|
1059
|
+
let inverseTwoSigma2 = 1.0 / (2.0 * sigma * sigma);
|
|
1060
|
+
|
|
1061
|
+
var result = textureLoad(textureSampler, center, 0);
|
|
1062
|
+
var totalWeight = 1.0;
|
|
1063
|
+
|
|
1064
|
+
for (var i: i32 = 1; i <= VOL_BLUR_RADIUS; i++) {
|
|
1065
|
+
let spatialWeight = exp(-f32(i * i) * inverseTwoSigma2);
|
|
1066
|
+
|
|
1067
|
+
let positive = center + direction * i;
|
|
1068
|
+
let negative = center - direction * i;
|
|
1069
|
+
|
|
1070
|
+
if (positive.x < size.x && positive.y < size.y) {
|
|
1071
|
+
let weight = spatialWeight * exp(-abs(volReadLinearDepth(volScatterToDepthTexel(positive, depthSize)) - centerDepth) * inverseDepthThreshold);
|
|
1072
|
+
result += textureLoad(textureSampler, positive, 0) * weight;
|
|
1073
|
+
totalWeight += weight;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (negative.x >= 0 && negative.y >= 0) {
|
|
1077
|
+
let weight = spatialWeight * exp(-abs(volReadLinearDepth(volScatterToDepthTexel(negative, depthSize)) - centerDepth) * inverseDepthThreshold);
|
|
1078
|
+
result += textureLoad(textureSampler, negative, 0) * weight;
|
|
1079
|
+
totalWeight += weight;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
fragmentOutputs.color = result / totalWeight;
|
|
1084
|
+
}
|
|
1085
|
+
`;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* The composition pass. @see buildVolumetricLightingComposeShader.
|
|
1089
|
+
*/
|
|
1090
|
+
export function buildVolumetricLightingComposeShaderWGSL() {
|
|
1091
|
+
return /* wgsl */ `
|
|
1092
|
+
varying vUV: vec2f;
|
|
1093
|
+
|
|
1094
|
+
var textureSampler: texture_2d<f32>;
|
|
1095
|
+
var volSceneSamplerSampler: sampler;
|
|
1096
|
+
var volSceneSampler: texture_2d<f32>;
|
|
1097
|
+
var volLinearDepthSampler: texture_2d<f32>;
|
|
1098
|
+
|
|
1099
|
+
uniform volCameraMinMaxZ: vec2f;
|
|
1100
|
+
uniform volDepthRatio: vec2f;
|
|
1101
|
+
uniform volComposeParams: vec4f;
|
|
1102
|
+
|
|
1103
|
+
${packingHelpers}
|
|
1104
|
+
${buildLinearDepthHelpers("volLinearDepthSampler")}
|
|
1105
|
+
|
|
1106
|
+
fn volMod(x: f32, y: f32) -> f32 {
|
|
1107
|
+
return x - y * floor(x / y);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
fn volBayer8(p: vec2f) -> f32 {
|
|
1111
|
+
let p1 = vec2f(volMod(floor(p.x), 2.0), volMod(floor(p.y), 2.0));
|
|
1112
|
+
let p2 = vec2f(volMod(floor(p.x * 0.5), 2.0), volMod(floor(p.y * 0.5), 2.0));
|
|
1113
|
+
let p3 = vec2f(volMod(floor(p.x * 0.25), 2.0), volMod(floor(p.y * 0.25), 2.0));
|
|
1114
|
+
|
|
1115
|
+
let b1 = 2.0 * p1.x + 3.0 * p1.y - 4.0 * p1.x * p1.y;
|
|
1116
|
+
let b2 = 2.0 * p2.x + 3.0 * p2.y - 4.0 * p2.x * p2.y;
|
|
1117
|
+
let b3 = 2.0 * p3.x + 3.0 * p3.y - 4.0 * p3.x * p3.y;
|
|
1118
|
+
|
|
1119
|
+
return (b1 * 16.0 + b2 * 4.0 + b3) / 64.0;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Nearest depth upsampling. @see the GLSL version.
|
|
1123
|
+
fn volUpsample(uv0: vec2f, centerDepth: f32, depthSize: vec2i) -> vec4f {
|
|
1124
|
+
let size = vec2i(textureDimensions(textureSampler));
|
|
1125
|
+
let texelCoordinate = uv0 * vec2f(size) - 0.5;
|
|
1126
|
+
let baseCoordinate = floor(texelCoordinate);
|
|
1127
|
+
let fraction = texelCoordinate - baseCoordinate;
|
|
1128
|
+
|
|
1129
|
+
let base = vec2i(baseCoordinate);
|
|
1130
|
+
let maximum = size - vec2i(1);
|
|
1131
|
+
|
|
1132
|
+
let inverseDepthThreshold = 1.0 / max(uniforms.volComposeParams.z * max(centerDepth, uniforms.volCameraMinMaxZ.x), 1e-4);
|
|
1133
|
+
|
|
1134
|
+
var result = vec4f(0.0);
|
|
1135
|
+
var totalWeight = 0.0;
|
|
1136
|
+
|
|
1137
|
+
var nearestSample = vec4f(0.0);
|
|
1138
|
+
var nearestDistance = 1e20;
|
|
1139
|
+
|
|
1140
|
+
for (var y: i32 = 0; y < 2; y++) {
|
|
1141
|
+
for (var x: i32 = 0; x < 2; x++) {
|
|
1142
|
+
let coordinates = clamp(base + vec2i(x, y), vec2i(0), maximum);
|
|
1143
|
+
|
|
1144
|
+
let bilinearWeight = select(fraction.x, 1.0 - fraction.x, x == 0) * select(fraction.y, 1.0 - fraction.y, y == 0);
|
|
1145
|
+
let depthDistance = abs(volReadLinearDepth(volScatterToDepthTexel(coordinates, depthSize)) - centerDepth);
|
|
1146
|
+
|
|
1147
|
+
let tap = textureLoad(textureSampler, coordinates, 0);
|
|
1148
|
+
|
|
1149
|
+
if (depthDistance < nearestDistance) {
|
|
1150
|
+
nearestDistance = depthDistance;
|
|
1151
|
+
nearestSample = tap;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
let weight = bilinearWeight / (1e-4 + depthDistance * inverseDepthThreshold);
|
|
1155
|
+
result += tap * weight;
|
|
1156
|
+
totalWeight += weight;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// Every tap sits on the other side of a silhouette, fall back to the closest one instead of averaging.
|
|
1161
|
+
return select(nearestSample, result / totalWeight, totalWeight > 1e-4);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
@fragment
|
|
1165
|
+
fn main(input: FragmentInputs) -> FragmentOutputs {
|
|
1166
|
+
// The linear depth has the full resolution of the canvas. @see the GLSL version.
|
|
1167
|
+
let depthSize = vec2i(textureDimensions(volLinearDepthSampler));
|
|
1168
|
+
let centerDepth = volReadLinearDepth(min(vec2i(input.vUV * vec2f(depthSize)), depthSize - 1));
|
|
1169
|
+
|
|
1170
|
+
var volumetric = volUpsample(input.vUV, centerDepth, depthSize);
|
|
1171
|
+
|
|
1172
|
+
#ifdef VOL_LDR_ENCODE
|
|
1173
|
+
volumetric = vec4f(volumetric.rgb / max(vec3f(1e-4), 1.0 - volumetric.rgb), volumetric.a);
|
|
1174
|
+
#endif
|
|
1175
|
+
|
|
1176
|
+
// WGSL has no early return from the fragment entry point, so the debug outputs are branches instead.
|
|
1177
|
+
#if VOL_DEBUG == 1 || VOL_DEBUG == 3
|
|
1178
|
+
fragmentOutputs.color = vec4f(volumetric.rgb * uniforms.volComposeParams.x, 1.0);
|
|
1179
|
+
#elif VOL_DEBUG == 2
|
|
1180
|
+
fragmentOutputs.color = vec4f(vec3f(volumetric.a), 1.0);
|
|
1181
|
+
#else
|
|
1182
|
+
var sceneColor = textureSampleLevel(volSceneSampler, volSceneSamplerSampler, input.vUV, 0.0).rgb;
|
|
1183
|
+
|
|
1184
|
+
sceneColor *= mix(1.0, volumetric.a, uniforms.volComposeParams.y);
|
|
1185
|
+
|
|
1186
|
+
var result = sceneColor + volumetric.rgb * uniforms.volComposeParams.x;
|
|
1187
|
+
|
|
1188
|
+
// Breaks up the banding an 8 bits output would otherwise show on the smooth gradients of the shafts.
|
|
1189
|
+
result += (volBayer8(fragmentInputs.position.xy) - 0.5) * uniforms.volComposeParams.w;
|
|
1190
|
+
|
|
1191
|
+
fragmentOutputs.color = vec4f(result, 1.0);
|
|
1192
|
+
#endif
|
|
1193
|
+
}
|
|
1194
|
+
`;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Returns the four WGSL sources of the pipeline, keyed by the name they are registered under.
|
|
1198
|
+
*/
|
|
1199
|
+
export function getVolumetricLightingShadersWGSL() {
|
|
1200
|
+
return {
|
|
1201
|
+
linearDepth: buildVolumetricLightingLinearDepthShaderWGSL(),
|
|
1202
|
+
scattering: buildVolumetricLightingScatteringShaderWGSL(maxVolumetricShadowSlots),
|
|
1203
|
+
blur: buildVolumetricLightingBlurShaderWGSL(),
|
|
1204
|
+
compose: buildVolumetricLightingComposeShaderWGSL(),
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
//# sourceMappingURL=shaders-wgsl.js.map
|